basic feature set
This commit is contained in:
commit
ef7103043c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
707
Cargo.lock
generated
Normal file
707
Cargo.lock
generated
Normal file
@ -0,0 +1,707 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||||
|
|
||||||
|
[[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.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"clap_derive",
|
||||||
|
"clap_lex",
|
||||||
|
"indexmap",
|
||||||
|
"once_cell",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "3.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"lazy_static",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "confy"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2913470204e9e8498a0f31f17f90a0de801ae92c8c5ac18c49af4819e6786697"
|
||||||
|
dependencies = [
|
||||||
|
"directories",
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "directories"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_users",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "getrandom"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.132"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "malloc_buf"
|
||||||
|
version = "0.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matches"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||||
|
dependencies = [
|
||||||
|
"malloc_buf",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"redox_syscall",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.143"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.143"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
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 = "toml"
|
||||||
|
version = "0.5.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toru"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"colored",
|
||||||
|
"confy",
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
|
"trash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trash"
|
||||||
|
version = "2.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe090367848cd40c4230ff3ce4e2ff6a2fd511c1e14ae047a4a4c37ef7965236"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"objc",
|
||||||
|
"once_cell",
|
||||||
|
"scopeguard",
|
||||||
|
"url",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
||||||
|
|
||||||
|
[[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-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",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "windows"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "toru"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Aaron Manning <contact@aaronmanning.net>"]
|
||||||
|
description = "A command line task manager with time tracking."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { version = "0.4.22", features = ["serde"] }
|
||||||
|
clap = { version = "3.2.17", features = ["derive"] }
|
||||||
|
colored = "2.0.0"
|
||||||
|
confy = "0.4.0"
|
||||||
|
serde = { version = "1.0.143", features = ["derive"] }
|
||||||
|
toml = "0.5.9"
|
||||||
|
trash = "2.1.5"
|
30
README.md
Normal file
30
README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Toru
|
||||||
|
|
||||||
|
A (currently in development) to do app for the command line.
|
||||||
|
|
||||||
|
## Planned Features and Changes:
|
||||||
|
- Viewing individual tasks in full (command: `view`)
|
||||||
|
- Options for editing additional config
|
||||||
|
- `config`
|
||||||
|
- `editor` subcommand for setting default text editor
|
||||||
|
- Editing individual tasks directly (command: `edit`)
|
||||||
|
- Create temporary file for the data
|
||||||
|
- Fork process to open the text editor
|
||||||
|
- Wait for process to return
|
||||||
|
- Open, read and then delete the temporary file
|
||||||
|
- Deserialize as a map so each value can be checked and useful errors reported
|
||||||
|
- Listing tasks in vault (command: `list`)
|
||||||
|
- Options for which field to order by, and how to order (ascending or descending)
|
||||||
|
- Options for which columns to include
|
||||||
|
- If no values given, read a set of defaults from a `list.toml` file, which can be edited from a similar command
|
||||||
|
- Git integration
|
||||||
|
- `toru git` should run the provided Git command directly at the root of the current vault
|
||||||
|
- Ability to view, edit, delete, etc. using name
|
||||||
|
- Have a file containing a serialized `HashMap<String, Vec<Id>>`
|
||||||
|
- Disallow numerical names and have command automatically identify if it is a name or Id
|
||||||
|
- Error on operation if two tasks exist with the same name
|
||||||
|
- Dependency tracker
|
||||||
|
- Store dependencies in a file and correctly update them upon creation and removal of notes
|
||||||
|
- Error if any circular dependencies are introduced
|
||||||
|
- Make sure dependencies written to file are only those that could be successfully created
|
||||||
|
- Automatically added recurring notes
|
31
src/colour.rs
Normal file
31
src/colour.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
// Yellow
|
||||||
|
pub fn vault(text : &str) -> colored::ColoredString {
|
||||||
|
text.truecolor(243, 156, 18).bold()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Red
|
||||||
|
pub fn error(text : &str) -> colored::ColoredString {
|
||||||
|
text.truecolor(192, 57, 43).bold()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purple
|
||||||
|
pub fn command(text : &str) -> colored::ColoredString {
|
||||||
|
text.truecolor(155, 89, 182).bold()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Green
|
||||||
|
pub fn task_name(text : &str) -> colored::ColoredString {
|
||||||
|
text.truecolor(39, 174, 96).bold()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beige
|
||||||
|
pub fn file(text : &str) -> colored::ColoredString {
|
||||||
|
text.truecolor(255, 184, 184).bold()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pink
|
||||||
|
pub fn id(text : &str) -> colored::ColoredString {
|
||||||
|
text.truecolor(232, 67, 147).bold()
|
||||||
|
}
|
103
src/config.rs
Normal file
103
src/config.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use crate::error;
|
||||||
|
|
||||||
|
use crate::colour;
|
||||||
|
|
||||||
|
use std::path;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Paths for all vaults, ordered according to recent usage, with current at the front.
|
||||||
|
pub vaults : Vec<(String, path::PathBuf)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn current_vault(&self) -> Result<&(String, path::PathBuf), error::Error> {
|
||||||
|
self.vaults.get(0).ok_or_else(|| error::Error::Generic(String::from("The attempted operation requires a vault, none of which have been set up")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(self) -> Result<(), error::Error> {
|
||||||
|
Ok(confy::store::<Config>("toru", self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load() -> Result<Config, error::Error> {
|
||||||
|
Ok(confy::load::<Config>("toru")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_name(&self, name : &String) -> bool {
|
||||||
|
self.vaults.iter().any(|(n, _)| n == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_path(&self, path : &path::PathBuf) -> bool {
|
||||||
|
self.vaults.iter().any(|(_, p)| p == path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the vault to the configuration.
|
||||||
|
pub fn add(&mut self, name : String, path : path::PathBuf) {
|
||||||
|
debug_assert!(!self.contains_name(&name));
|
||||||
|
debug_assert!(!self.contains_path(&path));
|
||||||
|
|
||||||
|
self.vaults.push((name, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, name : &String) -> Result<path::PathBuf, error::Error> {
|
||||||
|
match self.vaults.iter().position(|(n, _)| n == name) {
|
||||||
|
Some(index) => {
|
||||||
|
let (_, path) = self.vaults.swap_remove(index);
|
||||||
|
Ok(path)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(error::Error::Generic(format!("No vault by the name {} exists", colour::vault(name))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switch(&mut self, name : &String) -> Result<(), error::Error> {
|
||||||
|
match self.vaults.iter().position(|(n, _)| n == name) {
|
||||||
|
Some(index) => {
|
||||||
|
self.vaults.swap(index, 0);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(error::Error::Generic(format!("No vault by the name {} exists", colour::vault(name))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all vaults to stdout.
|
||||||
|
pub fn list_vaults(&self) {
|
||||||
|
|
||||||
|
let width = self.vaults.iter().fold(usize::MIN, |c, (n, _)| c.max(n.len()));
|
||||||
|
|
||||||
|
if self.vaults.is_empty() {
|
||||||
|
println!("No vaults currently set up, try running: {}", colour::command("toru vault new <NAME> <PATH>"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (i, (name, path)) in self.vaults.iter().enumerate() {
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
print!("* ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print!(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("{}", colour::vault(name));
|
||||||
|
|
||||||
|
let padding = width - name.len() + 1;
|
||||||
|
|
||||||
|
for _ in 0..padding {
|
||||||
|
print!(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("{}", path.display());
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
57
src/error.rs
Normal file
57
src/error.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use crate::colour;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Io(io::Error),
|
||||||
|
Confy(confy::ConfyError),
|
||||||
|
Trash(trash::Error),
|
||||||
|
TomlDe(toml::de::Error),
|
||||||
|
TomlSer(toml::ser::Error),
|
||||||
|
Generic(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Io(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||||
|
Error::Confy(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||||
|
Error::Trash(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||||
|
Error::TomlDe(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||||
|
Error::TomlSer(err) => write!(f, "{} {}", colour::error("Internal Error:"), err),
|
||||||
|
Error::Generic(message) => write!(f, "{}", message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(err : io::Error) -> Self {
|
||||||
|
Error::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<confy::ConfyError> for Error {
|
||||||
|
fn from(err : confy::ConfyError) -> Self {
|
||||||
|
Error::Confy(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<trash::Error> for Error {
|
||||||
|
fn from(err : trash::Error) -> Self {
|
||||||
|
Error::Trash(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<toml::de::Error> for Error {
|
||||||
|
fn from(err : toml::de::Error) -> Self {
|
||||||
|
Error::TomlDe(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<toml::ser::Error> for Error {
|
||||||
|
fn from(err : toml::ser::Error) -> Self {
|
||||||
|
Error::TomlSer(err)
|
||||||
|
}
|
||||||
|
}
|
164
src/main.rs
Normal file
164
src/main.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
|
mod vault;
|
||||||
|
mod error;
|
||||||
|
mod tasks;
|
||||||
|
mod state;
|
||||||
|
mod config;
|
||||||
|
mod colour;
|
||||||
|
|
||||||
|
use std::path;
|
||||||
|
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command : Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Subcommand, Debug)]
|
||||||
|
#[clap(version, about, author, global_setting = clap::AppSettings::DisableHelpSubcommand)]
|
||||||
|
enum Command {
|
||||||
|
/// Create a new task.
|
||||||
|
New {
|
||||||
|
#[clap(short, long)]
|
||||||
|
name : String,
|
||||||
|
#[clap(short, long)]
|
||||||
|
info : Option<String>,
|
||||||
|
#[clap(short, long)]
|
||||||
|
tags : Vec<String>,
|
||||||
|
#[clap(short, long)]
|
||||||
|
dependencies : Vec<tasks::Id>,
|
||||||
|
#[clap(short, long, value_enum)]
|
||||||
|
priority : Option<tasks::Priority>,
|
||||||
|
},
|
||||||
|
/// Delete a task completely.
|
||||||
|
Delete {
|
||||||
|
id : tasks::Id,
|
||||||
|
},
|
||||||
|
/// Discard a task without deleting the underlying file.
|
||||||
|
Discard {
|
||||||
|
id : tasks::Id,
|
||||||
|
},
|
||||||
|
/// Mark a task as complete.
|
||||||
|
Complete {
|
||||||
|
id : tasks::Id,
|
||||||
|
},
|
||||||
|
/// Commands for interacting with vaults.
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Vault(VaultCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Subcommand, Debug)]
|
||||||
|
enum VaultCommand {
|
||||||
|
/// Creates a new vault at the specified location of the given name.
|
||||||
|
New {
|
||||||
|
name : String,
|
||||||
|
path : path::PathBuf,
|
||||||
|
},
|
||||||
|
/// Disconnects the specified vault from toru, without altering the files.
|
||||||
|
Disconnect {
|
||||||
|
name : String,
|
||||||
|
},
|
||||||
|
/// Connects an existing fault to toru.
|
||||||
|
Connect {
|
||||||
|
name : String,
|
||||||
|
path : path::PathBuf,
|
||||||
|
},
|
||||||
|
/// Deletes the specified vault along with all of its data.
|
||||||
|
Delete {
|
||||||
|
name : String,
|
||||||
|
},
|
||||||
|
/// Lists all configured vaults.
|
||||||
|
List,
|
||||||
|
/// Switches to the specified vault.
|
||||||
|
Switch {
|
||||||
|
name : String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let result = program();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(error::Error::Generic(message)) => {
|
||||||
|
println!("{} {}", colour::error("Error:"), message);
|
||||||
|
}
|
||||||
|
result => println!("{:?}", result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn program() -> Result<(), error::Error> {
|
||||||
|
let command = {
|
||||||
|
use clap::Parser;
|
||||||
|
Args::parse().command
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut config = config::Config::load()?;
|
||||||
|
|
||||||
|
use Command::*;
|
||||||
|
match command {
|
||||||
|
Vault(command) => {
|
||||||
|
use VaultCommand::*;
|
||||||
|
match command {
|
||||||
|
New { name, path } => {
|
||||||
|
vault::new(name.clone(), path, &mut config)?;
|
||||||
|
println!("Created vault {}", colour::vault(&name));
|
||||||
|
},
|
||||||
|
Disconnect { name } => {
|
||||||
|
vault::disconnect(&name, &mut config)?;
|
||||||
|
println!("Disconnected vault {}", colour::vault(&name));
|
||||||
|
},
|
||||||
|
Connect { name , path } => {
|
||||||
|
vault::connect(name.clone(), path, &mut config)?;
|
||||||
|
println!("Connected vault {}", colour::vault(&name));
|
||||||
|
},
|
||||||
|
Delete { name } => {
|
||||||
|
vault::delete(&name, &mut config)?;
|
||||||
|
println!("Deleted vault {}", colour::vault(&name));
|
||||||
|
},
|
||||||
|
List => {
|
||||||
|
config.list_vaults();
|
||||||
|
},
|
||||||
|
Switch { name } => {
|
||||||
|
config.switch(&name)?;
|
||||||
|
println!("Switched to vault {}", colour::vault(&name));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command => {
|
||||||
|
let vault_folder = &config.current_vault()?.1;
|
||||||
|
let mut state = state::State::load(vault_folder)?;
|
||||||
|
|
||||||
|
match command {
|
||||||
|
New { name, info, tags, dependencies, priority } => {
|
||||||
|
let task = tasks::Task::new(name, info, tags, dependencies, priority, vault_folder, &mut state)?;
|
||||||
|
println!("Created task {}", colour::task_name(&task.data.name));
|
||||||
|
},
|
||||||
|
Delete { id } => {
|
||||||
|
tasks::Task::delete_by_id(id, vault_folder)?;
|
||||||
|
println!("Deleted task {}", colour::id(&id.to_string()));
|
||||||
|
}
|
||||||
|
Discard { id } => {
|
||||||
|
let mut task = tasks::Task::load(id, vault_folder.clone(), false)?;
|
||||||
|
task.data.discarded = true;
|
||||||
|
task.save()?;
|
||||||
|
println!("Discarded task {}", colour::id(&id.to_string()));
|
||||||
|
},
|
||||||
|
Complete { id } => {
|
||||||
|
let mut task = tasks::Task::load(id, vault_folder.clone(), false)?;
|
||||||
|
task.data.complete = true;
|
||||||
|
task.save()?;
|
||||||
|
println!("Marked task {} as complete", colour::id(&id.to_string()));
|
||||||
|
},
|
||||||
|
Vault(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
state.save()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.save()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
86
src/state.rs
Normal file
86
src/state.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Write, Seek};
|
||||||
|
|
||||||
|
use crate::error;
|
||||||
|
use crate::tasks::Id;
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
file : fs::File,
|
||||||
|
pub data : InternalState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct InternalState {
|
||||||
|
pub next_id : Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// This function should be called after creating or checking that the "notes" folder exists.
|
||||||
|
pub fn load(vault_location : &path::Path) -> Result<Self, error::Error> {
|
||||||
|
let path = vault_location.join("state.toml");
|
||||||
|
|
||||||
|
if path.exists() && path.is_file() {
|
||||||
|
// Read file before opening (and truncating).
|
||||||
|
let contents = fs::read_to_string(&path)?;
|
||||||
|
|
||||||
|
let file = fs::File::options()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)?;
|
||||||
|
|
||||||
|
let data = toml::from_str::<InternalState>(&contents)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
file,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
let mut max_id : i128 = -1;
|
||||||
|
|
||||||
|
for id in vault_location.join("notes").read_dir()?.filter_map(|p| p.ok()).map(|p| p.path()).filter(|p| p.extension().map(|s| s.to_str()) == Some(Some("toml"))).filter_map(|p| p.file_stem().map(|x| x.to_str().map(|y| y.to_string()))).flatten().filter_map(|p| p.parse::<Id>().ok()) {
|
||||||
|
|
||||||
|
if i128::try_from(id).unwrap() > max_id {
|
||||||
|
max_id = i128::from(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = InternalState {
|
||||||
|
next_id : u64::try_from(max_id + 1).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = fs::File::options()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)?;
|
||||||
|
|
||||||
|
file.set_len(0)?;
|
||||||
|
file.seek(io::SeekFrom::Start(0))?;
|
||||||
|
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||||
|
|
||||||
|
let task = Self {
|
||||||
|
file,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(self) -> Result<(), error::Error> {
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
mut file,
|
||||||
|
data,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
file.set_len(0)?;
|
||||||
|
file.seek(io::SeekFrom::Start(0))?;
|
||||||
|
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
157
src/tasks.rs
Normal file
157
src/tasks.rs
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
use crate::error;
|
||||||
|
use crate::state;
|
||||||
|
use crate::colour;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::mem;
|
||||||
|
use std::path;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Write, Seek};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub type Id = u64;
|
||||||
|
|
||||||
|
pub struct Task {
|
||||||
|
path : path::PathBuf,
|
||||||
|
file : fs::File,
|
||||||
|
pub data : InternalTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum Priority {
|
||||||
|
#[default]
|
||||||
|
Unspecified,
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct TimeEntry {
|
||||||
|
hours : u32,
|
||||||
|
minutes : u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct InternalTask {
|
||||||
|
pub id : Id,
|
||||||
|
pub name : String,
|
||||||
|
pub info : Option<String>,
|
||||||
|
pub tags : HashSet<String>,
|
||||||
|
pub dependencies : HashSet<Id>,
|
||||||
|
pub priority : Priority,
|
||||||
|
//due : Option<chrono::NaiveDateTime>,
|
||||||
|
pub time_entries : Vec<TimeEntry>,
|
||||||
|
pub created : chrono::NaiveDateTime,
|
||||||
|
pub complete : bool,
|
||||||
|
pub discarded : bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
pub fn new(name : String, info : Option<String>, tags : Vec<String>, dependencies : Vec<Id>, priority : Option<Priority>, vault_folder : &path::Path, state : &mut state::State) -> Result<Self, error::Error> {
|
||||||
|
|
||||||
|
let id = state.data.next_id;
|
||||||
|
state.data.next_id += 1;
|
||||||
|
|
||||||
|
let path = vault_folder.join("notes").join(&format!("{}.toml", id));
|
||||||
|
|
||||||
|
let mut file = fs::File::options()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)?;
|
||||||
|
|
||||||
|
let data = InternalTask {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
info,
|
||||||
|
tags : tags.into_iter().collect(),
|
||||||
|
dependencies : dependencies.into_iter().collect(),
|
||||||
|
priority : priority.unwrap_or_default(),
|
||||||
|
time_entries : Vec::new(),
|
||||||
|
created : chrono::Utc::now().naive_local(),
|
||||||
|
complete : false,
|
||||||
|
discarded : false,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
file.set_len(0)?;
|
||||||
|
file.seek(io::SeekFrom::Start(0))?;
|
||||||
|
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(Task {
|
||||||
|
path,
|
||||||
|
file,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The read_only flag is so that the file will not be truncated, and therefore doesn't need to
|
||||||
|
/// be saved when finished.
|
||||||
|
pub fn load(id : Id, vault_folder : path::PathBuf, read_only : bool) -> Result<Self, error::Error> {
|
||||||
|
let path = Task::check_exists(id, &vault_folder)?;
|
||||||
|
|
||||||
|
let file_contents = fs::read_to_string(&path)?;
|
||||||
|
let file = if read_only {
|
||||||
|
fs::File::open(&path)?
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fs::File::options()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = toml::from_str(&file_contents)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
path,
|
||||||
|
file,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_exists(id : Id, vault_folder : &path::Path) -> Result<path::PathBuf, error::Error> {
|
||||||
|
let path = vault_folder.join("notes").join(format!("{}.toml", id));
|
||||||
|
if path.exists() && path.is_file() {
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(error::Error::Generic(format!("No task with the ID {} exists", colour::id(&id.to_string()))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(self) -> Result<(), error::Error> {
|
||||||
|
let Self {
|
||||||
|
path,
|
||||||
|
mut file,
|
||||||
|
data,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
file.set_len(0)?;
|
||||||
|
file.seek(io::SeekFrom::Start(0))?;
|
||||||
|
file.write_all(toml::to_string(&data)?.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(self) -> Result<(), error::Error> {
|
||||||
|
let Self {
|
||||||
|
path,
|
||||||
|
file,
|
||||||
|
data,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
mem::drop(file);
|
||||||
|
fs::remove_file(&path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_by_id(id : Id, vault_folder : &path::Path) -> Result<(), error::Error> {
|
||||||
|
let path = Task::check_exists(id, vault_folder)?;
|
||||||
|
fs::remove_file(&path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
105
src/vault.rs
Normal file
105
src/vault.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use crate::error;
|
||||||
|
use crate::state;
|
||||||
|
use crate::colour;
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path;
|
||||||
|
|
||||||
|
pub fn new(name : String, path : path::PathBuf, config : &mut config::Config) -> Result<(), error::Error> {
|
||||||
|
|
||||||
|
fn create_all_metadata(path : &path::Path) -> Result<(), error::Error> {
|
||||||
|
fs::create_dir(path.join("notes"))?;
|
||||||
|
let state = state::State::load(path)?;
|
||||||
|
//state.save()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration already contains a vault by the given name.
|
||||||
|
if config.contains_name(&name) {
|
||||||
|
Err(error::Error::Generic(format!("A vault named \"{}\" already exists", name)))
|
||||||
|
}
|
||||||
|
else if config.contains_path(&path) {
|
||||||
|
Err(error::Error::Generic(format!("A vault at the path {:?} already exists", path)))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Folder exists and contains data.
|
||||||
|
if path.exists() && path.is_dir() && path.read_dir()?.next().is_some() {
|
||||||
|
Err(error::Error::Generic(String::from("The specified folder already exists and contains other data, please provide a path to a new or empty folder")))
|
||||||
|
}
|
||||||
|
// Folder exists and is empty, so set up the vault metadata.
|
||||||
|
else if path.exists() && path.is_dir() {
|
||||||
|
|
||||||
|
// Create the vault metadata.
|
||||||
|
create_all_metadata(&path)?;
|
||||||
|
|
||||||
|
config.add(name, path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
// Provided path is to a file, not a directory.
|
||||||
|
else if path.exists() {
|
||||||
|
Err(error::Error::Generic(String::from("The specified path already points to a file, please provide a path to a new or empty folder")))
|
||||||
|
}
|
||||||
|
// Path does not yet exist, and should be created.
|
||||||
|
else {
|
||||||
|
fs::create_dir_all(&path)?;
|
||||||
|
|
||||||
|
// Create the vault metadata.
|
||||||
|
create_all_metadata(&path)?;
|
||||||
|
|
||||||
|
config.add(name, path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(name : String, path : path::PathBuf, config : &mut config::Config) -> Result<(), error::Error> {
|
||||||
|
// Configuration already contains a vault by the given name.
|
||||||
|
if config.contains_name(&name) {
|
||||||
|
Err(error::Error::Generic(format!("A vault named \"{}\" already exists", name)))
|
||||||
|
}
|
||||||
|
else if config.contains_path(&path) {
|
||||||
|
Err(error::Error::Generic(format!("A vault at the path {:?} is already set up", path)))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Folder exists and contains data.
|
||||||
|
if path.exists() && path.is_dir() {
|
||||||
|
// Vault is missing required metadata files.
|
||||||
|
if !path.join("notes").exists() {
|
||||||
|
Err(error::Error::Generic(format!("Cannot connect the vault as it is missing the {} folder", colour::file("notes"))))
|
||||||
|
}
|
||||||
|
else if !path.join("state.toml").exists() {
|
||||||
|
Err(error::Error::Generic(format!("Cannot connect the vault as it is missing the {} file", colour::file("state.toml"))))
|
||||||
|
}
|
||||||
|
// Required metadata exists, so the vault is connected.
|
||||||
|
else {
|
||||||
|
config.add(name, path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Provided path is to a file, not a directory.
|
||||||
|
else if path.exists() {
|
||||||
|
Err(error::Error::Generic(String::from("The specified path points to a file, not a folder")))
|
||||||
|
}
|
||||||
|
// Path does not yet exist.
|
||||||
|
else {
|
||||||
|
Err(error::Error::Generic(format!("The path {:?} does not exist", path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnect(name : &String, config : &mut config::Config) -> Result<(), error::Error> {
|
||||||
|
config.remove(name)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(name : &String, config : &mut config::Config) -> Result<(), error::Error> {
|
||||||
|
let path = config.remove(name)?;
|
||||||
|
trash::delete(path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user