From d1c09f2561f75a911f044245c96ad2e862444c6c Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 9 Nov 2018 19:34:55 -0500 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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.