diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 933386615..a9fed18ba 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -8,7 +8,6 @@ on: jobs: mdbook-deploy: runs-on: ubuntu-latest - needs: mdbook steps: - uses: actions/checkout@v1 diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 41b9a1ed3..000000000 --- a/Cargo.lock +++ /dev/null @@ -1,2765 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "actix-codec" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" -dependencies = [ - "bitflags", - "bytes 0.5.6", - "futures-core", - "futures-sink", - "log", - "pin-project 0.4.28", - "tokio 0.2.25", - "tokio-util", -] - -[[package]] -name = "actix-connect" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" -dependencies = [ - "actix-codec", - "actix-rt 1.1.1", - "actix-service", - "actix-utils", - "derive_more", - "either", - "futures-util", - "http", - "log", - "trust-dns-proto", - "trust-dns-resolver", -] - -[[package]] -name = "actix-http" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" -dependencies = [ - "actix-codec", - "actix-connect", - "actix-rt 1.1.1", - "actix-service", - "actix-threadpool", - "actix-utils", - "base64", - "bitflags", - "brotli2", - "bytes 0.5.6", - "cookie", - "copyless", - "derive_more", - "either", - "encoding_rs", - "flate2", - "futures-channel", - "futures-core", - "futures-util", - "fxhash", - "h2", - "http", - "httparse", - "indexmap", - "itoa", - "language-tags", - "lazy_static", - "log", - "mime", - "percent-encoding", - "pin-project 1.0.7", - "rand 0.7.3", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "sha-1", - "slab", - "time 0.2.26", -] - -[[package]] -name = "actix-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcb2b608f0accc2f5bcf3dd872194ce13d94ee45b571487035864cf966b04ef" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c" -dependencies = [ - "bytestring", - "http", - "log", - "regex", - "serde", -] - -[[package]] -name = "actix-rt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" -dependencies = [ - "actix-macros 0.1.3", - "actix-threadpool", - "copyless", - "futures-channel", - "futures-util", - "smallvec", - "tokio 0.2.25", -] - -[[package]] -name = "actix-rt" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d7cd957c9ed92288a7c3c96af81fa5291f65247a76a34dac7b6af74e52ba0" -dependencies = [ - "actix-macros 0.2.0", - "futures-core", - "tokio 1.5.0", -] - -[[package]] -name = "actix-server" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" -dependencies = [ - "actix-codec", - "actix-rt 1.1.1", - "actix-service", - "actix-utils", - "futures-channel", - "futures-util", - "log", - "mio 0.6.23", - "mio-uds", - "num_cpus", - "slab", - "socket2", -] - -[[package]] -name = "actix-service" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" -dependencies = [ - "futures-util", - "pin-project 0.4.28", -] - -[[package]] -name = "actix-testing" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" -dependencies = [ - "actix-macros 0.1.3", - "actix-rt 1.1.1", - "actix-server", - "actix-service", - "log", - "socket2", -] - -[[package]] -name = "actix-threadpool" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" -dependencies = [ - "derive_more", - "futures-channel", - "lazy_static", - "log", - "num_cpus", - "parking_lot", - "threadpool", -] - -[[package]] -name = "actix-tls" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" -dependencies = [ - "actix-codec", - "actix-service", - "actix-utils", - "futures-util", -] - -[[package]] -name = "actix-utils" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" -dependencies = [ - "actix-codec", - "actix-rt 1.1.1", - "actix-service", - "bitflags", - "bytes 0.5.6", - "either", - "futures-channel", - "futures-sink", - "futures-util", - "log", - "pin-project 0.4.28", - "slab", -] - -[[package]] -name = "actix-web" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros 0.1.3", - "actix-router", - "actix-rt 1.1.1", - "actix-server", - "actix-service", - "actix-testing", - "actix-threadpool", - "actix-tls", - "actix-utils", - "actix-web-codegen", - "awc", - "bytes 0.5.6", - "derive_more", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "fxhash", - "log", - "mime", - "pin-project 1.0.7", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "socket2", - "time 0.2.26", - "tinyvec", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - -[[package]] -name = "aho-corasick" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "anyhow" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "assert_cmd" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2475b58cd94eb4f70159f4fd8844ba3b807532fe3131b3373fae060bbe30396" -dependencies = [ - "bstr", - "doc-comment", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "async-trait" -version = "0.1.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "awc" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" -dependencies = [ - "actix-codec", - "actix-http", - "actix-rt 1.1.1", - "actix-service", - "base64", - "bytes 0.5.6", - "cfg-if 1.0.0", - "derive_more", - "futures-core", - "log", - "mime", - "percent-encoding", - "rand 0.7.3", - "serde", - "serde_json", - "serde_urlencoded", -] - -[[package]] -name = "base-x" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "brotli2" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" -dependencies = [ - "brotli-sys", - "libc", -] - -[[package]] -name = "bstr" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - -[[package]] -name = "bytes" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" - -[[package]] -name = "bytestring" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" -dependencies = [ - "bytes 1.0.1", -] - -[[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "serde", - "time 0.1.43", - "winapi 0.3.9", -] - -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] -name = "clap" -version = "2.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap 0.11.0", - "unicode-width", - "vec_map", -] - -[[package]] -name = "config" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" -dependencies = [ - "lazy_static", - "nom 5.1.2", - "serde", - "yaml-rust", -] - -[[package]] -name = "const_fn" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28" - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" -dependencies = [ - "percent-encoding", - "time 0.2.26", - "version_check", -] - -[[package]] -name = "copyless" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" - -[[package]] -name = "cpuid-bool" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" - -[[package]] -name = "crc32fast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "lazy_static", -] - -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - -[[package]] -name = "derive_more" -version = "0.99.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dirs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -dependencies = [ - "libc", - "redox_users 0.3.5", - "winapi 0.3.9", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users 0.4.0", - "winapi 0.3.9", -] - -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "enum-as-inner" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "env_logger" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "flate2" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" -dependencies = [ - "cfg-if 1.0.0", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "float-cmp" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "futures" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" - -[[package]] -name = "futures-executor" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" - -[[package]] -name = "futures-macro" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" - -[[package]] -name = "futures-task" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" - -[[package]] -name = "futures-util" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite 0.2.6", - "pin-utils", - "proc-macro-hack", - "proc-macro-nested", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "h2" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" -dependencies = [ - "bytes 0.5.6", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio 0.2.25", - "tokio-util", - "tracing", - "tracing-futures", -] - -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "heck" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.9", -] - -[[package]] -name = "http" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" -dependencies = [ - "bytes 1.0.1", - "fnv", - "itoa", -] - -[[package]] -name = "httparse" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "instant" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - -[[package]] -name = "ipconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" -dependencies = [ - "socket2", - "widestring", - "winapi 0.3.9", - "winreg", -] - -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - -[[package]] -name = "js-sys" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lexical-core" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if 1.0.0", - "ryu", - "static_assertions", -] - -[[package]] -name = "libc" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" - -[[package]] -name = "libsqlite3-sys" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19cb1effde5f834799ac5e5ef0e40d45027cd74f271b1de786ba8abb30e2164d" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - -[[package]] -name = "lock_api" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "memchr" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "mio" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" -dependencies = [ - "libc", - "log", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio 0.6.23", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - -[[package]] -name = "nom" -version = "6.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" -dependencies = [ - "bitvec", - "funty", - "lexical-core", - "memchr", - "version_check", -] - -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "parking_lot" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.6", - "smallvec", - "winapi 0.3.9", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918192b5c59119d51e0cd221f4d49dde9112824ba717369e903c97d076083d0f" -dependencies = [ - "pin-project-internal 0.4.28", -] - -[[package]] -name = "pin-project" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" -dependencies = [ - "pin-project-internal 1.0.7", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be26700300be6d9d23264c73211d8190e755b6b5ca7a1b28230025511b52a5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - -[[package]] -name = "pin-project-lite" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "predicates" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb433456c1a57cc93554dea3ce40b4c19c4057e41c55d4a0f3d84ea71c325aa" -dependencies = [ - "difference", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" - -[[package]] -name = "predicates-tree" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" -dependencies = [ - "predicates-core", - "treeline", -] - -[[package]] -name = "prettytable-rs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" -dependencies = [ - "atty", - "csv", - "encode_unicode", - "lazy_static", - "term", - "unicode-width", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - -[[package]] -name = "proc-macro2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "proptest" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" -dependencies = [ - "bit-set", - "bitflags", - "byteorder", - "lazy_static", - "num-traits", - "quick-error 2.0.0", - "rand 0.8.3", - "rand_chacha 0.3.0", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", -] - -[[package]] -name = "protobuf" -version = "2.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b7f4a129bb3754c25a4e04032a90173c68f85168f77118ac4cb4936e7f06f92" - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-error" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha 0.3.0", - "rand_core 0.6.2", - "rand_hc 0.3.0", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.2", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" -dependencies = [ - "getrandom 0.2.2", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core 0.6.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core 0.6.2", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_syscall" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" -dependencies = [ - "getrandom 0.1.16", - "redox_syscall 0.1.57", - "rust-argon2", -] - -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom 0.2.2", - "redox_syscall 0.2.6", -] - -[[package]] -name = "regex" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" -dependencies = [ - "byteorder", -] - -[[package]] -name = "regex-syntax" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi 0.3.9", -] - -[[package]] -name = "rusqlite" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc783b7ddae608338003bac1fa00b6786a75a9675fbd8e87243ecfdea3f6ed2" -dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "memchr", - "smallvec", -] - -[[package]] -name = "rust-argon2" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustls" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error 1.2.3", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.125" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha-1" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" -dependencies = [ - "block-buffer", - "cfg-if 1.0.0", - "cpuid-bool", - "digest", - "opaque-debug", -] - -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - -[[package]] -name = "signal-hook-registry" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - -[[package]] -name = "smawk" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "syn" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "taskchampion" -version = "0.3.0" -dependencies = [ - "anyhow", - "chrono", - "log", - "proptest", - "rusqlite", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tindercrypt", - "ureq", - "uuid", -] - -[[package]] -name = "taskchampion-cli" -version = "0.3.0" -dependencies = [ - "anyhow", - "assert_cmd", - "atty", - "config", - "dirs-next", - "env_logger", - "log", - "nom 6.1.2", - "predicates", - "prettytable-rs", - "taskchampion", - "tempfile", - "termcolor", - "textwrap 0.13.4", -] - -[[package]] -name = "taskchampion-sync-server" -version = "0.3.0" -dependencies = [ - "actix-rt 2.2.0", - "actix-web", - "anyhow", - "clap", - "env_logger", - "futures", - "log", - "rusqlite", - "serde", - "serde_json", - "tempfile", - "thiserror", - "uuid", -] - -[[package]] -name = "tempfile" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "rand 0.8.3", - "redox_syscall 0.2.6", - "remove_dir_all", - "winapi 0.3.9", -] - -[[package]] -name = "term" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" -dependencies = [ - "byteorder", - "dirs", - "winapi 0.3.9", -] - -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "terminal_size" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" -dependencies = [ - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "textwrap" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835" -dependencies = [ - "smawk", - "terminal_size", - "unicode-width", -] - -[[package]] -name = "thiserror" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "time" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi 0.3.9", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] - -[[package]] -name = "tindercrypt" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34fd5cc8db265f27abf29e8a3cec5cc643ae9f3f9ae39f08a77dc4add375b6d" -dependencies = [ - "protobuf", - "rand 0.7.3", - "ring", - "thiserror", -] - -[[package]] -name = "tinyvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" -dependencies = [ - "bytes 0.5.6", - "futures-core", - "iovec", - "lazy_static", - "libc", - "memchr", - "mio 0.6.23", - "mio-uds", - "pin-project-lite 0.1.12", - "signal-hook-registry", - "slab", - "winapi 0.3.9", -] - -[[package]] -name = "tokio" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" -dependencies = [ - "autocfg", - "libc", - "mio 0.7.11", - "once_cell", - "parking_lot", - "pin-project-lite 0.2.6", - "signal-hook-registry", - "winapi 0.3.9", -] - -[[package]] -name = "tokio-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -dependencies = [ - "bytes 0.5.6", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.1.12", - "tokio 0.2.25", -] - -[[package]] -name = "tracing" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" -dependencies = [ - "cfg-if 1.0.0", - "log", - "pin-project-lite 0.2.6", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project 1.0.7", - "tracing", -] - -[[package]] -name = "treeline" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" - -[[package]] -name = "trust-dns-proto" -version = "0.19.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "enum-as-inner", - "futures", - "idna", - "lazy_static", - "log", - "rand 0.7.3", - "smallvec", - "thiserror", - "tokio 0.2.25", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.19.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" -dependencies = [ - "cfg-if 0.1.10", - "futures", - "ipconfig", - "lazy_static", - "log", - "lru-cache", - "resolv-conf", - "smallvec", - "thiserror", - "tokio 0.2.25", - "trust-dns-proto", -] - -[[package]] -name = "typenum" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" - -[[package]] -name = "unicode-bidi" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "ureq" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" -dependencies = [ - "base64", - "chunked_transfer", - "log", - "once_cell", - "rustls", - "url", - "webpki", - "webpki-roots", -] - -[[package]] -name = "url" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.2", - "serde", -] - -[[package]] -name = "vcpkg" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasm-bindgen" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" - -[[package]] -name = "web-sys" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/POLICY.md b/POLICY.md new file mode 100644 index 000000000..9f9af590b --- /dev/null +++ b/POLICY.md @@ -0,0 +1,55 @@ +# Compatibility & deprecation + +Until TaskChampion reaches [v1.0.0](https://github.com/taskchampion/taskchampion/milestone/7), nothing is set in stone. That being said, we aim for the following: + +1. Major versions represent significant change and may be incompatible with previous major release. +2. Minor versions are always backwards compatible and might add some new functionality. +3. Patch versions should not introduce any new functionality and do what name implies — fix bugs. + +As there are no major releases yet, we do not support any older versions. Users are encouraged to use the latest release. + +## ABI policy + +1. We target stable `rustc`. +2. TaskChampion will never upgrade any storage to a non-compatible version without explicit user's request. + +## API policy + +1. Deprecated features return a warning at least 1 minor version prior to being removed. + + Example: + + > If support of `--bar` is to be dropped in v2.0.0, we shall announce it in v1.9.0 at latest. + +2. We aim to issue a notice of newly added functionality when appropriate. + + Example: + + > "NOTICE: Since v1.1.0 you can use `--foo` in conjunction with `--bar`. Foobar!" + +3. TaskChampion always uses UTF-8. + +## Command-line interface + +Considered to be part of the API policy. + +## CLI exit codes + +- `0` No errors, normal exit. +- `1` Generic error. +- `2` Never used to avoid conflicts with Bash. +- `3` Unable to execute with the given parameters. +- `4` I/O error. +- `5` Database error. + +# Security + +To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `D8097934A92E4B4210368102FF8B7AC6154E3226` which is available [here](https://keybase.io/djmitche/pgp_keys.asc?fingerprint=d8097934a92e4b4210368102ff8b7ac6154e3226). Initial response is expected within ~48h. + +We kinldy ask to follow the responsible disclosure model and refrain from sharing information until: +1. Vulnerabilities are patched in TaskChampion + 60 days to coordinate with distributions. +2. 90 days since the vulnerability is disclosed to us. + +We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally. + +We will assist with obtaining CVE and acknowledge the vulnerabilites reported. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 3f9c867c2..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Security Policy - -## Supported Versions - -This software is currently pre-release, so no versions are formally supported. - -Once 1.0 has been released, only the most recent version will be supported. - -## Reporting a Vulnerability - -To report a vulnerability in this application, contact me directly at `dustin@cs.uchicago.edu`. -You can expect an initial response within a day or two, and a regular email conversational cadence thereafter. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 570d106da..d66cc63b9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -14,11 +14,8 @@ prettytable-rs = "^0.8.0" textwrap = { version="^0.13.4", features=["terminal_size"] } termcolor = "^1.1.2" atty = "^0.2.14" - -[dependencies.config] -default-features = false -features = ["yaml"] -version = "^0.11.0" +toml = "^0.5.8" +toml_edit = "^0.2.0" [dependencies.taskchampion] path = "../taskchampion" diff --git a/cli/src/argparse/command.rs b/cli/src/argparse/command.rs index f9c71352c..79a715ec7 100644 --- a/cli/src/argparse/command.rs +++ b/cli/src/argparse/command.rs @@ -49,10 +49,10 @@ mod test { #[test] fn test_version() { assert_eq!( - Command::from_argv(argv!["task", "version"]).unwrap(), + Command::from_argv(argv!["ta", "version"]).unwrap(), Command { subcommand: Subcommand::Version, - command_name: s!("task"), + command_name: s!("ta"), } ); } diff --git a/cli/src/argparse/config.rs b/cli/src/argparse/config.rs new file mode 100644 index 000000000..924164564 --- /dev/null +++ b/cli/src/argparse/config.rs @@ -0,0 +1,36 @@ +use super::args::{any, arg_matching, literal}; +use super::ArgList; +use crate::usage; +use nom::{combinator::*, sequence::*, IResult}; + +#[derive(Debug, PartialEq)] +/// A config operation +pub(crate) enum ConfigOperation { + /// Set a configuration value + Set(String, String), +} + +impl ConfigOperation { + pub(super) fn parse(input: ArgList) -> IResult { + fn set_to_op(input: (&str, &str, &str)) -> Result { + Ok(ConfigOperation::Set(input.1.to_owned(), input.2.to_owned())) + } + map_res( + tuple(( + arg_matching(literal("set")), + arg_matching(any), + arg_matching(any), + )), + set_to_op, + )(input) + } + + pub(super) fn get_usage(u: &mut usage::Usage) { + u.subcommands.push(usage::Subcommand { + name: "config set", + syntax: "config set ", + summary: "Set a configuration value", + description: "Update Taskchampion configuration file to set key = value", + }); + } +} diff --git a/cli/src/argparse/mod.rs b/cli/src/argparse/mod.rs index 3bdac1a8f..88de59046 100644 --- a/cli/src/argparse/mod.rs +++ b/cli/src/argparse/mod.rs @@ -18,12 +18,14 @@ That is, they contain no references, and have no methods to aid in their executi */ mod args; mod command; +mod config; mod filter; mod modification; mod subcommand; pub(crate) use args::TaskId; pub(crate) use command::Command; +pub(crate) use config::ConfigOperation; pub(crate) use filter::{Condition, Filter}; pub(crate) use modification::{DescriptionMod, Modification}; pub(crate) use subcommand::Subcommand; diff --git a/cli/src/argparse/subcommand.rs b/cli/src/argparse/subcommand.rs index 1d7ddc2ba..5609192b7 100644 --- a/cli/src/argparse/subcommand.rs +++ b/cli/src/argparse/subcommand.rs @@ -1,5 +1,5 @@ use super::args::*; -use super::{ArgList, DescriptionMod, Filter, Modification}; +use super::{ArgList, ConfigOperation, DescriptionMod, Filter, Modification}; use crate::usage; use nom::{branch::alt, combinator::*, sequence::*, IResult}; use taskchampion::Status; @@ -25,6 +25,11 @@ pub(crate) enum Subcommand { summary: bool, }, + /// Manipulate configuration + Config { + config_operation: ConfigOperation, + }, + /// Add a new task Add { modification: Modification, @@ -61,6 +66,7 @@ impl Subcommand { all_consuming(alt(( Version::parse, Help::parse, + Config::parse, Add::parse, Modify::parse, Info::parse, @@ -74,6 +80,7 @@ impl Subcommand { pub(super) fn get_usage(u: &mut usage::Usage) { Version::get_usage(u); Help::get_usage(u); + Config::get_usage(u); Add::get_usage(u); Modify::get_usage(u); Info::get_usage(u); @@ -131,6 +138,26 @@ impl Help { fn get_usage(_u: &mut usage::Usage) {} } +struct Config; + +impl Config { + fn parse(input: ArgList) -> IResult { + fn to_subcommand(input: (&str, ConfigOperation)) -> Result { + Ok(Subcommand::Config { + config_operation: input.1, + }) + } + map_res( + tuple((arg_matching(literal("config")), ConfigOperation::parse)), + to_subcommand, + )(input) + } + + fn get_usage(u: &mut usage::Usage) { + ConfigOperation::get_usage(u); + } +} + struct Add; impl Add { @@ -427,6 +454,19 @@ mod test { ); } + #[test] + fn test_config_set() { + assert_eq!( + Subcommand::parse(argv!["config", "set", "x", "y"]).unwrap(), + ( + &EMPTY[..], + Subcommand::Config { + config_operation: ConfigOperation::Set("x".to_owned(), "y".to_owned()) + } + ) + ); + } + #[test] fn test_add_description() { let subcommand = Subcommand::Add { diff --git a/cli/src/bin/task.rs b/cli/src/bin/ta.rs similarity index 78% rename from cli/src/bin/task.rs rename to cli/src/bin/ta.rs index 8d8a756cb..ecf529be3 100644 --- a/cli/src/bin/task.rs +++ b/cli/src/bin/ta.rs @@ -2,7 +2,7 @@ use std::process::exit; pub fn main() { if let Err(err) = taskchampion_cli::main() { - eprintln!("{}", err); + eprintln!("{:?}", err); exit(1); } } diff --git a/cli/src/invocation/cmd/config.rs b/cli/src/invocation/cmd/config.rs new file mode 100644 index 000000000..0f34defae --- /dev/null +++ b/cli/src/invocation/cmd/config.rs @@ -0,0 +1,62 @@ +use crate::argparse::ConfigOperation; +use crate::settings::Settings; +use termcolor::{ColorSpec, WriteColor}; + +pub(crate) fn execute( + w: &mut W, + config_operation: ConfigOperation, + settings: &Settings, +) -> anyhow::Result<()> { + match config_operation { + ConfigOperation::Set(key, value) => { + let filename = settings.set(&key, &value)?; + write!(w, "Set configuration value ")?; + w.set_color(ColorSpec::new().set_bold(true))?; + write!(w, "{}", &key)?; + w.set_color(ColorSpec::new().set_bold(false))?; + write!(w, " in ")?; + w.set_color(ColorSpec::new().set_bold(true))?; + writeln!(w, "{:?}.", filename)?; + w.set_color(ColorSpec::new().set_bold(false))?; + } + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::invocation::test::*; + use std::fs; + use tempfile::TempDir; + + #[test] + fn test_config_set() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + fs::write( + cfg_file.clone(), + "# store data everywhere\ndata_dir = \"/nowhere\"\n", + ) + .unwrap(); + + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + + let mut w = test_writer(); + + execute( + &mut w, + ConfigOperation::Set("data_dir".to_owned(), "/somewhere".to_owned()), + &settings, + ) + .unwrap(); + assert!(w.into_string().starts_with("Set configuration value ")); + + let updated_toml = fs::read_to_string(cfg_file.clone()).unwrap(); + dbg!(&updated_toml); + assert_eq!( + updated_toml, + "# store data everywhere\ndata_dir = \"/somewhere\"\n" + ); + } +} diff --git a/cli/src/invocation/cmd/help.rs b/cli/src/invocation/cmd/help.rs index 878234933..421c140a7 100644 --- a/cli/src/invocation/cmd/help.rs +++ b/cli/src/invocation/cmd/help.rs @@ -19,12 +19,12 @@ mod test { #[test] fn test_summary() { let mut w = test_writer(); - execute(&mut w, s!("task"), true).unwrap(); + execute(&mut w, s!("ta"), true).unwrap(); } #[test] fn test_long() { let mut w = test_writer(); - execute(&mut w, s!("task"), false).unwrap(); + execute(&mut w, s!("ta"), false).unwrap(); } } diff --git a/cli/src/invocation/cmd/mod.rs b/cli/src/invocation/cmd/mod.rs index 18a973ebb..9371f1f2c 100644 --- a/cli/src/invocation/cmd/mod.rs +++ b/cli/src/invocation/cmd/mod.rs @@ -1,6 +1,7 @@ //! Responsible for executing commands as parsed by [`crate::argparse`]. pub(crate) mod add; +pub(crate) mod config; pub(crate) mod gc; pub(crate) mod help; pub(crate) mod info; diff --git a/cli/src/invocation/cmd/report.rs b/cli/src/invocation/cmd/report.rs index bcb258298..7123f0353 100644 --- a/cli/src/invocation/cmd/report.rs +++ b/cli/src/invocation/cmd/report.rs @@ -1,13 +1,13 @@ use crate::argparse::Filter; use crate::invocation::display_report; -use config::Config; +use crate::settings::Settings; use taskchampion::Replica; use termcolor::WriteColor; pub(crate) fn execute( w: &mut W, replica: &mut Replica, - settings: &Config, + settings: &Settings, report_name: String, filter: Filter, ) -> anyhow::Result<()> { @@ -30,7 +30,7 @@ mod test { // The function being tested is only one line long, so this is sort of an integration test // for display_report. - let settings = crate::settings::default_settings().unwrap(); + let settings = Default::default(); let report_name = "next".to_owned(); let filter = Filter { ..Default::default() diff --git a/cli/src/invocation/cmd/sync.rs b/cli/src/invocation/cmd/sync.rs index 9a1bb18d2..2e9400642 100644 --- a/cli/src/invocation/cmd/sync.rs +++ b/cli/src/invocation/cmd/sync.rs @@ -6,7 +6,7 @@ pub(crate) fn execute( replica: &mut Replica, server: &mut Box, ) -> anyhow::Result<()> { - replica.sync(server).unwrap(); + replica.sync(server)?; writeln!(w, "sync complete.")?; Ok(()) } diff --git a/cli/src/invocation/mod.rs b/cli/src/invocation/mod.rs index feff61c27..a9723fe9a 100644 --- a/cli/src/invocation/mod.rs +++ b/cli/src/invocation/mod.rs @@ -1,7 +1,7 @@ //! The invocation module handles invoking the commands parsed by the argparse module. use crate::argparse::{Command, Subcommand}; -use config::Config; +use crate::settings::Settings; use taskchampion::{Replica, Server, ServerConfig, StorageConfig, Uuid}; use termcolor::{ColorChoice, StandardStream}; @@ -19,7 +19,7 @@ use report::display_report; /// Invoke the given Command in the context of the given settings #[allow(clippy::needless_return)] -pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { +pub(crate) fn invoke(command: Command, settings: Settings) -> anyhow::Result<()> { log::debug!("command: {:?}", command); log::debug!("settings: {:?}", settings); @@ -35,6 +35,10 @@ pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { subcommand: Subcommand::Help { summary }, command_name, } => return cmd::help::execute(&mut w, command_name, summary), + Command { + subcommand: Subcommand::Config { config_operation }, + .. + } => return cmd::config::execute(&mut w, config_operation, &settings), Command { subcommand: Subcommand::Version, .. @@ -90,6 +94,10 @@ pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { subcommand: Subcommand::Help { .. }, .. } => unreachable!(), + Command { + subcommand: Subcommand::Config { .. }, + .. + } => unreachable!(), Command { subcommand: Subcommand::Version, .. @@ -100,35 +108,33 @@ pub(crate) fn invoke(command: Command, settings: Config) -> anyhow::Result<()> { // utilities for invoke /// Get the replica for this invocation -fn get_replica(settings: &Config) -> anyhow::Result { - let taskdb_dir = settings.get_str("data_dir")?.into(); +fn get_replica(settings: &Settings) -> anyhow::Result { + let taskdb_dir = settings.data_dir.clone(); log::debug!("Replica data_dir: {:?}", taskdb_dir); let storage_config = StorageConfig::OnDisk { taskdb_dir }; Ok(Replica::new(storage_config.into_storage()?)) } /// Get the server for this invocation -fn get_server(settings: &Config) -> anyhow::Result> { +fn get_server(settings: &Settings) -> anyhow::Result> { // if server_client_key and server_origin are both set, use // the remote server - let config = if let (Ok(client_key), Ok(origin)) = ( - settings.get_str("server_client_key"), - settings.get_str("server_origin"), + let config = if let (Some(client_key), Some(origin), Some(encryption_secret)) = ( + settings.server_client_key.as_ref(), + settings.server_origin.as_ref(), + settings.encryption_secret.as_ref(), ) { let client_key = Uuid::parse_str(&client_key)?; - let encryption_secret = settings - .get_str("encryption_secret") - .map_err(|_| anyhow::anyhow!("Could not read `encryption_secret` configuration"))?; log::debug!("Using sync-server with origin {}", origin); log::debug!("Sync client ID: {}", client_key); ServerConfig::Remote { - origin, + origin: origin.clone(), client_key, encryption_secret: encryption_secret.as_bytes().to_vec(), } } else { - let server_dir = settings.get_str("server_dir")?.into(); + let server_dir = settings.server_dir.clone(); log::debug!("Using local sync-server at `{:?}`", server_dir); ServerConfig::Local { server_dir } }; diff --git a/cli/src/invocation/report.rs b/cli/src/invocation/report.rs index 0556d3b9d..36d15574a 100644 --- a/cli/src/invocation/report.rs +++ b/cli/src/invocation/report.rs @@ -1,8 +1,8 @@ use crate::argparse::Filter; use crate::invocation::filtered_tasks; -use crate::report::{Column, Property, Report, SortBy}; +use crate::settings::{Column, Property, Report, Settings, SortBy}; use crate::table; -use config::Config; +use anyhow::anyhow; use prettytable::{Row, Table}; use std::cmp::Ordering; use taskchampion::{Replica, Task, WorkingSet}; @@ -18,8 +18,6 @@ fn sort_tasks(tasks: &mut Vec, report: &Report, working_set: &WorkingSet) let b_uuid = b.get_uuid(); let a_id = working_set.by_uuid(a_uuid); let b_id = working_set.by_uuid(b_uuid); - println!("a_uuid {} -> a_id {:?}", a_uuid, a_id); - println!("b_uuid {} -> b_id {:?}", b_uuid, b_id); match (a_id, b_id) { (Some(a_id), Some(b_id)) => a_id.cmp(&b_id), (Some(_), None) => Ordering::Less, @@ -79,7 +77,7 @@ fn task_column(task: &Task, column: &Column, working_set: &WorkingSet) -> String pub(super) fn display_report( w: &mut W, replica: &mut Replica, - settings: &Config, + settings: &Settings, report_name: String, filter: Filter, ) -> anyhow::Result<()> { @@ -87,8 +85,11 @@ pub(super) fn display_report( let working_set = replica.working_set()?; // Get the report from settings - let mut report = Report::from_config(settings.get(&format!("reports.{}", report_name))?) - .map_err(|e| anyhow::anyhow!("report.{}{}", report_name, e))?; + let mut report = settings + .reports + .get(&report_name) + .ok_or_else(|| anyhow!("report `{}` not defined", report_name))? + .clone(); // include any user-supplied filter conditions report.filter = report.filter.intersect(filter); @@ -122,7 +123,7 @@ pub(super) fn display_report( mod test { use super::*; use crate::invocation::test::*; - use crate::report::Sort; + use crate::settings::Sort; use std::convert::TryInto; use taskchampion::{Status, Uuid}; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e71dcc024..2890e4b9f 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::all)] #![allow(clippy::unnecessary_wraps)] // for Rust 1.50, https://github.com/rust-lang/rust-clippy/pull/6765 +#![allow(clippy::module_inception)] // we use re-exports to shorten stuttering paths like settings::settings::Settings /*! This crate implements the command-line interface to TaskChampion. @@ -38,11 +39,12 @@ mod macros; mod argparse; mod invocation; -mod report; mod settings; mod table; mod usage; +use settings::Settings; + /// The main entry point for the command-line interface. This builds an Invocation /// from the particulars of the operating-system interface, and then executes it. pub fn main() -> anyhow::Result<()> { @@ -59,7 +61,7 @@ pub fn main() -> anyhow::Result<()> { let command = argparse::Command::from_argv(&argv[..])?; // load the application settings - let settings = settings::read_settings()?; + let settings = Settings::read()?; invocation::invoke(command, settings)?; Ok(()) diff --git a/cli/src/report.rs b/cli/src/report.rs deleted file mode 100644 index e2cdddac4..000000000 --- a/cli/src/report.rs +++ /dev/null @@ -1,582 +0,0 @@ -//! This module contains the data structures used to define reports. - -use crate::argparse::{Condition, Filter}; -use anyhow::bail; - -/// A report specifies a filter as well as a sort order and information about which -/// task attributes to display -#[derive(Clone, Debug, PartialEq, Default)] -pub(crate) struct Report { - /// Columns to display in this report - pub columns: Vec, - /// Sort order for this report - pub sort: Vec, - /// Filter selecting tasks for this report - pub filter: Filter, -} - -/// A column to display in a report -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct Column { - /// The label for this column - pub label: String, - - /// The property to display - pub property: Property, -} - -/// Task property to display in a report -#[derive(Clone, Debug, PartialEq)] -#[allow(dead_code)] -pub(crate) enum Property { - /// The task's ID, either working-set index or Uuid if not in the working set - Id, - - /// The task's full UUID - Uuid, - - /// Whether the task is active or not - Active, - - /// The task's description - Description, - - /// The task's tags - Tags, -} - -/// A sorting criterion for a sort operation. -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct Sort { - /// True if the sort should be "ascending" (a -> z, 0 -> 9, etc.) - pub ascending: bool, - - /// The property to sort on - pub sort_by: SortBy, -} - -/// Task property to sort by -#[derive(Clone, Debug, PartialEq)] -#[allow(dead_code)] -pub(crate) enum SortBy { - /// The task's ID, either working-set index or a UUID prefix; working - /// set tasks sort before others. - Id, - - /// The task's full UUID - Uuid, - - /// The task's description - Description, -} - -// Conversions from config::Value. Note that these cannot ergonomically use TryFrom/TryInto; see -// https://github.com/mehcode/config-rs/issues/162 - -impl Report { - /// Create a Report from a config value. This should be the `report.` value. - /// The error message begins with any additional path information, e.g., `.sort[1].sort_by: - /// ..`. - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; - let sort = if let Some(sort_array) = map.remove("sort") { - sort_array - .into_array() - .map_err(|e| anyhow::anyhow!(".sort: {}", e))? - .drain(..) - .enumerate() - .map(|(i, v)| { - Sort::from_config(v).map_err(|e| anyhow::anyhow!(".sort[{}]{}", i, e)) - }) - .collect::>>()? - } else { - vec![] - }; - - let columns = map - .remove("columns") - .ok_or_else(|| anyhow::anyhow!(": 'columns' property is required"))? - .into_array() - .map_err(|e| anyhow::anyhow!(".columns: {}", e))? - .drain(..) - .enumerate() - .map(|(i, v)| { - Column::from_config(v).map_err(|e| anyhow::anyhow!(".columns[{}]{}", i, e)) - }) - .collect::>>()?; - - let conditions = if let Some(conditions) = map.remove("filter") { - conditions - .into_array() - .map_err(|e| anyhow::anyhow!(".filter: {}", e))? - .drain(..) - .enumerate() - .map(|(i, v)| { - v.into_str() - .map_err(|e| e.into()) - .and_then(|s| Condition::parse_str(&s)) - .map_err(|e| anyhow::anyhow!(".filter[{}]: {}", i, e)) - }) - .collect::>>()? - } else { - vec![] - }; - - let filter = Filter { conditions }; - - if !map.is_empty() { - bail!(": unknown properties"); - } - - Ok(Report { - columns, - sort, - filter, - }) - } -} - -impl Column { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; - let label = map - .remove("label") - .ok_or_else(|| anyhow::anyhow!(": 'label' property is required"))? - .into_str() - .map_err(|e| anyhow::anyhow!(".label: {}", e))?; - let property: config::Value = map - .remove("property") - .ok_or_else(|| anyhow::anyhow!(": 'property' property is required"))?; - let property = - Property::from_config(property).map_err(|e| anyhow::anyhow!(".property{}", e))?; - - if !map.is_empty() { - bail!(": unknown properties"); - } - - Ok(Column { label, property }) - } -} - -impl Property { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let s = cfg.into_str().map_err(|e| anyhow::anyhow!(": {}", e))?; - Ok(match s.as_ref() { - "id" => Property::Id, - "uuid" => Property::Uuid, - "active" => Property::Active, - "description" => Property::Description, - "tags" => Property::Tags, - _ => bail!(": unknown property {}", s), - }) - } -} - -impl Sort { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let mut map = cfg.into_table().map_err(|e| anyhow::anyhow!(": {}", e))?; - let ascending = match map.remove("ascending") { - Some(v) => v - .into_bool() - .map_err(|e| anyhow::anyhow!(".ascending: {}", e))?, - None => true, // default - }; - let sort_by: config::Value = map - .remove("sort_by") - .ok_or_else(|| anyhow::anyhow!(": 'sort_by' property is required"))?; - let sort_by = SortBy::from_config(sort_by).map_err(|e| anyhow::anyhow!(".sort_by{}", e))?; - - if !map.is_empty() { - bail!(": unknown properties"); - } - - Ok(Sort { ascending, sort_by }) - } -} - -impl SortBy { - pub(crate) fn from_config(cfg: config::Value) -> anyhow::Result { - let s = cfg.into_str().map_err(|e| anyhow::anyhow!(": {}", e))?; - Ok(match s.as_ref() { - "id" => SortBy::Id, - "uuid" => SortBy::Uuid, - "description" => SortBy::Description, - _ => bail!(": unknown sort_by {}", s), - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use config::{Config, File, FileFormat, FileSourceString}; - use taskchampion::Status; - use textwrap::{dedent, indent}; - - fn config_from(cfg: &str) -> config::Value { - // wrap this in a "table" so that we can get any type of value at the top level. - let yaml = format!("val:\n{}", indent(&dedent(&cfg), " ")); - let mut settings = Config::new(); - let cfg_file: File = File::from_str(&yaml, FileFormat::Yaml); - settings.merge(cfg_file).unwrap(); - settings.cache.into_table().unwrap().remove("val").unwrap() - } - - #[test] - fn test_report_ok() { - let val = config_from( - " - filter: [] - sort: [] - columns: [] - filter: - - status:pending", - ); - let report = Report::from_config(val).unwrap(); - assert_eq!( - report.filter, - Filter { - conditions: vec![Condition::Status(Status::Pending),], - } - ); - assert_eq!(report.columns, vec![]); - assert_eq!(report.sort, vec![]); - } - - #[test] - fn test_report_no_sort() { - let val = config_from( - " - filter: [] - columns: []", - ); - let report = Report::from_config(val).unwrap(); - assert_eq!(report.sort, vec![]); - } - - #[test] - fn test_report_sort_not_array() { - let val = config_from( - " - filter: [] - sort: true - columns: []", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ".sort: invalid type: boolean `true`, expected an array" - ); - } - - #[test] - fn test_report_sort_error() { - let val = config_from( - " - filter: [] - sort: - - sort_by: id - - true - columns: []", - ); - assert!(&Report::from_config(val) - .unwrap_err() - .to_string() - .starts_with(".sort[1]")); - } - - #[test] - fn test_report_unknown_prop() { - let val = config_from( - " - columns: [] - filter: [] - sort: [] - nosuch: true - ", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ": unknown properties" - ); - } - - #[test] - fn test_report_no_columns() { - let val = config_from( - " - filter: [] - sort: []", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ": \'columns\' property is required" - ); - } - - #[test] - fn test_report_columns_not_array() { - let val = config_from( - " - filter: [] - sort: [] - columns: true", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ".columns: invalid type: boolean `true`, expected an array" - ); - } - - #[test] - fn test_report_column_error() { - let val = config_from( - " - filter: [] - sort: [] - columns: - - label: ID - property: id - - true", - ); - assert!(&Report::from_config(val) - .unwrap_err() - .to_string() - .starts_with(".columns[1]:")); - } - - #[test] - fn test_report_filter_not_array() { - let val = config_from( - " - filter: [] - sort: [] - columns: [] - filter: true", - ); - assert_eq!( - &Report::from_config(val).unwrap_err().to_string(), - ".filter: invalid type: boolean `true`, expected an array" - ); - } - - #[test] - fn test_report_filter_error() { - let val = config_from( - " - filter: [] - sort: [] - columns: [] - filter: - - nosuchfilter", - ); - assert!(&Report::from_config(val) - .unwrap_err() - .to_string() - .starts_with(".filter[0]: invalid filter condition:")); - } - - #[test] - fn test_column() { - let val = config_from( - " - label: ID - property: id", - ); - let column = Column::from_config(val).unwrap(); - assert_eq!( - column, - Column { - label: "ID".to_owned(), - property: Property::Id, - } - ); - } - - #[test] - fn test_column_unknown_prop() { - let val = config_from( - " - label: ID - property: id - nosuch: foo", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ": unknown properties" - ); - } - - #[test] - fn test_column_no_label() { - let val = config_from( - " - property: id", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ": 'label' property is required" - ); - } - - #[test] - fn test_column_invalid_label() { - let val = config_from( - " - label: [] - property: id", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ".label: invalid type: sequence, expected a string" - ); - } - - #[test] - fn test_column_no_property() { - let val = config_from( - " - label: ID", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ": 'property' property is required" - ); - } - - #[test] - fn test_column_invalid_property() { - let val = config_from( - " - label: ID - property: []", - ); - assert_eq!( - &Column::from_config(val).unwrap_err().to_string(), - ".property: invalid type: sequence, expected a string" - ); - } - - #[test] - fn test_property() { - let val = config_from("uuid"); - let prop = Property::from_config(val).unwrap(); - assert_eq!(prop, Property::Uuid); - } - - #[test] - fn test_property_invalid_type() { - let val = config_from("{}"); - assert_eq!( - &Property::from_config(val).unwrap_err().to_string(), - ": invalid type: map, expected a string" - ); - } - - #[test] - fn test_sort() { - let val = config_from( - " - ascending: false - sort_by: id", - ); - let sort = Sort::from_config(val).unwrap(); - assert_eq!( - sort, - Sort { - ascending: false, - sort_by: SortBy::Id, - } - ); - } - - #[test] - fn test_sort_no_ascending() { - let val = config_from( - " - sort_by: id", - ); - let sort = Sort::from_config(val).unwrap(); - assert_eq!( - sort, - Sort { - ascending: true, - sort_by: SortBy::Id, - } - ); - } - - #[test] - fn test_sort_unknown_prop() { - let val = config_from( - " - sort_by: id - nosuch: foo", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ": unknown properties" - ); - } - - #[test] - fn test_sort_no_sort_by() { - let val = config_from( - " - ascending: true", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ": 'sort_by' property is required" - ); - } - - #[test] - fn test_sort_invalid_ascending() { - let val = config_from( - " - sort_by: id - ascending: {}", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ".ascending: invalid type: map, expected a boolean" - ); - } - - #[test] - fn test_sort_invalid_sort_by() { - let val = config_from( - " - sort_by: {}", - ); - assert_eq!( - &Sort::from_config(val).unwrap_err().to_string(), - ".sort_by: invalid type: map, expected a string" - ); - } - - #[test] - fn test_sort_by() { - let val = config_from("uuid"); - let prop = SortBy::from_config(val).unwrap(); - assert_eq!(prop, SortBy::Uuid); - } - - #[test] - fn test_sort_by_unknown() { - let val = config_from("nosuch"); - assert_eq!( - &SortBy::from_config(val).unwrap_err().to_string(), - ": unknown sort_by nosuch" - ); - } - - #[test] - fn test_sort_by_invalid_type() { - let val = config_from("{}"); - assert_eq!( - &SortBy::from_config(val).unwrap_err().to_string(), - ": invalid type: map, expected a string" - ); - } -} diff --git a/cli/src/settings.rs b/cli/src/settings.rs deleted file mode 100644 index de8c137f2..000000000 --- a/cli/src/settings.rs +++ /dev/null @@ -1,85 +0,0 @@ -use config::{Config, Environment, File, FileFormat, FileSourceFile, FileSourceString}; -use std::env; -use std::path::PathBuf; - -const DEFAULTS: &str = r#" -reports: - list: - sort: - - sort_by: uuid - columns: - - label: Id - property: id - - label: Description - property: description - - label: Active - property: active - - label: Tags - property: tags - next: - filter: - - "status:pending" - sort: - - sort_by: uuid - columns: - - label: Id - property: id - - label: Description - property: description - - label: Active - property: active - - label: Tags - property: tags -"#; - -/// Get the default settings for this application -pub(crate) fn default_settings() -> anyhow::Result { - let mut settings = Config::default(); - - // set up defaults - if let Some(dir) = dirs_next::data_local_dir() { - let mut tc_dir = dir.clone(); - tc_dir.push("taskchampion"); - settings.set_default( - "data_dir", - // the config crate does not support non-string paths - tc_dir.to_str().expect("data_local_dir is not utf-8"), - )?; - - let mut server_dir = dir; - server_dir.push("taskchampion-sync-server"); - settings.set_default( - "server_dir", - // the config crate does not support non-string paths - server_dir.to_str().expect("data_local_dir is not utf-8"), - )?; - } - - let defaults: File = File::from_str(DEFAULTS, FileFormat::Yaml); - settings.merge(defaults)?; - - Ok(settings) -} - -pub(crate) fn read_settings() -> anyhow::Result { - let mut settings = default_settings()?; - - // load either from the path in TASKCHAMPION_CONFIG, or from CONFIG_DIR/taskchampion - if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { - log::debug!("Loading configuration from {:?}", config_file); - let config_file: PathBuf = config_file.into(); - let config_file: File = config_file.into(); - settings.merge(config_file.required(true))?; - env::remove_var("TASKCHAMPION_CONFIG"); - } else if let Some(mut dir) = dirs_next::config_dir() { - dir.push("taskchampion"); - log::debug!("Loading configuration from {:?} (optional)", dir); - let config_file: File = dir.into(); - settings.merge(config_file.required(false))?; - } - - // merge environment variables - settings.merge(Environment::with_prefix("TASKCHAMPION"))?; - - Ok(settings) -} diff --git a/cli/src/settings/mod.rs b/cli/src/settings/mod.rs new file mode 100644 index 000000000..896de38b8 --- /dev/null +++ b/cli/src/settings/mod.rs @@ -0,0 +1,11 @@ +//! Support for the CLI's configuration file, including default settings. +//! +//! Configuration is stored in a "parsed" format, meaning that any syntax errors will be caught on +//! startup and not just when those values are used. + +mod report; +mod settings; +mod util; + +pub(crate) use report::{Column, Property, Report, Sort, SortBy}; +pub(crate) use settings::Settings; diff --git a/cli/src/settings/report.rs b/cli/src/settings/report.rs new file mode 100644 index 000000000..959494e5d --- /dev/null +++ b/cli/src/settings/report.rs @@ -0,0 +1,535 @@ +//! This module contains the data structures used to define reports. + +use crate::argparse::{Condition, Filter}; +use crate::settings::util::table_with_keys; +use anyhow::{anyhow, bail, Result}; +use std::convert::{TryFrom, TryInto}; + +/// A report specifies a filter as well as a sort order and information about which +/// task attributes to display +#[derive(Clone, Debug, PartialEq, Default)] +pub(crate) struct Report { + /// Columns to display in this report + pub columns: Vec, + /// Sort order for this report + pub sort: Vec, + /// Filter selecting tasks for this report + pub filter: Filter, +} + +/// A column to display in a report +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Column { + /// The label for this column + pub label: String, + + /// The property to display + pub property: Property, +} + +/// Task property to display in a report +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Property { + /// The task's ID, either working-set index or Uuid if not in the working set + Id, + + /// The task's full UUID + Uuid, + + /// Whether the task is active or not + Active, + + /// The task's description + Description, + + /// The task's tags + Tags, +} + +/// A sorting criterion for a sort operation. +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Sort { + /// True if the sort should be "ascending" (a -> z, 0 -> 9, etc.) + pub ascending: bool, + + /// The property to sort on + pub sort_by: SortBy, +} + +/// Task property to sort by +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum SortBy { + /// The task's ID, either working-set index or a UUID prefix; working + /// set tasks sort before others. + Id, + + /// The task's full UUID + Uuid, + + /// The task's description + Description, +} + +// Conversions from settings::Settings. + +impl TryFrom for Report { + type Error = anyhow::Error; + + fn try_from(cfg: toml::Value) -> Result { + Report::try_from(&cfg) + } +} + +impl TryFrom<&toml::Value> for Report { + type Error = anyhow::Error; + + /// Create a Report from a toml value. This should be the `report.` value. + /// The error message begins with any additional path information, e.g., `.sort[1].sort_by: + /// ..`. + fn try_from(cfg: &toml::Value) -> Result { + let keys = ["sort", "columns", "filter"]; + let table = table_with_keys(cfg, &keys).map_err(|e| anyhow!(": {}", e))?; + + let sort = match table.get("sort") { + Some(v) => v + .as_array() + .ok_or_else(|| anyhow!(".sort: not an array"))? + .iter() + .enumerate() + .map(|(i, v)| v.try_into().map_err(|e| anyhow!(".sort[{}]{}", i, e))) + .collect::>>()?, + None => vec![], + }; + + let columns = match table.get("columns") { + Some(v) => v + .as_array() + .ok_or_else(|| anyhow!(".columns: not an array"))? + .iter() + .enumerate() + .map(|(i, v)| v.try_into().map_err(|e| anyhow!(".columns[{}]{}", i, e))) + .collect::>>()?, + None => bail!(": `columns` property is required"), + }; + + let conditions = match table.get("filter") { + Some(v) => v + .as_array() + .ok_or_else(|| anyhow!(".filter: not an array"))? + .iter() + .enumerate() + .map(|(i, v)| { + v.as_str() + .ok_or_else(|| anyhow!(".filter[{}]: not a string", i)) + .and_then(|s| Condition::parse_str(&s)) + .map_err(|e| anyhow!(".filter[{}]: {}", i, e)) + }) + .collect::>>()?, + None => vec![], + }; + + Ok(Report { + columns, + sort, + filter: Filter { conditions }, + }) + } +} + +impl TryFrom<&toml::Value> for Column { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let keys = ["label", "property"]; + let table = table_with_keys(cfg, &keys).map_err(|e| anyhow!(": {}", e))?; + + let label = match table.get("label") { + Some(v) => v + .as_str() + .ok_or_else(|| anyhow!(".label: not a string"))? + .to_owned(), + None => bail!(": `label` property is required"), + }; + + let property = match table.get("property") { + Some(v) => v.try_into().map_err(|e| anyhow!(".property{}", e))?, + None => bail!(": `property` property is required"), + }; + + Ok(Column { label, property }) + } +} + +impl TryFrom<&toml::Value> for Property { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let s = cfg.as_str().ok_or_else(|| anyhow!(": not a string"))?; + Ok(match s { + "id" => Property::Id, + "uuid" => Property::Uuid, + "active" => Property::Active, + "description" => Property::Description, + "tags" => Property::Tags, + _ => bail!(": unknown property {}", s), + }) + } +} + +impl TryFrom<&toml::Value> for Sort { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let keys = ["ascending", "sort_by"]; + let table = table_with_keys(cfg, &keys).map_err(|e| anyhow!(": {}", e))?; + let ascending = match table.get("ascending") { + Some(v) => v + .as_bool() + .ok_or_else(|| anyhow!(".ascending: not a boolean value"))?, + None => true, // default + }; + + let sort_by = match table.get("sort_by") { + Some(v) => v.try_into().map_err(|e| anyhow!(".sort_by{}", e))?, + None => bail!(": `sort_by` property is required"), + }; + + Ok(Sort { ascending, sort_by }) + } +} + +impl TryFrom<&toml::Value> for SortBy { + type Error = anyhow::Error; + + fn try_from(cfg: &toml::Value) -> Result { + let s = cfg.as_str().ok_or_else(|| anyhow!(": not a string"))?; + Ok(match s { + "id" => SortBy::Id, + "uuid" => SortBy::Uuid, + "description" => SortBy::Description, + _ => bail!(": unknown sort_by value `{}`", s), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use taskchampion::Status; + use toml::toml; + + #[test] + fn test_report_ok() { + let val = toml! { + sort = [] + columns = [] + filter = ["status:pending"] + }; + let report: Report = TryInto::try_into(val).unwrap(); + assert_eq!( + report.filter, + Filter { + conditions: vec![Condition::Status(Status::Pending),], + } + ); + assert_eq!(report.columns, vec![]); + assert_eq!(report.sort, vec![]); + } + + #[test] + fn test_report_no_sort() { + let val = toml! { + filter = [] + columns = [] + }; + let report = Report::try_from(val).unwrap(); + assert_eq!(report.sort, vec![]); + } + + #[test] + fn test_report_sort_not_array() { + let val = toml! { + filter = [] + sort = true + columns = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".sort: not an array"); + } + + #[test] + fn test_report_sort_error() { + let val = toml! { + filter = [] + sort = [ { sort_by = "id" }, true ] + columns = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert!(err.starts_with(".sort[1]")); + } + + #[test] + fn test_report_unknown_prop() { + let val = toml! { + columns = [] + filter = [] + sort = [] + nosuch = true + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ": unknown table key `nosuch`"); + } + + #[test] + fn test_report_no_columns() { + let val = toml! { + filter = [] + sort = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ": `columns` property is required"); + } + + #[test] + fn test_report_columns_not_array() { + let val = toml! { + filter = [] + sort = [] + columns = true + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".columns: not an array"); + } + + #[test] + fn test_report_column_error() { + let val = toml! { + filter = [] + sort = [] + + [[columns]] + label = "ID" + property = "id" + + [[columns]] + foo = 10 + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".columns[1]: unknown table key `foo`"); + } + + #[test] + fn test_report_filter_not_array() { + let val = toml! { + filter = "foo" + sort = [] + columns = [] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert_eq!(&err, ".filter: not an array"); + } + + #[test] + fn test_report_filter_error() { + let val = toml! { + sort = [] + columns = [] + filter = [ "nosuchfilter" ] + }; + let err = Report::try_from(val).unwrap_err().to_string(); + assert!(err.starts_with(".filter[0]: invalid filter condition:")); + } + + #[test] + fn test_column() { + let val = toml! { + label = "ID" + property = "id" + }; + let column = Column::try_from(&val).unwrap(); + assert_eq!( + column, + Column { + label: "ID".to_owned(), + property: Property::Id, + } + ); + } + + #[test] + fn test_column_unknown_prop() { + let val = toml! { + label = "ID" + property = "id" + nosuch = "foo" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ": unknown table key `nosuch`" + ); + } + + #[test] + fn test_column_no_label() { + let val = toml! { + property = "id" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ": `label` property is required" + ); + } + + #[test] + fn test_column_invalid_label() { + let val = toml! { + label = [] + property = "id" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ".label: not a string" + ); + } + + #[test] + fn test_column_no_property() { + let val = toml! { + label = "ID" + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ": `property` property is required" + ); + } + + #[test] + fn test_column_invalid_property() { + let val = toml! { + label = "ID" + property = [] + }; + assert_eq!( + &Column::try_from(&val).unwrap_err().to_string(), + ".property: not a string" + ); + } + + #[test] + fn test_property() { + let val = toml::Value::String("uuid".to_owned()); + let prop = Property::try_from(&val).unwrap(); + assert_eq!(prop, Property::Uuid); + } + + #[test] + fn test_property_invalid_type() { + let val = toml::Value::Array(vec![]); + assert_eq!( + &Property::try_from(&val).unwrap_err().to_string(), + ": not a string" + ); + } + + #[test] + fn test_sort() { + let val = toml! { + ascending = false + sort_by = "id" + }; + let sort = Sort::try_from(&val).unwrap(); + assert_eq!( + sort, + Sort { + ascending: false, + sort_by: SortBy::Id, + } + ); + } + + #[test] + fn test_sort_no_ascending() { + let val = toml! { + sort_by = "id" + }; + let sort = Sort::try_from(&val).unwrap(); + assert_eq!( + sort, + Sort { + ascending: true, + sort_by: SortBy::Id, + } + ); + } + + #[test] + fn test_sort_unknown_prop() { + let val = toml! { + sort_by = "id" + nosuch = true + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ": unknown table key `nosuch`" + ); + } + + #[test] + fn test_sort_no_sort_by() { + let val = toml! { + ascending = true + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ": `sort_by` property is required" + ); + } + + #[test] + fn test_sort_invalid_ascending() { + let val = toml! { + sort_by = "id" + ascending = {} + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ".ascending: not a boolean value" + ); + } + + #[test] + fn test_sort_invalid_sort_by() { + let val = toml! { + sort_by = {} + }; + assert_eq!( + &Sort::try_from(&val).unwrap_err().to_string(), + ".sort_by: not a string" + ); + } + + #[test] + fn test_sort_by() { + let val = toml::Value::String("uuid".to_string()); + let prop = SortBy::try_from(&val).unwrap(); + assert_eq!(prop, SortBy::Uuid); + } + + #[test] + fn test_sort_by_unknown() { + let val = toml::Value::String("nosuch".to_string()); + assert_eq!( + &SortBy::try_from(&val).unwrap_err().to_string(), + ": unknown sort_by value `nosuch`" + ); + } + + #[test] + fn test_sort_by_invalid_type() { + let val = toml::Value::Array(vec![]); + assert_eq!( + &SortBy::try_from(&val).unwrap_err().to_string(), + ": not a string" + ); + } +} diff --git a/cli/src/settings/settings.rs b/cli/src/settings/settings.rs new file mode 100644 index 000000000..b17750fcb --- /dev/null +++ b/cli/src/settings/settings.rs @@ -0,0 +1,360 @@ +use super::util::table_with_keys; +use super::{Column, Property, Report, Sort, SortBy}; +use crate::argparse::{Condition, Filter}; +use anyhow::{anyhow, bail, Context, Result}; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::env; +use std::fs; +use std::path::PathBuf; +use taskchampion::Status; +use toml::value::Table; +use toml_edit::Document; + +#[derive(Debug, PartialEq)] +pub(crate) struct Settings { + // filename from which this configuration was loaded, if any + pub(crate) filename: Option, + + // replica + pub(crate) data_dir: PathBuf, + + // remote sync server + pub(crate) server_client_key: Option, + pub(crate) server_origin: Option, + pub(crate) encryption_secret: Option, + + // local sync server + pub(crate) server_dir: PathBuf, + + // reports + pub(crate) reports: HashMap, +} + +impl Settings { + pub(crate) fn read() -> Result { + if let Some(config_file) = env::var_os("TASKCHAMPION_CONFIG") { + log::debug!("Loading configuration from {:?}", config_file); + env::remove_var("TASKCHAMPION_CONFIG"); + Self::load_from_file(config_file.into(), true) + } else if let Some(filename) = Settings::default_filename() { + log::debug!("Loading configuration from {:?} (optional)", filename); + Self::load_from_file(filename, false) + } else { + Ok(Default::default()) + } + } + + /// Get the default filename for the configuration, or None if that cannot + /// be determined. + fn default_filename() -> Option { + dirs_next::config_dir().map(|dir| dir.join("taskchampion.toml")) + } + + /// Update this settings object with the contents of the given TOML file. Top-level settings + /// are overwritten, and reports are overwritten by name. + pub(crate) fn load_from_file(config_file: PathBuf, required: bool) -> Result { + let mut settings = Self::default(); + + let config_toml = match fs::read_to_string(config_file.clone()) { + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return if required { + Err(e.into()) + } else { + settings.filename = Some(config_file); + Ok(settings) + }; + } + Err(e) => return Err(e.into()), + Ok(s) => s, + }; + + let config_toml = config_toml + .parse::() + .with_context(|| format!("error while reading {:?}", config_file))?; + + settings.filename = Some(config_file.clone()); + settings + .update_from_toml(&config_toml) + .with_context(|| format!("error while parsing {:?}", config_file))?; + + Ok(settings) + } + + /// Update this object with configuration from the given config file. This is + /// broken out mostly for convenience in error handling + fn update_from_toml(&mut self, config_toml: &toml::Value) -> Result<()> { + let table_keys = [ + "data_dir", + "server_client_key", + "server_origin", + "encryption_secret", + "server_dir", + "reports", + ]; + let table = table_with_keys(&config_toml, &table_keys)?; + + fn get_str_cfg( + table: &Table, + name: &'static str, + setter: F, + ) -> Result<()> { + if let Some(v) = table.get(name) { + setter( + v.as_str() + .ok_or_else(|| anyhow!(".{}: not a string", name))? + .to_owned(), + ); + } + Ok(()) + } + + get_str_cfg(table, "data_dir", |v| { + self.data_dir = v.into(); + })?; + + get_str_cfg(table, "server_client_key", |v| { + self.server_client_key = Some(v); + })?; + + get_str_cfg(table, "server_origin", |v| { + self.server_origin = Some(v); + })?; + + get_str_cfg(table, "encryption_secret", |v| { + self.encryption_secret = Some(v); + })?; + + get_str_cfg(table, "server_dir", |v| { + self.server_dir = v.into(); + })?; + + if let Some(v) = table.get("reports") { + let report_cfgs = v + .as_table() + .ok_or_else(|| anyhow!(".reports: not a table"))?; + for (name, cfg) in report_cfgs { + let report = Report::try_from(cfg).map_err(|e| anyhow!("reports.{}{}", name, e))?; + self.reports.insert(name.clone(), report); + } + } + + Ok(()) + } + + /// Set a value in the config file, modifying it in place. Returns the filename. + pub(crate) fn set(&self, key: &str, value: &str) -> Result { + let allowed_keys = [ + "data_dir", + "server_client_key", + "server_origin", + "encryption_secret", + "server_dir", + // reports is not allowed, since it is not a string + ]; + if !allowed_keys.contains(&key) { + bail!("No such configuration key {}", key); + } + + let filename = if let Some(ref f) = self.filename { + f.clone() + } else { + Settings::default_filename() + .ok_or_else(|| anyhow!("Could not determine config file name"))? + }; + + let mut document = fs::read_to_string(filename.clone()) + .context("Could not read existing configuration file")? + .parse::() + .context("Could not parse existing configuration file")?; + + document[key] = toml_edit::value(value); + + fs::write(filename.clone(), document.to_string()) + .context("Could not write updated configuration file")?; + + Ok(filename) + } +} + +impl Default for Settings { + fn default() -> Self { + let data_dir; + let server_dir; + + if let Some(dir) = dirs_next::data_local_dir() { + data_dir = dir.join("taskchampion"); + server_dir = dir.join("taskchampion-sync-server"); + } else { + // fallback + data_dir = PathBuf::from("."); + server_dir = PathBuf::from("."); + } + + // define the default reports + let mut reports = HashMap::new(); + + reports.insert( + "list".to_owned(), + Report { + sort: vec![Sort { + ascending: true, + sort_by: SortBy::Uuid, + }], + columns: vec![ + Column { + label: "id".to_owned(), + property: Property::Id, + }, + Column { + label: "description".to_owned(), + property: Property::Description, + }, + Column { + label: "active".to_owned(), + property: Property::Active, + }, + Column { + label: "tags".to_owned(), + property: Property::Tags, + }, + ], + filter: Default::default(), + }, + ); + + reports.insert( + "next".to_owned(), + Report { + sort: vec![ + Sort { + ascending: true, + sort_by: SortBy::Id, + }, + Sort { + ascending: true, + sort_by: SortBy::Uuid, + }, + ], + columns: vec![ + Column { + label: "id".to_owned(), + property: Property::Id, + }, + Column { + label: "description".to_owned(), + property: Property::Description, + }, + Column { + label: "active".to_owned(), + property: Property::Active, + }, + Column { + label: "tags".to_owned(), + property: Property::Tags, + }, + ], + filter: Filter { + conditions: vec![Condition::Status(Status::Pending)], + }, + }, + ); + + Self { + filename: None, + data_dir, + server_client_key: None, + server_origin: None, + encryption_secret: None, + server_dir, + reports, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use tempfile::TempDir; + use toml::toml; + + #[test] + fn test_load_from_file_not_required() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + + let settings = Settings::load_from_file(cfg_file.clone(), false).unwrap(); + + let mut expected = Settings::default(); + expected.filename = Some(cfg_file.clone()); + assert_eq!(settings, expected); + } + + #[test] + fn test_load_from_file_required() { + let cfg_dir = TempDir::new().unwrap(); + + assert!(Settings::load_from_file(cfg_dir.path().join("foo.toml"), true).is_err()); + } + + #[test] + fn test_load_from_file_exists() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + fs::write(cfg_file.clone(), "data_dir = \"/nowhere\"").unwrap(); + + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + assert_eq!(settings.data_dir, PathBuf::from("/nowhere")); + assert_eq!(settings.filename, Some(cfg_file)); + } + + #[test] + fn test_update_from_toml_top_level_keys() { + let val = toml! { + data_dir = "/data" + server_client_key = "sck" + server_origin = "so" + encryption_secret = "es" + server_dir = "/server" + }; + let mut settings = Settings::default(); + settings.update_from_toml(&val).unwrap(); + + assert_eq!(settings.data_dir, PathBuf::from("/data")); + assert_eq!(settings.server_client_key, Some("sck".to_owned())); + assert_eq!(settings.server_origin, Some("so".to_owned())); + assert_eq!(settings.encryption_secret, Some("es".to_owned())); + assert_eq!(settings.server_dir, PathBuf::from("/server")); + } + + #[test] + fn test_update_from_toml_report() { + let val = toml! { + [reports.foo] + sort = [ { sort_by = "id" } ] + columns = [ { label = "ID", property = "id" } ] + }; + let mut settings = Settings::default(); + settings.update_from_toml(&val).unwrap(); + + assert!(settings.reports.get("foo").is_some()); + // beyond existence of this report, we can rely on Report's unit tests + } + + #[test] + fn test_set_valid_key() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_file = cfg_dir.path().join("foo.toml"); + fs::write(cfg_file.clone(), "server_dir = \"/srv\"").unwrap(); + + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + assert_eq!(settings.filename, Some(cfg_file.clone())); + settings.set("data_dir", "/data").unwrap(); + + // load the file again and see the change + let settings = Settings::load_from_file(cfg_file.clone(), true).unwrap(); + assert_eq!(settings.data_dir, PathBuf::from("/data")); + assert_eq!(settings.server_dir, PathBuf::from("/srv")); + assert_eq!(settings.filename, Some(cfg_file)); + } +} diff --git a/cli/src/settings/util.rs b/cli/src/settings/util.rs new file mode 100644 index 000000000..ea585bef2 --- /dev/null +++ b/cli/src/settings/util.rs @@ -0,0 +1,41 @@ +use anyhow::{anyhow, bail, Result}; +use toml::value::Table; + +/// Check that the input is a table and contains no keys not in the given list, returning +/// the table. +pub(super) fn table_with_keys<'a>(cfg: &'a toml::Value, keys: &[&str]) -> Result<&'a Table> { + let table = cfg.as_table().ok_or_else(|| anyhow!("not a table"))?; + + for tk in table.keys() { + if !keys.iter().any(|k| k == tk) { + bail!("unknown table key `{}`", tk); + } + } + Ok(table) +} + +#[cfg(test)] +mod test { + use super::*; + use toml::toml; + + #[test] + fn test_dissect_table_missing() { + let val = toml! { bar = true }; + let diss = table_with_keys(&val, &["foo", "bar"]).unwrap(); + assert_eq!(diss.get("bar"), Some(&toml::Value::Boolean(true))); + assert_eq!(diss.get("foo"), None); + } + + #[test] + fn test_dissect_table_extra() { + let val = toml! { nosuch = 10 }; + assert!(table_with_keys(&val, &["foo", "bar"]).is_err()); + } + + #[test] + fn test_dissect_table_not_a_table() { + let val = toml::Value::Array(vec![]); + assert!(table_with_keys(&val, &["foo", "bar"]).is_err()); + } +} diff --git a/cli/src/usage.rs b/cli/src/usage.rs index 159a7d368..f1bacf674 100644 --- a/cli/src/usage.rs +++ b/cli/src/usage.rs @@ -42,7 +42,7 @@ impl Usage { writeln!(w, "USAGE:\n {} [args]\n", command_name)?; writeln!(w, "TaskChampion subcommands:")?; for subcommand in self.subcommands.iter() { - subcommand.write_help(&mut w, summary)?; + subcommand.write_help(&mut w, command_name, summary)?; } writeln!(w, "Filter Expressions:\n")?; writeln!( @@ -56,7 +56,7 @@ impl Usage { ) )?; for filter in self.filters.iter() { - filter.write_help(&mut w, summary)?; + filter.write_help(&mut w, command_name, summary)?; } writeln!(w, "Modifications:\n")?; writeln!( @@ -70,10 +70,10 @@ impl Usage { ) )?; for modification in self.modifications.iter() { - modification.write_help(&mut w, summary)?; + modification.write_help(&mut w, command_name, summary)?; } if !summary { - writeln!(w, "\nSee `task help` for more detail")?; + writeln!(w, "\nSee `{} help` for more detail", command_name)?; } Ok(()) } @@ -108,13 +108,14 @@ pub(crate) struct Subcommand { } impl Subcommand { - fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + fn write_help(&self, mut w: W, command_name: &str, summary: bool) -> Result<()> { if summary { - writeln!(w, " task {} - {}", self.name, self.summary)?; + writeln!(w, " {} {} - {}", command_name, self.name, self.summary)?; } else { writeln!( w, - " task {}\n{}", + " {} {}\n{}", + command_name, self.syntax, indented(self.description, " ") )?; @@ -138,7 +139,7 @@ pub(crate) struct Filter { } impl Filter { - fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + fn write_help(&self, mut w: W, _: &str, summary: bool) -> Result<()> { if summary { writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { @@ -168,7 +169,7 @@ pub(crate) struct Modification { } impl Modification { - fn write_help(&self, mut w: W, summary: bool) -> Result<()> { + fn write_help(&self, mut w: W, _: &str, summary: bool) -> Result<()> { if summary { writeln!(w, " {} - {}", self.syntax, self.summary)?; } else { diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index e4eb0250b..7eabe2c77 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -1,13 +1,31 @@ use assert_cmd::prelude::*; use predicates::prelude::*; +use std::fs; use std::process::Command; +use tempfile::TempDir; -// NOTE: This tests that the task binary is running and parsing arguments. The details of +// NOTE: This tests that the `ta` binary is running and parsing arguments. The details of // subcommands are handled with unit tests. +/// These tests force config to be read via TASKCHAMPION_CONFIG so that a user's own config file +/// (in their homedir) does not interfere with tests. +fn test_cmd(dir: &TempDir) -> Result> { + let config_filename = dir.path().join("config.toml"); + fs::write( + config_filename.clone(), + format!("data_dir = {:?}", dir.path()), + )?; + + let config_filename = config_filename.to_str().unwrap(); + let mut cmd = Command::cargo_bin("ta")?; + cmd.env("TASKCHAMPION_CONFIG", config_filename); + Ok(cmd) +} + #[test] fn help() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("task")?; + let dir = TempDir::new().unwrap(); + let mut cmd = test_cmd(&dir)?; cmd.arg("--help"); cmd.assert() @@ -19,7 +37,8 @@ fn help() -> Result<(), Box> { #[test] fn version() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("task")?; + let dir = TempDir::new().unwrap(); + let mut cmd = test_cmd(&dir)?; cmd.arg("--version"); cmd.assert() @@ -31,7 +50,8 @@ fn version() -> Result<(), Box> { #[test] fn invalid_option() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("task")?; + let dir = TempDir::new().unwrap(); + let mut cmd = test_cmd(&dir)?; cmd.arg("--no-such-option"); cmd.assert() diff --git a/docs/assets/cgi/LICENSE.md b/docs/assets/cgi/LICENSE.md new file mode 100644 index 000000000..1d4dbe059 --- /dev/null +++ b/docs/assets/cgi/LICENSE.md @@ -0,0 +1,2 @@ +Copyright (C) Andrew Savchenko - All Rights Reserved +All files within this folder are proprietary and reserved for the use by TaskChampion project. diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_1024.png b/docs/assets/cgi/icon_rounded/icon_rounded_1024.png new file mode 100755 index 000000000..d4a4a9e13 Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_1024.png differ diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_128.png b/docs/assets/cgi/icon_rounded/icon_rounded_128.png new file mode 100755 index 000000000..c6f6ccf87 Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_128.png differ diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_16.png b/docs/assets/cgi/icon_rounded/icon_rounded_16.png new file mode 100755 index 000000000..225b311de Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_16.png differ diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_256.png b/docs/assets/cgi/icon_rounded/icon_rounded_256.png new file mode 100755 index 000000000..9717c8d70 Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_256.png differ diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_32.png b/docs/assets/cgi/icon_rounded/icon_rounded_32.png new file mode 100755 index 000000000..2832f0b3a Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_32.png differ diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_512.png b/docs/assets/cgi/icon_rounded/icon_rounded_512.png new file mode 100755 index 000000000..986236490 Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_512.png differ diff --git a/docs/assets/cgi/icon_rounded/icon_rounded_64.png b/docs/assets/cgi/icon_rounded/icon_rounded_64.png new file mode 100755 index 000000000..1c831acce Binary files /dev/null and b/docs/assets/cgi/icon_rounded/icon_rounded_64.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_1024.png b/docs/assets/cgi/icon_square/icon_square_1024.png new file mode 100755 index 000000000..275f3b206 Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_1024.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_128.png b/docs/assets/cgi/icon_square/icon_square_128.png new file mode 100755 index 000000000..2600bae3b Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_128.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_16.png b/docs/assets/cgi/icon_square/icon_square_16.png new file mode 100755 index 000000000..e11979d52 Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_16.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_256.png b/docs/assets/cgi/icon_square/icon_square_256.png new file mode 100755 index 000000000..25cf7f694 Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_256.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_32.png b/docs/assets/cgi/icon_square/icon_square_32.png new file mode 100755 index 000000000..24e9a6097 Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_32.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_512.png b/docs/assets/cgi/icon_square/icon_square_512.png new file mode 100755 index 000000000..da117347a Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_512.png differ diff --git a/docs/assets/cgi/icon_square/icon_square_64.png b/docs/assets/cgi/icon_square/icon_square_64.png new file mode 100755 index 000000000..d9d63e823 Binary files /dev/null and b/docs/assets/cgi/icon_square/icon_square_64.png differ diff --git a/docs/assets/cgi/logo/logo_1024.png b/docs/assets/cgi/logo/logo_1024.png new file mode 100755 index 000000000..8f2cf7724 Binary files /dev/null and b/docs/assets/cgi/logo/logo_1024.png differ diff --git a/docs/assets/cgi/logo/logo_128.png b/docs/assets/cgi/logo/logo_128.png new file mode 100755 index 000000000..c32d2abe4 Binary files /dev/null and b/docs/assets/cgi/logo/logo_128.png differ diff --git a/docs/assets/cgi/logo/logo_16.png b/docs/assets/cgi/logo/logo_16.png new file mode 100755 index 000000000..867dda789 Binary files /dev/null and b/docs/assets/cgi/logo/logo_16.png differ diff --git a/docs/assets/cgi/logo/logo_256.png b/docs/assets/cgi/logo/logo_256.png new file mode 100755 index 000000000..a01735a0d Binary files /dev/null and b/docs/assets/cgi/logo/logo_256.png differ diff --git a/docs/assets/cgi/logo/logo_32.png b/docs/assets/cgi/logo/logo_32.png new file mode 100755 index 000000000..b180de372 Binary files /dev/null and b/docs/assets/cgi/logo/logo_32.png differ diff --git a/docs/assets/cgi/logo/logo_512.png b/docs/assets/cgi/logo/logo_512.png new file mode 100755 index 000000000..2a2fb1502 Binary files /dev/null and b/docs/assets/cgi/logo/logo_512.png differ diff --git a/docs/assets/cgi/logo/logo_64.png b/docs/assets/cgi/logo/logo_64.png new file mode 100755 index 000000000..47e028add Binary files /dev/null and b/docs/assets/cgi/logo/logo_64.png differ diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index d8112b285..f69280b27 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,20 +1,20 @@ # Summary - [Welcome to TaskChampion](./welcome.md) - - [Installation](./installation.md) + * [Installation](./installation.md) * [Using the Task Command](./using-task-command.md) * [Configuration](./config-file.md) * [Reports](./reports.md) * [Tags](./tags.md) + * [Environment](./environment.md) * [Synchronization](./task-sync.md) * [Running the Sync Server](./running-sync-server.md) - * [Debugging](./debugging.md) - [Internal Details](./internals.md) - - [Data Model](./data-model.md) - - [Replica Storage](./storage.md) - - [Task Database](./taskdb.md) - - [Tasks](./tasks.md) - - [Synchronization and the Sync Server](./sync.md) - - [Synchronization Model](./sync-model.md) - - [Server-Replica Protocol](./sync-protocol.md) - - [Planned Functionality](./plans.md) + * [Data Model](./data-model.md) + * [Replica Storage](./storage.md) + * [Task Database](./taskdb.md) + * [Tasks](./tasks.md) + * [Synchronization and the Sync Server](./sync.md) + * [Synchronization Model](./sync-model.md) + * [Server-Replica Protocol](./sync-protocol.md) + * [Planned Functionality](./plans.md) diff --git a/docs/src/config-file.md b/docs/src/config-file.md index 74737b98c..6968e6e51 100644 --- a/docs/src/config-file.md +++ b/docs/src/config-file.md @@ -1,15 +1,19 @@ # Configuration -The `task` command will work out-of-the-box with no configuration file, using default values. +The `ta` command will work out-of-the-box with no configuration file, using default values. -Configuration is read from `taskchampion.yaml` in your config directory. +Configuration is read from `taskchampion.toml` in your config directory. On Linux systems, that directory is `~/.config`. On OS X, it's `~/Library/Preferences`. On Windows, it's `AppData/Roaming` in your home directory. -The path can be overridden by setting `$TASKCHAMPION_CONFIG`. +This can be overridden by setting `TASKCHAMPION_CONFIG` to the configuration filename. -Individual configuration parameters can be overridden by environment variables, converted to upper-case and prefixed with `TASKCHAMPION_`, e.g., `TASKCHAMPION_DATA_DIR`. -Nested configuration parameters such as `reports` cannot be overridden by environment variables. +The file format is [TOML](https://toml.io/). +For example: + +```toml +data_dir = "/home/myuser/.tasks" +``` ## Directories @@ -36,7 +40,15 @@ If using a remote server: * `server_client_key` - Client key to identify this replica to the sync server (a UUID) If not set, then sync is done to a local server. -# Reports +## Reports * `reports` - a mapping of each report's name to its definition. See [Reports](./reports.md) for details. + +## Editing + +As a shortcut, the simple, top-level configuration values can be edited from the command line: + +```shell +ta config set data_dir /home/myuser/.taskchampion +``` diff --git a/docs/src/debugging.md b/docs/src/debugging.md deleted file mode 100644 index f3de1c7a7..000000000 --- a/docs/src/debugging.md +++ /dev/null @@ -1,9 +0,0 @@ -# Debugging - -Both `task` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. -For example: -```shell -$ RUST_LOG=taskchampion=trace task add foo -``` - -The output may provide valuable clues in debugging problems. diff --git a/docs/src/environment.md b/docs/src/environment.md new file mode 100644 index 000000000..14fea8aba --- /dev/null +++ b/docs/src/environment.md @@ -0,0 +1,21 @@ +# Environment Variables + +## Configuration + +Set `TASKCHAMPION_CONFIG` to the location of a configuration file in order to override the default location. + +## Terminal Output + +Taskchampion uses [termcolor](https://github.com/BurntSushi/termcolor) to color its output. +This library interprets [`TERM` and `NO_COLOR`](https://github.com/BurntSushi/termcolor#automatic-color-selection) to determine how it should behave, when writing to a tty. +Set `NO_COLOR` to any value to force plain-text output. + +## Debugging + +Both `ta` and `taskchampion-sync-server` use [env-logger](https://docs.rs/env_logger) and can be configured to log at various levels with the `RUST_LOG` environment variable. +For example: +```shell +$ RUST_LOG=taskchampion=trace ta add foo +``` + +The output may provide valuable clues in debugging problems. diff --git a/docs/src/reports.md b/docs/src/reports.md index 376bf3531..05026da6f 100644 --- a/docs/src/reports.md +++ b/docs/src/reports.md @@ -10,71 +10,70 @@ TaskChampion includes several "built-in" reports, as well as supporting custom r The `next` report is the default, and lists all pending tasks: ```text -$ task +$ ta Id Description Active Tags 1 learn about TaskChampion +next 2 buy wedding gift * +buy +3 plant tomatoes +garden ``` The `Id` column contains short numeric IDs that are assigned to pending tasks. -These IDs are easy to type, such as to mark task 2 done (`task 2 done`). +These IDs are easy to type, such as to mark task 2 done (`ta 2 done`). The `list` report lists all tasks, with a similar set of columns. ## Custom Reports -Custom reports are defined in the configuration file's `reports` property. +Custom reports are defined in the configuration file's `reports` table. This is a mapping from each report's name to its definition. Each definition has the following properties: -* `filter` - criteria for the tasks to include in the report -* `sort` - how to order the tasks +* `filter` - criteria for the tasks to include in the report (optional) +* `sort` - how to order the tasks (optional) * `columns` - the columns of information to display for each task +For example: + +```toml +[reports.garden] +sort = [ + { sort_by = "description" } +] +filter = [ + "status:pending", + "+garden" +] +columns = [ + { label = "ID", property = "id" }, + { label = "Description", property = "description" }, +] +``` + The filter is a list of filter arguments, just like those that can be used on the command line. -See the `task help` output for more details on this syntax. -For example: +See the `ta help` output for more details on this syntax. +It will be merged with any filters provided on the command line, when the report is invoked. -```yaml -reports: - garden: - filter: - - "status:pending" - - "+garden" -``` - -The sort order is defined by an array of objects containing a `sort_by` property and an optional `ascending` property. +The sort order is defined by an array of tables containing a `sort_by` property and an optional `ascending` property. Tasks are compared by the first criterion, and if that is equal by the second, and so on. -For example: - -```yaml -reports: - garden: - sort: - - sort_by: description - - sort_by: uuid - ascending: false -``` If `ascending` is given, it can be `true` for the default sort order, or `false` for the reverse. +In most cases tasks are just sorted by one criterion, but a more advanced example might look like: + +```toml +[reports.garden] +sort = [ + { sort_by = "description" } + { sort_by = "uuid", ascending = false } +] +... +``` + The available values of `sort_by` are (TODO: generate automatically) -Finally, the configuration specifies the list of columns to display in the `columns` property. -Each element has a `label` and a `property`: - -```yaml -reports: - garden: - columns: - - label: Id - property: id - - label: Description - property: description - - label: Tags - property: tags -``` +Finally, the `columns` configuration specifies the list of columns to display. +Each element has a `label` and a `property`, as shown in the example above. The avaliable properties are: diff --git a/docs/src/sync-model.md b/docs/src/sync-model.md index 691312efa..e75bab670 100644 --- a/docs/src/sync-model.md +++ b/docs/src/sync-model.md @@ -125,4 +125,4 @@ Without synchronization, its list of pending operations would grow indefinitely, So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. TaskChampion provides a `LocalServer` for this purpose. -It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `task` binary. +It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally, all within the `ta` binary. diff --git a/docs/src/sync-protocol.md b/docs/src/sync-protocol.md index e96f7cc83..db02d03a2 100644 --- a/docs/src/sync-protocol.md +++ b/docs/src/sync-protocol.md @@ -73,7 +73,7 @@ This value is passed with every request in the `X-Client-Id` header, in its dash ### AddVersion -The request is a `POST` to `/client/add-version/`. +The request is a `POST` to `/v1/client/add-version/`. The request body contains the history segment, optionally encoded using any encoding supported by actix-web. The content-type must be `application/vnd.taskchampion.history-segment`. @@ -87,7 +87,7 @@ Other error responses (4xx or 5xx) may be returned and should be treated appropr ### GetChildVersion -The request is a `GET` to `/client/get-child-version/`. +The request is a `GET` to `/v1/client/get-child-version/`. The response is 404 NOT FOUND if no such version exists. Otherwise, the response is a 200 OK. The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. diff --git a/docs/src/tags.md b/docs/src/tags.md index 4148d5117..7c159c644 100644 --- a/docs/src/tags.md +++ b/docs/src/tags.md @@ -4,7 +4,7 @@ Each task has a collection of associated tags. Tags are short words that categorize tasks, typically written with a leading `+`, such as `+next` or `+jobsearch`. Tags are useful for filtering tasks in reports or on the command line. -For example, when it's time to continue the job search, `task +jobsearch` will show pending tasks with the `jobsearch` tag. +For example, when it's time to continue the job search, `ta +jobsearch` will show pending tasks with the `jobsearch` tag. ## Allowed Tags diff --git a/docs/src/task-sync.md b/docs/src/task-sync.md index 7d874760c..82200ae48 100644 --- a/docs/src/task-sync.md +++ b/docs/src/task-sync.md @@ -4,20 +4,46 @@ A single TaskChampion task database is known as a "replica". A replica "synchronizes" its local information with other replicas via a sync server. Many replicas can thus share the same task history. -This operation is triggered by running `task sync`. +This operation is triggered by running `ta sync`. Typically this runs frequently in a cron task. Synchronization is quick, especially if no changes have occurred. Each replica expects to be synchronized frequently, even if no server is involved. Without periodic syncs, the storage space used for the task database will grow quickly, and performance will suffer. +## Local Sync + By default, TaskChampion syncs to a "local server", as specified by the `server_dir` configuration parameter. +This defaults to `taskchampion-sync-server` in your [data directory](https://docs.rs/dirs-next/2.0.0/dirs_next/fn.data_dir.html), but can be customized in the configuration file. + +## Remote Sync + +For remote synchronization, you will need a few pieces of information. +From the server operator, you will need an origin and a client key. +Configure these with + +```shell +ta config set server_origin "" +ta config set server_client_key "" +``` + +You will need to generate your own encryption secret. +This is used to encrypt your task history, so treat it as a password. +The following will use the `openssl` utility to generate a suitable value: + +```shell +ta config set encryption_secret $(openssl rand -hex 35) +``` + Every replica sharing a task history should have precisely the same configuration for `server_origin`, `server_client_key`, and `encryption_secret`. -Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `task sync`. +### Adding a New Replica + +Synchronizing a new replica to an existing task history is easy: begin with an empty replica, configured for the remote server, and run `ta sync`. The replica will download the entire task history. -It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `task sync`. +### Upgrading a Locally-Sync'd Replica + +It is possible to switch a single replica to a remote server by simply configuring for the remote server and running `ta sync`. The replica will upload the entire task history to the server. Once this is complete, additional replicas can be configured with the same settings in order to share the task history. - diff --git a/docs/src/using-task-command.md b/docs/src/using-task-command.md index dcc57b6f5..d2e7f0ca0 100644 --- a/docs/src/using-task-command.md +++ b/docs/src/using-task-command.md @@ -1,9 +1,9 @@ # Using the Task Command -The main interface to your tasks is the `task` command, which supports various subcommands such as `add`, `modify`, `start`, and `done`. +The main interface to your tasks is the `ta` command, which supports various subcommands such as `add`, `modify`, `start`, and `done`. Customizable [reports](./reports.md) are also available as subcommands, such as `next`. The command reads a [configuration file](./config-file.md) for its settings, including where to find the task database. And the `sync` subcommand [synchronizes tasks with a sync server](./task-sync.md). -You can find a list of all subcommands, as well as the built-in reports, with `task help`. +You can find a list of all subcommands, as well as the built-in reports, with `ta help`. > NOTE: the `task` interface does not precisely match that of TaskWarrior. diff --git a/docs/src/welcome.md b/docs/src/welcome.md index a650c543a..0c21e9f53 100644 --- a/docs/src/welcome.md +++ b/docs/src/welcome.md @@ -1,7 +1,7 @@ # TaskChampion TaskChampion is a personal task-tracking tool. -It works from the command line, with simple commands like `task add "fix the kitchen sink"`. +It works from the command line, with simple commands like `ta add "fix the kitchen sink"`. It can synchronize tasks on multiple devices, and does so in an "offline" mode so you can update your tasks even when you can't reach the server. If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very similar, but with some different design choices and greater reliability. @@ -10,18 +10,18 @@ If you've heard of [TaskWarrior](https://taskwarrior.org/), this tool is very si > NOTE: TaskChampion is still in development and not yet feature-complete. > This section is limited to completed functionality. -Once you've [installed TaskChampion](./installation.md), your interface will be via the `task` command. +Once you've [installed TaskChampion](./installation.md), your interface will be via the `ta` command. Start by adding a task: ```shell -$ task add learn how to use taskchampion +$ ta add learn how to use taskchampion added task ba57deaf-f97b-4e9c-b9ab-04bc1ecb22b8 ``` -You can see all of your pending tasks with `task next`, or just `task` for short: +You can see all of your pending tasks with `ta next`, or just `ta` for short: ```shell -$ task +$ ta Id Description Active Tags 1 learn how to use taskchampion ``` @@ -29,13 +29,13 @@ $ task Tell TaskChampion you're working on the task, using the shorthand id: ```shell -$ task start 1 +$ ta start 1 ``` and when you're done with the task, mark it as complete: ```shell -$ task done 1 +$ ta done 1 ``` ## Synchronizing @@ -44,7 +44,7 @@ Even if you don't have a server, it's a good idea to sync your task database per This acts as a backup and also enables some internal house-cleaning. ```shell -$ task sync +$ ta sync ``` Typically sync is run from a crontab, on whatever schedule fits your needs. @@ -57,7 +57,7 @@ server_client_key: "f8d4d09d-f6c7-4dd2-ab50-634ed20a3ff2" server_origin: "https://taskchampion.example.com" ``` -The next run of `task sync` will upload your task history to that server. -Configuring another device identically and running `task sync` will download that task history, and continue to stay in sync with subsequent runs of the command. +The next run of `ta sync` will upload your task history to that server. +Configuring another device identically and running `ta sync` will download that task history, and continue to stay in sync with subsequent runs of the command. -See [Usage](./usage.md) for more detailed information on using TaskChampion. +See [Usage](./using-task-command.md) for more detailed information on using TaskChampion. diff --git a/sync-server/src/api/add_version.rs b/sync-server/src/api/add_version.rs index 4ca7cbdb1..60db1f385 100644 --- a/sync-server/src/api/add_version.rs +++ b/sync-server/src/api/add_version.rs @@ -19,7 +19,7 @@ const MAX_SIZE: usize = 100 * 1024 * 1024; /// parent version ID in the `X-Parent-Version-Id` header. /// /// Returns other 4xx or 5xx responses on other errors. -#[post("/client/add-version/{parent_version_id}")] +#[post("/v1/client/add-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, server_state: web::Data, @@ -99,7 +99,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( @@ -136,7 +136,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( @@ -163,7 +163,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header("Content-Type", "not/correct") @@ -182,7 +182,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/add-version/{}", parent_version_id); + let uri = format!("/v1/client/add-version/{}", parent_version_id); let req = test::TestRequest::post() .uri(&uri) .header( diff --git a/sync-server/src/api/get_child_version.rs b/sync-server/src/api/get_child_version.rs index 0bf04d96a..a38113803 100644 --- a/sync-server/src/api/get_child_version.rs +++ b/sync-server/src/api/get_child_version.rs @@ -13,7 +13,7 @@ use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; /// /// If no such child exists, returns a 404 with no content. /// Returns other 4xx or 5xx responses on other errors. -#[get("/client/get-child-version/{parent_version_id}")] +#[get("/v1/client/get-child-version/{parent_version_id}")] pub(crate) async fn service( req: HttpRequest, server_state: web::Data, @@ -68,7 +68,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/get-child-version/{}", parent_version_id); + let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) @@ -101,7 +101,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/get-child-version/{}", parent_version_id); + let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) @@ -126,7 +126,7 @@ mod test { let server_state = ServerState::new(server_box); let mut app = test::init_service(App::new().service(app_scope(server_state))).await; - let uri = format!("/client/get-child-version/{}", parent_version_id); + let uri = format!("/v1/client/get-child-version/{}", parent_version_id); let req = test::TestRequest::get() .uri(&uri) .header("X-Client-Key", client_key.to_string()) diff --git a/taskchampion/src/replica.rs b/taskchampion/src/replica.rs index ac1baea88..1e0a47382 100644 --- a/taskchampion/src/replica.rs +++ b/taskchampion/src/replica.rs @@ -4,6 +4,7 @@ use crate::storage::{Operation, Storage, TaskMap}; use crate::task::{Status, Task}; use crate::taskdb::TaskDb; use crate::workingset::WorkingSet; +use anyhow::Context; use chrono::Utc; use log::trace; use std::collections::HashMap; @@ -123,8 +124,10 @@ impl Replica { /// this occurs, but without renumbering, so any newly-pending tasks should appear in /// the working set. pub fn sync(&mut self, server: &mut Box) -> anyhow::Result<()> { - self.taskdb.sync(server)?; + self.taskdb.sync(server).context("Failed to synchronize")?; self.rebuild_working_set(false) + .context("Failed to rebuild working set after sync")?; + Ok(()) } /// Rebuild this replica's working set, based on whether tasks are pending or not. If diff --git a/taskchampion/src/server/remote/mod.rs b/taskchampion/src/server/remote/mod.rs index 8916c7a80..f6fbb4b0a 100644 --- a/taskchampion/src/server/remote/mod.rs +++ b/taskchampion/src/server/remote/mod.rs @@ -49,7 +49,10 @@ impl Server for RemoteServer { parent_version_id: VersionId, history_segment: HistorySegment, ) -> anyhow::Result { - let url = format!("{}/client/add-version/{}", self.origin, parent_version_id); + let url = format!( + "{}/v1/client/add-version/{}", + self.origin, parent_version_id + ); let history_cleartext = HistoryCleartext { parent_version_id, history_segment, @@ -82,7 +85,7 @@ impl Server for RemoteServer { parent_version_id: VersionId, ) -> anyhow::Result { let url = format!( - "{}/client/get-child-version/{}", + "{}/v1/client/get-child-version/{}", self.origin, parent_version_id ); match self diff --git a/taskchampion/src/taskdb.rs b/taskchampion/src/taskdb.rs index f4850f487..ef5fb6c8d 100644 --- a/taskchampion/src/taskdb.rs +++ b/taskchampion/src/taskdb.rs @@ -117,14 +117,14 @@ impl TaskDb { { let mut txn = self.storage.txn()?; - let mut new_ws = vec![]; + let mut new_ws = vec![None]; // index 0 is always None let mut seen = HashSet::new(); // The goal here is for existing working-set items to be "compressed' down to index 1, so // we begin by scanning the current working set and inserting any tasks that should still // be in the set into new_ws, implicitly dropping any tasks that are no longer in the // working set. - for elt in txn.get_working_set()? { + for elt in txn.get_working_set()?.drain(1..) { if let Some(uuid) = elt { if let Some(task) = txn.get_task(uuid)? { if in_working_set(&task) { @@ -144,14 +144,12 @@ impl TaskDb { // if renumbering, clear the working set and re-add if renumber { txn.clear_working_set()?; - for elt in new_ws.drain(0..new_ws.len()) { - if let Some(uuid) = elt { - txn.add_to_working_set(uuid)?; - } + for elt in new_ws.drain(1..new_ws.len()).flatten() { + txn.add_to_working_set(elt)?; } } else { // ..otherwise, just clear the None items determined above from the working set - for (i, elt) in new_ws.iter().enumerate() { + for (i, elt) in new_ws.iter().enumerate().skip(1) { if elt.is_none() { txn.set_working_set_item(i, None)?; } diff --git a/taskchampion/src/workingset.rs b/taskchampion/src/workingset.rs index 5bdc3696b..04aa4dcc5 100644 --- a/taskchampion/src/workingset.rs +++ b/taskchampion/src/workingset.rs @@ -21,6 +21,10 @@ impl WorkingSet { /// Create a new WorkingSet. Typically this is acquired via `replica.working_set()` pub(crate) fn new(by_index: Vec>) -> Self { let mut by_uuid = HashMap::new(); + + // working sets are 1-indexed, so element 0 should always be None + assert!(by_index.is_empty() || by_index[0].is_none()); + for (index, uuid) in by_index.iter().enumerate() { if let Some(uuid) = uuid { by_uuid.insert(*uuid, index); @@ -58,13 +62,7 @@ impl WorkingSet { self.by_index .iter() .enumerate() - .filter_map(|(index, uuid)| { - if let Some(uuid) = uuid { - Some((index, *uuid)) - } else { - None - } - }) + .filter_map(|(index, uuid)| uuid.as_ref().map(|uuid| (index, *uuid))) } }