diff --git a/CHANGELOG.md b/CHANGELOG.md index a494d90..b969feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,48 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +### Added +- Marks to show which span a reminder belongs to + +### Changed +- Birthdays for current day are now highlighted +- Default value for `--range` argument + +### Fixed +- `--date` accepting incomplete expressions + +## 0.2.0 - 2022-03-18 + +### Added +- `LOG` command and `today log` CLI command +- `CAPTURE` command and `today new` CLI command +- `REMIND` statement +- `CANCEL` statement for tasks +- One-letter aliases for `show`, `log`, `done` and `cancel` CLI commands +- `MOVE` can now move entries to a different time +- `--date` now accepts expressions like `today-3d` +- In `--range` and `--date`, `t` can be used as abbreviation for `today` +- `*` markers in output for days with logs and entries with descriptions + +### Changed +- Output is now colored +- Better error messages +- Overhauled `today show` format + - It can now show log entries for days + - It now displays the source command (file and line) of the entry +- When saving... + - Unchanged files are no longer overwritten + - Imports are now sorted alphabetically + - Done and cancel dates are now simplified where possible +- Always prints import-based path, not absolute path + +### Fixed +- Alignment in output +- Respect `TZDIR` environment variable +- Negative weekday deltas + ## 0.1.0 - 2021-12-20 ### Added diff --git a/Cargo.lock b/Cargo.lock index a4a4cfc..4836ec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,12 +3,12 @@ version = 3 [[package]] -name = "ansi_term" -version = "0.12.1" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "winapi", + "libc", ] [[package]] @@ -17,16 +17,16 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" @@ -36,30 +36,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", "generic-array", ] [[package]] -name = "block-padding" -version = "0.1.5" +name = "bumpalo" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -67,6 +55,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" @@ -75,30 +69,86 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time", + "wasm-bindgen", "winapi", ] [[package]] name = "clap" -version = "2.34.0" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ - "ansi_term", - "atty", "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", "strsim", - "textwrap", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", "unicode-width", - "vec_map", +] + +[[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]] @@ -108,12 +158,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9eaa21df489b1e50af464db43c1fc69f60492060ac7baae3996fe8a5f476790" [[package]] -name = "digest" -version = "0.8.1" +name = "core-foundation-sys" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", ] [[package]] @@ -126,10 +246,20 @@ dependencies = [ ] [[package]] -name = "dirs-sys" -version = "0.3.6" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -137,39 +267,115 @@ dependencies = [ ] [[package]] -name = "fake-simd" +name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "edit" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c562aa71f7bc691fde4c6bf5f93ae5a5298b617c2eb44c76c87832299a17fbb4" +dependencies = [ + "tempfile", + "which", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fd-lock" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ef1a30ae415c3a691a4f41afddc2dbcd6d70baf338368d85ebc1e8ed92cedb9" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys", +] [[package]] name = "generic-array" -version = "0.12.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", + "version_check", ] [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "heck" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -180,6 +386,76 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -188,21 +464,76 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] -name = "maplit" -version = "1.0.2" +name = "link-cplusplus" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -210,33 +541,40 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] -name = "opaque-debug" -version = "0.2.3" +name = "once_cell" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "pest" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" dependencies = [ "pest", "pest_generator", @@ -244,9 +582,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" dependencies = [ "pest", "pest_meta", @@ -257,13 +595,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1", + "sha2", ] [[package]] @@ -292,117 +630,197 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.34" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "promptly" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9acbc6c5a5b029fe58342f58445acb00ccfe24624e538894bc2f04ce112980ba" +dependencies = [ + "rustyline", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] [[package]] -name = "redox_syscall" -version = "0.2.10" +name = "radix_trie" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[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.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", + "thiserror", ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "remove_dir_all" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", + "winapi", ] +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustyline" +version = "9.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "smallvec", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.82" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "tempfile" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "unicode-width", + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -411,40 +829,44 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "today" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", + "clap", + "codespan-reporting", + "colored", "computus", "directories", + "edit", "pest", "pest_derive", - "structopt", + "promptly", + "termcolor", "thiserror", "tzfile", ] [[package]] name = "typenum" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "tzfile" version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59c22c42a2537e4c7ad21a4007273bbc5bebed7f36bc93730a5780e22a4592e" +source = "git+https://github.com/Garmelon/tzfile.git?branch=tzdir#a2a4775fdb2a24f33f616ca6289d715f647d53c2" dependencies = [ "byteorder", "chrono", @@ -452,39 +874,39 @@ dependencies = [ [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "utf8parse" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" @@ -492,6 +914,77 @@ 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.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -508,8 +1001,83 @@ 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-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index edc3e45..0ae8a9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,35 @@ [package] name = "today" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] -chrono = "0.4.19" +chrono = "0.4.23" +clap = { version = "4.1.4", features = ["derive"] } +codespan-reporting = "0.11.1" +colored = "2.0.0" computus = "1.0.0" directories = "4.0.1" -pest = "2.1.3" -pest_derive = "2.1.0" -structopt = "0.3.25" -thiserror = "1.0.30" -tzfile = "0.1.3" +edit = "0.1.4" +pest = "2.5.5" +pest_derive = "2.5.5" +promptly = "0.3.1" +termcolor = "1.2.0" +thiserror = "1.0.38" +tzfile = { git = "https://github.com/Garmelon/tzfile.git", branch = "tzdir" } + +[lints] +rust.unsafe_code = { level = "forbid", priority = 1 } +rust.future_incompatible = "warn" +rust.rust_2018_idioms = "warn" +rust.noop_method_call = "warn" +rust.single_use_lifetimes = "warn" +rust.trivial_numeric_casts = "warn" +rust.unused = "warn" +rust.unused_crate_dependencies = "warn" +rust.unused_extern_crates = "warn" +rust.unused_import_braces = "warn" +rust.unused_lifetimes = "warn" +rust.unused_qualifications = "warn" +clippy.all = "warn" +clippy.use_self = "warn" diff --git a/src/cli.rs b/src/cli.rs index 25d064d..1189420 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,56 +3,100 @@ use std::str::FromStr; use std::{process, result}; use chrono::{NaiveDate, NaiveDateTime}; +use clap::Parser; +use codespan_reporting::files::SimpleFile; use directories::ProjectDirs; -use structopt::StructOpt; -use crate::eval::{DateRange, Entry, EntryMode, SourceInfo}; -use crate::files::arguments::Range; -use crate::files::{self, Files}; +use crate::eval::{self, DateRange, Entry, EntryMode}; +use crate::files::cli::{CliDate, CliIdent, CliRange}; +use crate::files::{self, Files, ParseError}; -use self::error::Result; +use self::error::{Error, Result}; use self::layout::line::LineLayout; +mod cancel; mod done; mod error; mod layout; +mod log; +mod new; mod print; mod show; +mod util; -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Parser)] pub struct Opt { /// File to load - #[structopt(short, long, parse(from_os_str))] + #[clap(short, long)] file: Option, /// Overwrite the current date - #[structopt(short, long)] - date: Option, - /// The range days to focus on - #[structopt(short, long, default_value = "today-2d--today+13d")] + #[clap(short, long, default_value = "t")] + date: String, + /// Range of days to focus on + #[clap(short, long, default_value = "t-2d--t+2w")] range: String, - #[structopt(subcommand)] + #[clap(subcommand)] command: Option, } -#[derive(Debug, StructOpt)] +#[derive(Debug, clap::Subcommand)] pub enum Command { - #[allow(rustdoc::broken_intra_doc_links)] /// Shows individual entries in detail + #[clap(alias = "s")] Show { - /// Entries to show - #[structopt(required = true)] - entries: Vec, + /// Entries and days to show + #[clap(required = true)] + identifiers: Vec, + }, + /// Create a new entry based on a template + #[clap(alias = "n")] + New { + #[clap(subcommand)] + template: Template, }, /// Marks one or more entries as done + #[clap(alias = "d")] Done { /// Entries to mark as done - #[structopt(required = true)] + #[clap(required = true)] entries: Vec, }, - /// Reformat all loaded files + /// Marks one or more entries as canceled + #[clap(alias = "c")] + Cancel { + /// Entries to mark as done + #[clap(required = true)] + entries: Vec, + }, + /// Edits or creates a log entry + #[clap(alias = "l")] + Log { + #[clap(default_value = "t")] + date: String, + }, + /// Reformats all loaded files Fmt, } +#[derive(Debug, clap::Subcommand)] +pub enum Template { + /// Adds a task + #[clap(alias = "t")] + Task { + /// If specified, the task is dated to this date + date: Option, + }, + /// Adds a note + #[clap(alias = "n")] + Note { + /// If specified, the note is dated to this date + date: Option, + }, + /// Adds an undated task marked as done today + #[clap(alias = "d")] + Done, +} + fn default_file() -> PathBuf { ProjectDirs::from("", "", "today") .expect("could not determine config dir") @@ -60,18 +104,9 @@ fn default_file() -> PathBuf { .join("main.today") } -fn load_files(opt: &Opt) -> result::Result { +fn load_files(opt: &Opt, files: &mut Files) -> result::Result<(), files::Error> { let file = opt.file.clone().unwrap_or_else(default_file); - Files::load(&file) -} - -fn find_now(opt: &Opt, files: &Files) -> NaiveDateTime { - let now = files.now().naive_local(); - if let Some(date) = opt.date { - date.and_time(now.time()) - } else { - now - } + files.load(&file) } fn find_entries(files: &Files, range: DateRange) -> Result> { @@ -87,6 +122,37 @@ fn find_layout( layout::layout(files, entries, range, now) } +fn parse_eval_arg(name: &str, text: &str, eval: E) -> Result +where + T: FromStr>, + E: FnOnce(T) -> result::Result>, +{ + let value = T::from_str(text).map_err(|error| Error::ArgumentParse { + file: SimpleFile::new(name.to_string(), text.to_string()), + error, + })?; + eval(value).map_err(|error| Error::ArgumentEval { + file: SimpleFile::new(name.to_string(), text.to_string()), + error, + }) +} + +fn parse_eval_date(name: &str, text: &str, today: NaiveDate) -> Result { + parse_eval_arg(name, text, |date: CliDate| date.eval((), today)) +} + +fn parse_show_idents(identifiers: &[String], today: NaiveDate) -> Result> { + let mut idents = vec![]; + for ident in identifiers { + let ident = parse_eval_arg("identifier", ident, |ident: CliIdent| match ident { + CliIdent::Number(n) => Ok(show::Ident::Number(n)), + CliIdent::Date(d) => Ok(show::Ident::Date(d.eval((), today)?)), + })?; + idents.push(ident); + } + Ok(idents) +} + fn run_command(opt: &Opt, files: &mut Files, range: DateRange, now: NaiveDateTime) -> Result<()> { match &opt.command { None => { @@ -94,11 +160,25 @@ fn run_command(opt: &Opt, files: &mut Files, range: DateRange, now: NaiveDateTim let layout = find_layout(files, &entries, range, now); print::print(&layout); } - Some(Command::Show { entries: ns }) => { + Some(Command::Show { identifiers }) => { let entries = find_entries(files, range)?; let layout = find_layout(files, &entries, range, now); - show::show(files, &entries, &layout, ns)?; + let idents = parse_show_idents(identifiers, now.date())?; + show::show(files, &entries, &layout, &idents); } + Some(Command::New { template }) => match template { + Template::Task { date: Some(date) } => { + let date = parse_eval_date("date", date, now.date())?; + new::task(files, Some(date))? + } + Template::Task { date: None } => new::task(files, None)?, + Template::Note { date: Some(date) } => { + let date = parse_eval_date("date", date, now.date())?; + new::note(files, Some(date))? + } + Template::Note { date: None } => new::note(files, None)?, + Template::Done => new::done(files, now.date())?, + }, Some(Command::Done { entries: ns }) => { let entries = find_entries(files, range)?; let layout = find_layout(files, &entries, range, now); @@ -107,50 +187,55 @@ fn run_command(opt: &Opt, files: &mut Files, range: DateRange, now: NaiveDateTim let layout = find_layout(files, &entries, range, now); print::print(&layout); } + Some(Command::Cancel { entries: ns }) => { + let entries = find_entries(files, range)?; + let layout = find_layout(files, &entries, range, now); + cancel::cancel(files, &entries, &layout, ns, now)?; + let entries = find_entries(files, range)?; + let layout = find_layout(files, &entries, range, now); + print::print(&layout); + } + Some(Command::Log { date }) => { + let date = parse_eval_arg("date", date, |date: CliDate| date.eval((), now.date()))?; + log::log(files, date)? + } Some(Command::Fmt) => files.mark_all_dirty(), } Ok(()) } +fn run_with_files(opt: Opt, files: &mut Files) -> Result<()> { + let now = files.now().naive_local(); + let today = parse_eval_arg("--date", &opt.date, |date: CliDate| { + date.eval((), now.date()) + })?; + let now = today.and_time(now.time()); + + let range = parse_eval_arg("--range", &opt.range, |range: CliRange| { + range.eval((), now.date()) + })?; + + run_command(&opt, files, range, now)?; + + Ok(()) +} + pub fn run() { - let opt = Opt::from_args(); + let opt = Opt::parse(); - let mut files = match load_files(&opt) { - Ok(result) => result, - Err(e) => { - e.print(); - process::exit(1); - } - }; + let mut files = Files::new(); + if let Err(e) = load_files(&opt, &mut files) { + crate::error::eprint_error(&files, &e); + process::exit(1); + } - let now = find_now(&opt, &files); - - // Kinda ugly, but it can stay for now (until it grows at least). - let range = match Range::from_str(&opt.range) { - Ok(range) => match range.eval(0, now.date()) { - Ok(range) => range, - Err(e) => { - eprintln!("Failed to evaluate --range:"); - e.print(&[SourceInfo { - name: Some("--range".to_string()), - content: &opt.range, - }]); - process::exit(1) - } - }, - Err(e) => { - eprintln!("Failed to parse --range:\n{}", e.with_path("--range")); - process::exit(1) - } - }; - - if let Err(e) = run_command(&opt, &mut files, range, now) { - e.print(&files.sources()); + if let Err(e) = run_with_files(opt, &mut files) { + crate::error::eprint_error(&files, &e); process::exit(1); } if let Err(e) = files.save() { - e.print(); + crate::error::eprint_error(&files, &e); process::exit(1); } } diff --git a/src/cli/cancel.rs b/src/cli/cancel.rs new file mode 100644 index 0000000..62cc8ea --- /dev/null +++ b/src/cli/cancel.rs @@ -0,0 +1,37 @@ +use std::vec; + +use chrono::NaiveDateTime; + +use crate::eval::Entry; +use crate::files::commands::{Done, DoneKind}; +use crate::files::Files; + +use super::error::{Error, Result}; +use super::layout::line::LineLayout; + +pub fn cancel( + files: &mut Files, + entries: &[Entry], + layout: &LineLayout, + numbers: &[usize], + now: NaiveDateTime, +) -> Result<()> { + let mut not_tasks = vec![]; + for &number in numbers { + let entry = &entries[layout.look_up_number(number)?]; + let done = Done { + kind: DoneKind::Canceled, + date: entry.dates.map(|dates| dates.into()), + done_at: now.date(), + }; + if !files.add_done(entry.source, done) { + not_tasks.push(number); + } + } + + if not_tasks.is_empty() { + Ok(()) + } else { + Err(Error::NotATask(not_tasks)) + } +} diff --git a/src/cli/done.rs b/src/cli/done.rs index 379d6ce..1d3d809 100644 --- a/src/cli/done.rs +++ b/src/cli/done.rs @@ -3,7 +3,7 @@ use std::vec; use chrono::NaiveDateTime; use crate::eval::Entry; -use crate::files::commands::Done; +use crate::files::commands::{Done, DoneKind}; use crate::files::Files; use super::error::{Error, Result}; @@ -20,6 +20,7 @@ pub fn done( for &number in numbers { let entry = &entries[layout.look_up_number(number)?]; let done = Done { + kind: DoneKind::Done, date: entry.dates.map(|dates| dates.into()), done_at: now.date(), }; diff --git a/src/cli/error.rs b/src/cli/error.rs index 3bf054e..05b239a 100644 --- a/src/cli/error.rs +++ b/src/cli/error.rs @@ -1,23 +1,54 @@ -use std::result; +use std::{io, result}; -use crate::eval::{self, SourceInfo}; +use chrono::NaiveDate; +use codespan_reporting::files::{Files, SimpleFile}; +use codespan_reporting::term::Config; + +use crate::error::Eprint; +use crate::files::FileSource; +use crate::{eval, files}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] - Eval(#[from] eval::Error), + Eval(#[from] eval::Error), + #[error("{error}")] + ArgumentParse { + file: SimpleFile, + error: files::ParseError<()>, + }, + #[error("{error}")] + ArgumentEval { + file: SimpleFile, + error: eval::Error<()>, + }, #[error("No entry with number {0}")] NoSuchEntry(usize), + #[error("No log for {0}")] + NoSuchLog(NaiveDate), #[error("Not a task")] NotATask(Vec), + #[error("No capture file found")] + NoCaptureFile, + #[error("Error editing: {0}")] + EditingIo(io::Error), } -impl Error { - pub fn print<'a>(&self, sources: &[SourceInfo<'a>]) { +pub type Result = result::Result; + +impl<'a, F> Eprint<'a, F> for Error +where + F: Files<'a, FileId = FileSource>, +{ + #[allow(single_use_lifetimes)] + fn eprint<'f: 'a>(&self, files: &'f F, config: &Config) { match self { - Error::Eval(e) => e.print(sources), - Error::NoSuchEntry(n) => eprintln!("No entry with number {}", n), - Error::NotATask(ns) => { + Self::Eval(e) => e.eprint(files, config), + Self::ArgumentParse { file, error } => error.eprint(file, config), + Self::ArgumentEval { file, error } => error.eprint(file, config), + Self::NoSuchEntry(n) => eprintln!("No entry with number {n}"), + Self::NoSuchLog(date) => eprintln!("No log for {date}"), + Self::NotATask(ns) => { if ns.is_empty() { eprintln!("Not a task."); } else if ns.len() == 1 { @@ -27,8 +58,11 @@ impl Error { eprintln!("{} are not tasks.", ns.join(", ")); } } + Self::NoCaptureFile => eprintln!("No capture file found"), + Self::EditingIo(error) => { + eprintln!("Error while editing:"); + eprintln!(" {error}"); + } } } } - -pub type Result = result::Result; diff --git a/src/cli/layout.rs b/src/cli/layout.rs index 76575eb..b063c44 100644 --- a/src/cli/layout.rs +++ b/src/cli/layout.rs @@ -16,7 +16,7 @@ pub fn layout( now: NaiveDateTime, ) -> LineLayout { let mut day_layout = DayLayout::new(range, now); - day_layout.layout(files, entries); + day_layout.layout(entries); let mut line_layout = LineLayout::new(); line_layout.render(files, entries, &day_layout); diff --git a/src/cli/layout/day.rs b/src/cli/layout/day.rs index 0cb4c0b..044269c 100644 --- a/src/cli/layout/day.rs +++ b/src/cli/layout/day.rs @@ -8,9 +8,7 @@ use std::collections::HashMap; use chrono::{NaiveDate, NaiveDateTime}; use crate::eval::{DateRange, Dates, Entry, EntryKind}; -use crate::files::commands::Command; use crate::files::primitives::Time; -use crate::files::Files; #[derive(Debug)] pub enum DayEntry { @@ -48,18 +46,13 @@ impl DayLayout { } } - pub fn layout(&mut self, files: &Files, entries: &[Entry]) { + pub fn layout(&mut self, entries: &[Entry]) { self.insert(self.today, DayEntry::Now(self.time)); - let mut commands = entries - .iter() - .enumerate() - .map(|(i, e)| (i, e, files.command(e.source))) - .collect::>(); + let mut entries = entries.iter().enumerate().collect::>(); + Self::sort_entries(&mut entries); - Self::sort_entries(&mut commands); - - for (index, entry, _) in commands { + for (index, entry) in entries { self.layout_entry(index, entry); } @@ -73,7 +66,9 @@ impl DayLayout { fn layout_entry(&mut self, index: usize, entry: &Entry) { match entry.kind { EntryKind::Task => self.layout_task(index, entry), - EntryKind::TaskDone(at) => self.layout_task_done(index, entry, at), + EntryKind::TaskDone(at) | EntryKind::TaskCanceled(at) => { + self.layout_task_done(index, entry, at) + } EntryKind::Note | EntryKind::Birthday(_) => self.layout_note(index, entry), } } @@ -81,10 +76,17 @@ impl DayLayout { fn layout_task(&mut self, index: usize, entry: &Entry) { if let Some(dates) = entry.dates { let (start, end) = dates.sorted().dates(); - if self.today < start && (start - self.today).num_days() < 7 { - // TODO Make this adjustable, maybe even per-command - let days = (start - self.today).num_days(); - self.insert(self.today, DayEntry::ReminderUntil(index, days)); + if self.today < self.range.from() || self.range.until() < self.today { + // If `self.today` is not in range, reminders won't be displayed + // (since they're always displayed on `self.today`) so there's + // no need to calculate them. + } else if self.today < start { + if let Some(remind) = entry.remind { + if remind <= self.today { + let days = (start - self.today).num_days(); + self.insert(self.today, DayEntry::ReminderUntil(index, days)); + } + } } else if start < self.today && self.today < end { let days = (end - self.today).num_days(); self.insert(self.today, DayEntry::ReminderWhile(index, days)); @@ -115,7 +117,18 @@ impl DayLayout { fn layout_note(&mut self, index: usize, entry: &Entry) { if let Some(dates) = entry.dates { let (start, end) = dates.sorted().dates(); - if start < self.range.from() && self.range.until() < end { + if self.today < self.range.from() || self.range.until() < self.today { + // if `self.today` is not in range, reminders won't be displayed + // (since they're always displayed on `self.today`) so there's + // no need to calculate them. + } else if self.today < start { + if let Some(remind) = entry.remind { + if remind <= self.today { + let days = (start - self.today).num_days(); + self.insert(self.today, DayEntry::ReminderUntil(index, days)); + } + } + } else if start < self.range.from() && self.range.until() < end { // This note applies to the current day, but it won't appear if // we just layout it as a dated entry, so instead we add it as a // reminder. Since we are usually more interested in when @@ -123,9 +136,8 @@ impl DayLayout { // the end. let days = (end - self.today).num_days(); self.insert(self.today, DayEntry::ReminderWhile(index, days)); - } else { - self.layout_dated_entry(index, dates); } + self.layout_dated_entry(index, dates); } else { self.insert(self.today, DayEntry::Undated(index)); } @@ -177,7 +189,7 @@ impl DayLayout { } } - fn sort_entries(entries: &mut Vec<(usize, &Entry, &Command)>) { + fn sort_entries(entries: &mut [(usize, &Entry)]) { // Entries should be sorted by these factors, in descending order of // significance: // 1. Their start date, if any @@ -186,28 +198,28 @@ impl DayLayout { // 4. Their title // 4. - entries.sort_by_key(|(_, _, c)| c.title()); + entries.sort_by_key(|(_, e)| &e.title); // 3. - entries.sort_by_key(|(_, e, _)| match e.kind { + entries.sort_by_key(|(_, e)| match e.kind { EntryKind::Task => 0, - EntryKind::TaskDone(_) => 1, + EntryKind::TaskDone(_) | EntryKind::TaskCanceled(_) => 1, EntryKind::Birthday(_) => 2, EntryKind::Note => 3, }); // 2. - entries.sort_by(|(_, e1, _), (_, e2, _)| { + entries.sort_by(|(_, e1), (_, e2)| { let d1 = e1.dates.map(|d| d.sorted().other_with_time()); let d2 = e2.dates.map(|d| d.sorted().other_with_time()); d2.cmp(&d1) // Inverse comparison }); // 1. - entries.sort_by_key(|(_, e, _)| e.dates.map(|d| d.sorted().root_with_time())); + entries.sort_by_key(|(_, e)| e.dates.map(|d| d.sorted().root_with_time())); } - fn sort_day(day: &mut Vec) { + fn sort_day(day: &mut [DayEntry]) { // In a day, entries should be sorted into these categories: // 1. Untimed entries that end at the current day // 2. Timed entries, based on diff --git a/src/cli/layout/line.rs b/src/cli/layout/line.rs index a0e74c6..5f5d686 100644 --- a/src/cli/layout/line.rs +++ b/src/cli/layout/line.rs @@ -11,14 +11,44 @@ use crate::eval::{Entry, EntryKind}; use crate::files::primitives::Time; use crate::files::Files; -use super::super::error::{Error, Result}; +use super::super::error::Error; use super::day::{DayEntry, DayLayout}; +#[derive(Debug, Clone, Copy)] +pub enum SpanStyle { + Solid, + Dashed, + Dotted, +} + +impl SpanStyle { + fn from_indentation(index: usize) -> Self { + match index % 3 { + 0 => Self::Solid, + 1 => Self::Dashed, + 2 => Self::Dotted, + _ => unreachable!(), + } + } +} + #[derive(Debug, Clone, Copy)] pub enum SpanSegment { - Start, - Middle, - End, + Start(SpanStyle), + Middle(SpanStyle), + Mark(SpanStyle), + End(SpanStyle), +} + +impl SpanSegment { + fn style(&self) -> SpanStyle { + match self { + Self::Start(s) => *s, + Self::Middle(s) => *s, + Self::Mark(s) => *s, + Self::End(s) => *s, + } + } } #[derive(Debug, Clone, Copy)] @@ -28,10 +58,21 @@ pub enum Times { FromTo(Time, Time), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LineKind { + Task, + Done, + Canceled, + Note, + Birthday, +} + pub enum LineEntry { Day { spans: Vec>, date: NaiveDate, + today: bool, + has_log: bool, }, Now { spans: Vec>, @@ -40,8 +81,12 @@ pub enum LineEntry { Entry { number: Option, spans: Vec>, + today: bool, time: Times, + kind: LineKind, text: String, + has_desc: bool, + extra: Option, }, } @@ -79,12 +124,18 @@ impl LineLayout { self.step_spans(); for day in layout.range.days() { + let today = day == layout.today; let spans = self.spans_for_line(); - self.line(LineEntry::Day { spans, date: day }); + self.line(LineEntry::Day { + spans, + date: day, + today, + has_log: files.log(day).is_some(), + }); let layout_entries = layout.days.get(&day).expect("got nonexisting day"); for layout_entry in layout_entries { - self.render_layout_entry(files, entries, layout_entry); + self.render_layout_entry(entries, layout_entry, today); } } } @@ -101,7 +152,7 @@ impl LineLayout { &self.lines } - pub fn look_up_number(&self, number: usize) -> Result { + pub fn look_up_number(&self, number: usize) -> Result { self.numbers .iter() .filter(|(_, n)| **n == number) @@ -110,12 +161,11 @@ impl LineLayout { .ok_or(Error::NoSuchEntry(number)) } - fn render_layout_entry(&mut self, files: &Files, entries: &[Entry], l_entry: &DayEntry) { + fn render_layout_entry(&mut self, entries: &[Entry], l_entry: &DayEntry, today: bool) { match l_entry { DayEntry::End(i) => { self.stop_span(*i); - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), Times::Untimed, text); + self.line_entry(entries, *i, today, Times::Untimed, None); } DayEntry::Now(t) => self.line(LineEntry::Now { spans: self.spans_for_line(), @@ -123,89 +173,95 @@ impl LineLayout { }), DayEntry::TimedEnd(i, t) => { self.stop_span(*i); - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), Times::At(*t), text); + self.line_entry(entries, *i, today, Times::At(*t), None); } DayEntry::TimedAt(i, t, t2) => { - let time = t2 - .map(|t2| Times::FromTo(*t, t2)) - .unwrap_or_else(|| Times::At(*t)); - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), time, text); + let time = t2.map(|t2| Times::FromTo(*t, t2)).unwrap_or(Times::At(*t)); + self.line_entry(entries, *i, today, time, None); } DayEntry::TimedStart(i, t) => { self.start_span(*i); - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), Times::At(*t), text); + self.line_entry(entries, *i, today, Times::At(*t), None); } DayEntry::ReminderSince(i, d) => { - let text = Self::format_entry(files, entries, *i); - let text = if *d == 1 { - format!("{} (yesterday)", text) + let extra = if *d == 1 { + "yesterday".to_string() } else { - format!("{} ({} days ago)", text, d) + format!("{d} days ago") }; - self.line_entry(Some(*i), Times::Untimed, text); + self.line_entry(entries, *i, today, Times::Untimed, Some(extra)); } DayEntry::At(i) => { - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), Times::Untimed, text); + self.line_entry(entries, *i, today, Times::Untimed, None); } DayEntry::ReminderWhile(i, d) => { - let text = Self::format_entry(files, entries, *i); let plural = if *d == 1 { "" } else { "s" }; - let text = format!("{} ({} day{} left)", text, i, plural); - self.line_entry(Some(*i), Times::Untimed, text); + let extra = format!("{d} day{plural} left"); + self.mark_span(*i); + self.line_entry(entries, *i, today, Times::Untimed, Some(extra)); } DayEntry::Undated(i) => { - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), Times::Untimed, text); + self.line_entry(entries, *i, today, Times::Untimed, None); } DayEntry::Start(i) => { self.start_span(*i); - let text = Self::format_entry(files, entries, *i); - self.line_entry(Some(*i), Times::Untimed, text); + self.line_entry(entries, *i, today, Times::Untimed, None); } DayEntry::ReminderUntil(i, d) => { - let text = Self::format_entry(files, entries, *i); - let text = if *d == 1 { - format!("{} (tomorrow)", text) + let extra = if *d == 1 { + "tomorrow".to_string() } else { - format!("{} (in {} days)", text, d) + format!("in {d} days") }; - self.line_entry(Some(*i), Times::Untimed, text); + self.line_entry(entries, *i, today, Times::Untimed, Some(extra)); } } } - fn format_entry(files: &Files, entries: &[Entry], index: usize) -> String { - let entry = entries[index]; - let command = files.command(entry.source); + pub fn entry_kind(entry: &Entry) -> LineKind { match entry.kind { - EntryKind::Task => format!("T {}", command.title()), - EntryKind::TaskDone(_) => format!("D {}", command.title()), - EntryKind::Note => format!("N {}", command.title()), - EntryKind::Birthday(Some(age)) => format!("B {} ({})", command.title(), age), - EntryKind::Birthday(None) => format!("B {}", command.title()), + EntryKind::Task => LineKind::Task, + EntryKind::TaskDone(_) => LineKind::Done, + EntryKind::TaskCanceled(_) => LineKind::Canceled, + EntryKind::Note => LineKind::Note, + EntryKind::Birthday(_) => LineKind::Birthday, + } + } + + fn entry_title(entry: &Entry) -> String { + match entry.kind { + EntryKind::Birthday(Some(age)) => format!("{} ({})", entry.title, age), + _ => entry.title.clone(), } } fn start_span(&mut self, index: usize) { - for span in self.spans.iter_mut() { + for (i, span) in self.spans.iter_mut().enumerate() { if span.is_none() { - *span = Some((index, SpanSegment::Start)); + let style = SpanStyle::from_indentation(i); + *span = Some((index, SpanSegment::Start(style))); return; } } // Not enough space, we need another column - self.spans.push(Some((index, SpanSegment::Start))); + let style = SpanStyle::from_indentation(self.spans.len()); + self.spans.push(Some((index, SpanSegment::Start(style)))); + } + + fn mark_span(&mut self, index: usize) { + for span in self.spans.iter_mut() { + match span { + Some((i, s)) if *i == index => *s = SpanSegment::Mark(s.style()), + _ => {} + } + } } fn stop_span(&mut self, index: usize) { for span in self.spans.iter_mut() { match span { - Some((i, s)) if *i == index => *s = SpanSegment::End, + Some((i, s)) if *i == index => *s = SpanSegment::End(s.style()), _ => {} } } @@ -214,8 +270,10 @@ impl LineLayout { fn step_spans(&mut self) { for span in self.spans.iter_mut() { match span { - Some((_, s @ SpanSegment::Start)) => *s = SpanSegment::Middle, - Some((_, SpanSegment::End)) => *span = None, + Some((_, s @ (SpanSegment::Start(_) | SpanSegment::Mark(_)))) => { + *s = SpanSegment::Middle(s.style()) + } + Some((_, SpanSegment::End(_))) => *span = None, _ => {} } } @@ -233,24 +291,34 @@ impl LineLayout { self.step_spans(); } - fn line_entry(&mut self, index: Option, time: Times, text: String) { - let number = match index { - Some(index) => Some(match self.numbers.get(&index) { - Some(number) => *number, - None => { - self.last_number += 1; - self.numbers.insert(index, self.last_number); - self.last_number - } - }), - None => None, + fn line_entry( + &mut self, + entries: &[Entry], + index: usize, + today: bool, + time: Times, + extra: Option, + ) { + let entry = &entries[index]; + + let number = match self.numbers.get(&index) { + Some(number) => *number, + None => { + self.last_number += 1; + self.numbers.insert(index, self.last_number); + self.last_number + } }; self.line(LineEntry::Entry { - number, + number: Some(number), spans: self.spans_for_line(), + today, time, - text, + kind: Self::entry_kind(entry), + text: Self::entry_title(entry), + has_desc: entry.has_description, + extra, }); } } diff --git a/src/cli/log.rs b/src/cli/log.rs new file mode 100644 index 0000000..3703cef --- /dev/null +++ b/src/cli/log.rs @@ -0,0 +1,24 @@ +use chrono::NaiveDate; + +use crate::files::Files; + +use super::error::Error; +use super::util; + +pub fn log(files: &mut Files, date: NaiveDate) -> Result<(), Error> { + let desc = files + .log(date) + .map(|log| log.value.desc.join("\n")) + .unwrap_or_default(); + + let edited = util::edit_with_suffix(&desc, ".md")?; + + let edited = edited + .lines() + .map(|line| line.to_string()) + .collect::>(); + + files.set_log(date, edited); + + Ok(()) +} diff --git a/src/cli/new.rs b/src/cli/new.rs new file mode 100644 index 0000000..ecd040f --- /dev/null +++ b/src/cli/new.rs @@ -0,0 +1,114 @@ +use std::result; +use std::str::FromStr; + +use chrono::NaiveDate; +use codespan_reporting::files::SimpleFile; + +use crate::files::cli::CliCommand; +use crate::files::commands::{Command, DateSpec, Done, DoneKind, Note, Spec, Statement, Task}; +use crate::files::{Files, ParseError}; + +use super::error::{Error, Result}; +use super::util; + +fn edit(name: &str, mut text: String, validate: F) -> Result> +where + R: FromStr>, + F: Fn(&R) -> result::Result<(), &str>, +{ + Ok(loop { + text = util::edit(&text)?; + match text.parse() { + Ok(command) => match validate(&command) { + Ok(()) => break Some(command), + Err(msg) => eprintln!("{msg}"), + }, + Err(e) => crate::error::eprint_error(&SimpleFile::new(name, &text), &e), + } + if !matches!( + promptly::prompt_default("Continue editing?", true), + Ok(true) + ) { + println!("Aborting"); + break None; + } + }) +} + +fn is_task_or_note(command: &CliCommand) -> result::Result<(), &str> { + match command.0 { + Command::Task(_) | Command::Note(_) => Ok(()), + _ => Err("Only TASK and NOTE are allowed"), + } +} + +fn new_command(files: &mut Files, command: Command) -> Result<()> { + let capture = files.capture().ok_or(Error::NoCaptureFile)?; + + let command = edit("new command", format!("{command}"), is_task_or_note)?; + if let Some(command) = command { + files.insert(capture, command.0) + } + + Ok(()) +} + +pub fn task(files: &mut Files, date: Option) -> Result<()> { + let statements = match date { + Some(date) => vec![Statement::Date(Spec::Date(DateSpec { + start: date, + start_delta: None, + start_time: None, + end: None, + end_delta: None, + end_time: None, + repeat: None, + }))], + None => vec![], + }; + let command = Command::Task(Task { + title: String::new(), + statements, + done: vec![], + desc: vec![], + }); + + new_command(files, command) +} + +pub fn note(files: &mut Files, date: Option) -> Result<()> { + let statements = match date { + Some(date) => vec![Statement::Date(Spec::Date(DateSpec { + start: date, + start_delta: None, + start_time: None, + end: None, + end_delta: None, + end_time: None, + repeat: None, + }))], + None => vec![], + }; + let command = Command::Note(Note { + title: String::new(), + statements, + desc: vec![], + }); + + new_command(files, command) +} + +pub fn done(files: &mut Files, date: NaiveDate) -> Result<()> { + let command = Command::Task(Task { + title: String::new(), + statements: vec![], + done: vec![Done { + kind: DoneKind::Done, + date: None, + done_at: date, + }], + desc: vec![], + }); + + new_command(files, command) +} diff --git a/src/cli/print.rs b/src/cli/print.rs index cd04776..cabba63 100644 --- a/src/cli/print.rs +++ b/src/cli/print.rs @@ -1,10 +1,12 @@ use std::cmp; use chrono::{Datelike, NaiveDate}; +use colored::{ColoredString, Colorize}; use crate::files::primitives::{Time, Weekday}; -use super::layout::line::{LineEntry, LineLayout, SpanSegment, Times}; +use super::layout::line::{LineEntry, LineKind, LineLayout, SpanSegment, SpanStyle, Times}; +use super::util; struct ShowLines { num_width: usize, @@ -23,86 +25,156 @@ impl ShowLines { fn display_line(&mut self, line: &LineEntry) { match line { - LineEntry::Day { spans, date } => self.display_line_date(spans, *date), + LineEntry::Day { + spans, + date, + today, + has_log, + } => self.display_line_date(spans, *date, *today, *has_log), LineEntry::Now { spans, time } => self.display_line_now(spans, *time), LineEntry::Entry { number, spans, + today, time, + kind, text, - } => self.display_line_entry(*number, spans, *time, text), + has_desc, + extra, + } => self + .display_line_entry(*number, spans, *today, *time, *kind, text, *has_desc, extra), } } - fn display_line_date(&mut self, spans: &[Option], date: NaiveDate) { + fn display_line_date( + &mut self, + spans: &[Option], + date: NaiveDate, + today: bool, + has_log: bool, + ) { let weekday: Weekday = date.weekday().into(); let weekday = weekday.full_name(); - self.push(&format!( - "{:=>nw$}={:=nw$}\n", + + let styled = |s: &str| { + if today { + s.bright_cyan().bold() + } else { + s.cyan() + } + }; + + // '=' symbols before the spans start + let p1 = styled(&format!("{:=], time: Time) { self.push(&format!( - "{:nw$} {} {}\n", + "now".bright_cyan().bold(), + self.display_spans(spans, " ".into()), + Self::display_time(Times::At(time)), nw = self.num_width, - sw = self.span_width )); } + #[allow(clippy::too_many_arguments)] fn display_line_entry( &mut self, number: Option, spans: &[Option], + today: bool, time: Times, + kind: LineKind, text: &str, + has_desc: bool, + extra: &Option, ) { let num = match number { - Some(n) => format!("{}", n), + Some(n) => format!("{n}"), None => "".to_string(), }; - let time = match time { - Times::Untimed => "".to_string(), - Times::At(t) => format!("{} ", t), - Times::FromTo(t1, t2) => format!("{}--{} ", t1, t2), + let text = if kind == LineKind::Birthday && today { + util::display_current_birthday_text(text) + } else { + text.into() }; self.push(&format!( - "{:>nw$} {:sw$} {}{}\n", - num, - Self::display_spans(spans, ' '), - time, + "{:>nw$} {} {}{} {}{}{}\n", + num.bright_black(), + self.display_spans(spans, " ".into()), + util::display_kind(kind), + Self::display_time(time), text, + Self::display_marker(has_desc, ""), + Self::display_extra(extra), nw = self.num_width, - sw = self.span_width )) } - fn display_spans(spans: &[Option], empty: char) -> String { + fn display_spans(&self, spans: &[Option], empty: ColoredString) -> String { let mut result = String::new(); - for segment in spans { - result.push(match segment { - Some(SpanSegment::Start) => '┌', - Some(SpanSegment::Middle) => '│', - Some(SpanSegment::End) => '└', - None => empty, - }); + for i in 0..self.span_width { + if let Some(Some(segment)) = spans.get(i) { + let colored_str = match segment { + SpanSegment::Start(_) => "┌".bright_black(), + SpanSegment::Middle(SpanStyle::Solid) => "│".bright_black(), + SpanSegment::Middle(SpanStyle::Dashed) => "╎".bright_black(), + SpanSegment::Middle(SpanStyle::Dotted) => "┊".bright_black(), + SpanSegment::Mark(_) => "┝".bright_black(), + SpanSegment::End(_) => "└".bright_black(), + }; + result.push_str(&format!("{colored_str}")); + } else { + result.push_str(&format!("{empty}")); + } } result } + fn display_time(time: Times) -> ColoredString { + match time { + Times::Untimed => "".into(), + Times::At(t) => format!(" {t}").bright_black(), + Times::FromTo(t1, t2) => format!(" {t1}--{t2}").bright_black(), + } + } + + fn display_marker(marker: bool, otherwise: &str) -> ColoredString { + if marker { + "*".bright_yellow() + } else { + otherwise.into() + } + } + + fn display_extra(extra: &Option) -> ColoredString { + match extra { + None => "".into(), + Some(extra) => format!(" ({extra})").bright_black(), + } + } + fn push(&mut self, line: &str) { self.result.push_str(line); } diff --git a/src/cli/show.rs b/src/cli/show.rs index 1b20413..12bcc31 100644 --- a/src/cli/show.rs +++ b/src/cli/show.rs @@ -1,61 +1,108 @@ -use crate::eval::{Entry, EntryKind}; -use crate::files::Files; +use chrono::NaiveDate; +use codespan_reporting::files::Files as CsFiles; +use colored::Colorize; -use super::error::Result; +use crate::eval::{Entry, EntryKind}; +use crate::files::commands::{Command, Log}; +use crate::files::primitives::Spanned; +use crate::files::{Files, Sourced}; + +use super::error::Error; use super::layout::line::LineLayout; +use super::util; + +fn fmt_where(files: &Files, command: &Sourced<'_, Spanned>) -> String { + let name = files.name(command.source.file()).expect("file exists"); + let line = files + .line_index(command.source.file(), command.value.span.start) + .expect("file exists and line is valid"); + let line = line + 1; // 1-indexed for human consumption + format!("Line {line} in {name}") +} + +fn print_desc(command: &Sourced<'_, Spanned>) { + let desc: &[String] = match &command.value.value { + Command::Task(task) => &task.desc, + Command::Note(note) => ¬e.desc, + Command::Log(log) => &log.desc, + _ => &[], + }; + if !desc.is_empty() { + println!(); + for line in desc { + println!("{}", line); + } + } +} fn show_entry(files: &Files, entry: &Entry) { let command = files.command(entry.source); - match entry.kind { - EntryKind::Task => println!("TASK {}", command.title()), - EntryKind::TaskDone(when) => { - println!("DONE {}", command.title()); - println!("DONE AT {}", when); - } - EntryKind::Note => println!("NOTE {}", command.title()), - EntryKind::Birthday(Some(age)) => { - println!("BIRTHDAY {}", command.title()); - println!("AGE {}", age); - } - EntryKind::Birthday(None) => { - println!("BIRTHDAY {}", command.title()); - println!("AGE UNKNOWN"); - } - } + let kind = util::display_kind(LineLayout::entry_kind(entry)); + println!("{} {} {}", "Title:".bright_black(), kind, entry.title); - if let Some(dates) = entry.dates { - println!("DATE {}", dates.sorted()); - } else { - println!("NO DATE"); - } + let what = match entry.kind { + EntryKind::Task => "Task".to_string(), + EntryKind::TaskDone(date) => format!("Task, done {date}"), + EntryKind::TaskCanceled(date) => format!("Task, canceled {date}"), + EntryKind::Note => "Note".to_string(), + EntryKind::Birthday(None) => "Birthday, age unknown".to_string(), + EntryKind::Birthday(Some(age)) => format!("Birthday, age {age}"), + }; + println!("{} {}", "What:".bright_black(), what); - for line in command.desc() { - println!("# {}", line); + let when = match entry.dates { + None => "no date".to_string(), + Some(date) => format!("{}", date.sorted()), + }; + println!("{} {}", "When:".bright_black(), when); + + println!("{} {}", "Where:".bright_black(), fmt_where(files, &command)); + + print_desc(&command); +} + +fn show_log(files: &Files, log: Sourced<'_, Log>) { + let command = files.command(log.source); + + println!("{} Log entry", "What:".bright_black()); + println!("{} {}", "When:".bright_black(), log.value.date); + + println!("{} {}", "Where:".bright_black(), fmt_where(files, &command)); + + print_desc(&command); +} + +fn show_ident(files: &Files, entries: &[Entry], layout: &LineLayout, ident: Ident) { + match ident { + Ident::Number(n) => match layout.look_up_number(n) { + Ok(index) => show_entry(files, &entries[index]), + Err(e) => println!("{e}"), + }, + Ident::Date(date) => match files.log(date) { + Some(log) => show_log(files, log), + None => println!("{}", Error::NoSuchLog(date)), + }, } } -pub fn show( - files: &Files, - entries: &[Entry], - layout: &LineLayout, - numbers: &[usize], -) -> Result<()> { - if numbers.is_empty() { +#[derive(Debug, Clone, Copy)] +pub enum Ident { + Number(usize), + Date(NaiveDate), +} + +pub fn show(files: &Files, entries: &[Entry], layout: &LineLayout, idents: &[Ident]) { + if idents.is_empty() { // Nothing to do - return Ok(()); + return; } - let indices = numbers - .iter() - .map(|n| layout.look_up_number(*n)) - .collect::>>()?; - - show_entry(files, &entries[indices[0]]); - for &index in indices.iter().skip(1) { + show_ident(files, entries, layout, idents[0]); + for &ident in idents.iter().skip(1) { println!(); - show_entry(files, &entries[index]); + println!(); + println!(); + show_ident(files, entries, layout, ident); } - - Ok(()) } diff --git a/src/cli/util.rs b/src/cli/util.rs new file mode 100644 index 0000000..e862088 --- /dev/null +++ b/src/cli/util.rs @@ -0,0 +1,28 @@ +use colored::{ColoredString, Colorize}; + +use super::error::{Error, Result}; +use super::layout::line::LineKind; + +pub fn display_kind(kind: LineKind) -> ColoredString { + match kind { + LineKind::Task => "T".magenta().bold(), + LineKind::Done => "D".green().bold(), + LineKind::Canceled => "C".red().bold(), + LineKind::Note => "N".blue().bold(), + LineKind::Birthday => "B".yellow().bold(), + } +} + +pub fn display_current_birthday_text(text: &str) -> ColoredString { + text.yellow() +} + +pub fn edit(input: &str) -> Result { + edit::edit(input).map_err(Error::EditingIo) +} + +pub fn edit_with_suffix(input: &str, suffix: &str) -> Result { + let mut builder = edit::Builder::new(); + builder.suffix(suffix); + edit::edit_with_builder(input, &builder).map_err(Error::EditingIo) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..2cee250 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,31 @@ +use codespan_reporting::diagnostic::Diagnostic; +use codespan_reporting::files::Files; +use codespan_reporting::term::{self, Config}; +use termcolor::StandardStream; + +pub trait Eprint<'a, F: Files<'a>> { + #[allow(single_use_lifetimes)] + fn eprint_diagnostic<'f: 'a>( + files: &'f F, + config: &Config, + diagnostic: &Diagnostic, + ) { + let mut out = StandardStream::stderr(termcolor::ColorChoice::Auto); + if let Err(e) = term::emit(&mut out, config, files, diagnostic) { + panic!("Error while reporting error: {e}"); + } + } + + #[allow(single_use_lifetimes)] + fn eprint<'f: 'a>(&self, files: &'f F, config: &Config); +} + +#[allow(single_use_lifetimes)] +pub fn eprint_error<'a, 'f: 'a, F, E>(files: &'f F, e: &E) +where + F: Files<'a>, + E: Eprint<'a, F>, +{ + let config = Config::default(); + e.eprint(files, &config); +} diff --git a/src/eval.rs b/src/eval.rs index e596ab1..24859af 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,14 +1,14 @@ use chrono::NaiveDate; -use crate::files::arguments::{Range, RangeDate}; -use crate::files::Files; +use crate::files::cli::{CliDate, CliDatum, CliRange}; +use crate::files::{FileSource, Files}; -use self::command::CommandState; +use self::command::{CommandState, EvalCommand}; pub use self::date::Dates; use self::delta::Delta; use self::entry::Entries; pub use self::entry::{Entry, EntryKind, EntryMode}; -pub use self::error::{Error, Result, SourceInfo}; +pub use self::error::Error; pub use self::range::DateRange; mod command; @@ -20,22 +20,41 @@ mod range; mod util; impl Files { - pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result> { + pub fn eval(&self, mode: EntryMode, range: DateRange) -> Result, Error> { let mut entries = Entries::new(mode, range); for command in self.commands() { - for entry in CommandState::new(command, range).eval()?.entries() { - entries.add(entry); + let source = command.source; + if let Some(command) = EvalCommand::new(&command.value.value) { + for entry in CommandState::new(command, source, range).eval()?.entries() { + entries.add(entry); + } } } Ok(entries.entries()) } } -impl Range { - pub fn eval(&self, index: usize, today: NaiveDate) -> Result { +impl CliDate { + pub fn eval(&self, index: S, today: NaiveDate) -> Result> { + let mut date = match self.datum { + CliDatum::Date(d) => d, + CliDatum::Today => today, + }; + + if let Some(delta) = &self.delta { + let delta: Delta = delta.into(); + date = delta.apply_date(index, date)?; + } + + Ok(date) + } +} + +impl CliRange { + pub fn eval(&self, index: S, today: NaiveDate) -> Result> { let mut start = match self.start { - RangeDate::Date(d) => d, - RangeDate::Today => today, + CliDatum::Date(d) => d, + CliDatum::Today => today, }; if let Some(delta) = &self.start_delta { @@ -46,8 +65,8 @@ impl Range { let mut end = start; match self.end { - Some(RangeDate::Date(d)) => end = d, - Some(RangeDate::Today) => end = today, + Some(CliDatum::Date(d)) => end = d, + Some(CliDatum::Today) => end = today, None => {} } diff --git a/src/eval/command.rs b/src/eval/command.rs index cec8e2d..0bec665 100644 --- a/src/eval/command.rs +++ b/src/eval/command.rs @@ -1,31 +1,100 @@ use std::collections::HashMap; -use chrono::NaiveDate; +use chrono::{Duration, NaiveDate}; -use crate::files::commands::{BirthdaySpec, Command, Done, DoneDate, Note, Spec, Statement, Task}; -use crate::files::primitives::Span; -use crate::files::SourcedCommand; +use crate::files::commands::{ + self, BirthdaySpec, Command, Done, DoneDate, DoneKind, Note, Spec, Statement, Task, +}; +use crate::files::primitives::{Span, Spanned, Time}; +use crate::files::{FileSource, Source}; use super::date::Dates; -use super::{DateRange, Entry, EntryKind, Error, Result}; +use super::delta::Delta; +use super::{DateRange, Entry, EntryKind, Error}; mod birthday; mod date; mod formula; +/// A command that can be evaluated. +pub enum EvalCommand<'a> { + Task(&'a Task), + Note(&'a Note), +} + +impl<'a> EvalCommand<'a> { + pub fn new(command: &'a Command) -> Option { + match command { + Command::Task(task) => Some(Self::Task(task)), + Command::Note(note) => Some(Self::Note(note)), + _ => None, + } + } + + fn statements(&self) -> &[Statement] { + match self { + Self::Task(task) => &task.statements, + Self::Note(note) => ¬e.statements, + } + } + + fn kind(&self) -> EntryKind { + match self { + Self::Task(_) => EntryKind::Task, + Self::Note(_) => EntryKind::Note, + } + } + + fn title(&self) -> String { + match self { + Self::Task(task) => task.title.clone(), + Self::Note(note) => note.title.clone(), + } + } + + fn has_description(&self) -> bool { + match self { + Self::Task(task) => !task.desc.is_empty(), + Self::Note(note) => !note.desc.is_empty(), + } + } + + /// Last root date mentioned in any `DONE`. + fn last_done_root(&self) -> Option { + match self { + Self::Task(task) => task + .done + .iter() + .filter_map(|done| done.date.map(DoneDate::root)) + .max(), + Self::Note(_) => None, + } + } + + /// Last completion date mentioned in any `DONE`. + fn last_done_completion(&self) -> Option { + match self { + Self::Task(task) => task.done.iter().map(|done| done.done_at).max(), + Self::Note(_) => None, + } + } +} + pub struct CommandState<'a> { - command: SourcedCommand<'a>, + command: EvalCommand<'a>, + source: Source, range: DateRange, from: Option, until: Option, + remind: Option>, dated: HashMap, undated: Vec, } impl<'a> CommandState<'a> { - pub fn new(command: SourcedCommand<'a>, mut range: DateRange) -> Self { + pub fn new(command: EvalCommand<'a>, source: Source, mut range: DateRange) -> Self { // If we don't calculate entries for the source of the move command, it // fails even though the user did nothing wrong. Also, move commands (or // chains thereof) may move an initially out-of-range entry into range. @@ -33,26 +102,28 @@ impl<'a> CommandState<'a> { // To fix this, we just expand the range to contain all move command // sources. This is a quick fix, but until it becomes a performance // issue (if ever), it's probably fine. - for statement in command.command.statements() { + for statement in command.statements() { if let Statement::Move { from, .. } = statement { range = range.containing(*from) } } Self { - range, command, + source, + range, from: None, until: None, + remind: None, dated: HashMap::new(), undated: Vec::new(), } } - pub fn eval(mut self) -> Result { - match self.command.command { - Command::Task(task) => self.eval_task(task)?, - Command::Note(note) => self.eval_note(note)?, + pub fn eval(mut self) -> Result> { + match self.command { + EvalCommand::Task(task) => self.eval_task(task)?, + EvalCommand::Note(note) => self.eval_note(note)?, } Ok(self) } @@ -66,30 +137,10 @@ impl<'a> CommandState<'a> { // Helper functions - fn kind(&self) -> EntryKind { - match self.command.command { - Command::Task(_) => EntryKind::Task, - Command::Note(_) => EntryKind::Note, - } - } - - /// Last root date mentioned in any `DONE`. - fn last_done_root(&self) -> Option { - match self.command.command { - Command::Task(task) => task - .done - .iter() - .filter_map(|done| done.date.map(DoneDate::root)) - .max(), - Command::Note(_) => None, - } - } - - /// Last completion date mentioned in any `DONE`. - fn last_done_completion(&self) -> Option { - match self.command.command { - Command::Task(task) => task.done.iter().map(|done| done.done_at).max(), - Command::Note(_) => None, + fn range_with_remind(&self) -> DateRange { + match &self.remind { + None => self.range, + Some(delta) => self.range.expand_by(&delta.value), } } @@ -113,11 +164,42 @@ impl<'a> CommandState<'a> { } } + fn entry_with_remind( + &self, + kind: EntryKind, + dates: Option, + ) -> Result> { + let remind = if let (Some(dates), Some(delta)) = (dates, &self.remind) { + let index = self.source.file(); + let start = dates.sorted().root(); + let remind = delta.value.apply_date(index, dates.sorted().root())?; + if remind >= start { + return Err(Error::RemindDidNotMoveBackwards { + index, + span: delta.span, + from: start, + to: remind, + }); + } + Some(remind) + } else { + None + }; + + Ok(Entry::new( + self.source, + kind, + self.command.title(), + self.command.has_description(), + dates, + remind, + )) + } + /// Add an entry, respecting [`Self::from`] and [`Self::until`]. Does not /// overwrite existing entries if a root date is specified. - fn add(&mut self, kind: EntryKind, dates: Option) { - let entry = Entry::new(self.command.source, kind, dates); - if let Some(dates) = dates { + fn add(&mut self, entry: Entry) { + if let Some(dates) = entry.dates { let root = dates.root(); if let Some(from) = self.from { if root < from { @@ -137,9 +219,8 @@ impl<'a> CommandState<'a> { /// Add an entry, ignoring [`Self::from`] and [`Self::until`]. Always /// overwrites existing entries if a root date is specified. - fn add_forced(&mut self, kind: EntryKind, dates: Option) { - let entry = Entry::new(self.command.source, kind, dates); - if let Some(dates) = dates { + fn add_forced(&mut self, entry: Entry) { + if let Some(dates) = entry.dates { self.dated.insert(dates.root(), entry); } else { self.undated.push(entry); @@ -154,47 +235,53 @@ impl<'a> CommandState<'a> { .any(|s| matches!(s, Statement::Date(_) | Statement::BDate(_))) } - fn eval_task(&mut self, task: &Task) -> Result<()> { + fn eval_task(&mut self, task: &Task) -> Result<(), Error> { if Self::has_date_stmt(&task.statements) { for statement in &task.statements { self.eval_statement(statement)?; } } else if task.done.is_empty() { - self.add(self.kind(), None); + self.add(self.entry_with_remind(self.command.kind(), None)?); } for done in &task.done { - self.eval_done(done); + self.eval_done(done)?; } Ok(()) } - fn eval_note(&mut self, note: &Note) -> Result<()> { + fn eval_note(&mut self, note: &Note) -> Result<(), Error> { if Self::has_date_stmt(¬e.statements) { for statement in ¬e.statements { self.eval_statement(statement)?; } } else { - self.add(self.kind(), None); + self.add(self.entry_with_remind(self.command.kind(), None)?); } Ok(()) } - fn eval_statement(&mut self, statement: &Statement) -> Result<()> { + fn eval_statement(&mut self, statement: &Statement) -> Result<(), Error> { match statement { Statement::Date(spec) => self.eval_date(spec)?, - Statement::BDate(spec) => self.eval_bdate(spec), + Statement::BDate(spec) => self.eval_bdate(spec)?, Statement::From(date) => self.from = *date, Statement::Until(date) => self.until = *date, Statement::Except(date) => self.eval_except(*date), - Statement::Move { span, from, to } => self.eval_move(*span, *from, *to)?, + Statement::Move { + span, + from, + to, + to_time, + } => self.eval_move(*span, *from, *to, *to_time)?, + Statement::Remind(delta) => self.eval_remind(delta), } Ok(()) } - fn eval_date(&mut self, spec: &Spec) -> Result<()> { + fn eval_date(&mut self, spec: &Spec) -> Result<(), Error> { match spec { Spec::Date(spec) => self.eval_date_spec(spec.into()), Spec::Weekday(spec) => self.eval_formula_spec(spec.into()), @@ -202,34 +289,69 @@ impl<'a> CommandState<'a> { } } - fn eval_bdate(&mut self, spec: &BirthdaySpec) { - self.eval_birthday_spec(spec); + fn eval_bdate(&mut self, spec: &BirthdaySpec) -> Result<(), Error> { + self.eval_birthday_spec(spec) } fn eval_except(&mut self, date: NaiveDate) { + // TODO Error if nothing is removed? self.dated.remove(&date); } - fn eval_move(&mut self, span: Span, from: NaiveDate, to: NaiveDate) -> Result<()> { + fn eval_move( + &mut self, + span: Span, + from: NaiveDate, + to: Option, + to_time: Option>, + ) -> Result<(), Error> { if let Some(mut entry) = self.dated.remove(&from) { - if let Some(dates) = entry.dates { - let delta = to - from; - entry.dates = Some(dates.move_by(delta)); + let mut dates = entry.dates.expect("comes from self.dated"); + + // Determine delta + let mut delta = Duration::zero(); + if let Some(to) = to { + delta = delta + (to - dates.root()); } - self.dated.insert(to, entry); + if let Some(to_time) = to_time { + if let Some((root, _)) = dates.times() { + delta = delta + Duration::minutes(root.minutes_to(to_time.value)); + } else { + return Err(Error::TimedMoveWithoutTime { + index: self.source.file(), + span: to_time.span, + }); + } + } + + dates = dates.move_by(delta); + entry.dates = Some(dates); + self.dated.insert(dates.root(), entry); + Ok(()) } else { Err(Error::MoveWithoutSource { - index: self.command.source.file(), + index: self.source.file(), span, }) } } - fn eval_done(&mut self, done: &Done) { - self.add_forced( - EntryKind::TaskDone(done.done_at), - done.date.map(|date| date.into()), - ); + fn eval_remind(&mut self, delta: &Option>) { + if let Some(delta) = delta { + self.remind = Some(Spanned::new(delta.span, (&delta.value).into())); + } else { + self.remind = None; + } + } + + fn eval_done(&mut self, done: &Done) -> Result<(), Error> { + let kind = match done.kind { + DoneKind::Done => EntryKind::TaskDone(done.done_at), + DoneKind::Canceled => EntryKind::TaskCanceled(done.done_at), + }; + let dates = done.date.map(|date| date.into()); + self.add_forced(self.entry_with_remind(kind, dates)?); + Ok(()) } } diff --git a/src/eval/command/birthday.rs b/src/eval/command/birthday.rs index 829160e..d6c05c7 100644 --- a/src/eval/command/birthday.rs +++ b/src/eval/command/birthday.rs @@ -1,16 +1,18 @@ use chrono::{Datelike, NaiveDate}; use crate::files::commands::BirthdaySpec; +use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; +use super::super::error::Error; use super::super::EntryKind; -impl<'a> CommandState<'a> { - pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) { - let range = match self.limit_from_until(self.range) { +impl CommandState<'_> { + pub fn eval_birthday_spec(&mut self, spec: &BirthdaySpec) -> Result<(), Error> { + let range = match self.limit_from_until(self.range_with_remind()) { Some(range) => range, - None => return, + None => return Ok(()), }; for year in range.years() { @@ -26,15 +28,19 @@ impl<'a> CommandState<'a> { let kind = EntryKind::Birthday(age); if let Some(date) = spec.date.with_year(year) { - self.add(EntryKind::Birthday(age), Some(Dates::new(date, date))); + self.add( + self.entry_with_remind(EntryKind::Birthday(age), Some(Dates::new(date, date)))?, + ); } else { assert_eq!(spec.date.month(), 2); assert_eq!(spec.date.day(), 29); - let first = NaiveDate::from_ymd(year, 2, 28); - let second = NaiveDate::from_ymd(year, 3, 1); - self.add(kind, Some(Dates::new(first, second))); + let first = NaiveDate::from_ymd_opt(year, 2, 28).unwrap(); + let second = NaiveDate::from_ymd_opt(year, 3, 1).unwrap(); + self.add(self.entry_with_remind(kind, Some(Dates::new(first, second)))?); } } + + Ok(()) } } diff --git a/src/eval/command/date.rs b/src/eval/command/date.rs index 9fe1292..58095f8 100644 --- a/src/eval/command/date.rs +++ b/src/eval/command/date.rs @@ -1,12 +1,14 @@ use chrono::NaiveDate; -use crate::files::commands::{self, Command}; +use crate::files::commands; use crate::files::primitives::{Spanned, Time}; +use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; use super::super::delta::{Delta, DeltaStep}; -use super::super::{DateRange, Error, Result}; +use super::super::{DateRange, Error}; +use super::EvalCommand; pub struct DateSpec { pub start: NaiveDate, @@ -72,28 +74,30 @@ impl DateSpec { /// `start` date itself should be skipped (and thus not result in an entry). /// This may be necessary if [`Self::start_at_done`] is set. fn start_and_range(&self, s: &CommandState<'_>) -> Option<(NaiveDate, bool, DateRange)> { - let (start, skip, range) = match s.command.command { - Command::Task(_) => { + let (start, skip, range) = match s.command { + EvalCommand::Task(_) => { let (start, skip) = s + .command .last_done_completion() .map(|start| (start, true)) .filter(|_| self.start_at_done) .unwrap_or((self.start, false)); let range_from = s + .command .last_done_root() - .map(|date| date.succ()) + .map(|date| date.succ_opt().unwrap()) .unwrap_or(self.start); let range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta) .with_from(range_from)?; (start, skip, range) } - Command::Note(_) => { + EvalCommand::Note(_) => { let start = self.start; let range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta); (start, false, range) @@ -103,7 +107,11 @@ impl DateSpec { Some((start, skip, range)) } - fn step(index: usize, from: NaiveDate, repeat: &Spanned) -> Result { + fn step( + index: FileSource, + from: NaiveDate, + repeat: &Spanned, + ) -> Result> { let to = repeat.value.apply_date(index, from)?; if to > from { Ok(to) @@ -117,7 +125,7 @@ impl DateSpec { } } - fn dates(&self, index: usize, start: NaiveDate) -> Result { + fn dates(&self, index: FileSource, start: NaiveDate) -> Result> { let root = self.start_delta.apply_date(index, start)?; Ok(if let Some(root_time) = self.start_time { let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?; @@ -129,9 +137,9 @@ impl DateSpec { } } -impl<'a> CommandState<'a> { - pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<()> { - let index = self.command.source.file(); +impl CommandState<'_> { + pub fn eval_date_spec(&mut self, spec: DateSpec) -> Result<(), Error> { + let index = self.source.file(); if let Some(repeat) = &spec.repeat { if let Some((mut start, skip, range)) = spec.start_and_range(self) { if skip { @@ -142,13 +150,13 @@ impl<'a> CommandState<'a> { } while start <= range.until() { let dates = spec.dates(index, start)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.command.kind(), Some(dates))?); start = DateSpec::step(index, start, repeat)?; } } } else { let dates = spec.dates(index, spec.start)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.command.kind(), Some(dates))?); } Ok(()) } diff --git a/src/eval/command/formula.rs b/src/eval/command/formula.rs index 43f7d21..138d3ce 100644 --- a/src/eval/command/formula.rs +++ b/src/eval/command/formula.rs @@ -1,12 +1,14 @@ use chrono::{Datelike, Duration, NaiveDate}; -use crate::files::commands::{self, Command}; +use crate::files::commands; use crate::files::primitives::{Span, Spanned, Time, Weekday}; +use crate::files::FileSource; use super::super::command::CommandState; use super::super::date::Dates; use super::super::delta::{Delta, DeltaStep}; -use super::super::{util, DateRange, Error, Result}; +use super::super::{util, DateRange, Error}; +use super::EvalCommand; fn b2i(b: bool) -> i64 { if b { @@ -47,58 +49,61 @@ pub enum Var { } impl Var { - fn eval(self, index: usize, date: NaiveDate) -> Result { + fn eval(self, index: S, date: NaiveDate) -> Result> { Ok(match self { - Var::JulianDay => date.num_days_from_ce().into(), - Var::Year => date.year().into(), - Var::YearLength => util::year_length(date.year()).into(), - Var::YearDay => date.ordinal().into(), - Var::YearDayReverse => (util::year_length(date.year()) - date.ordinal0()).into(), - Var::YearWeek => (date.ordinal0().div_euclid(7) + 1).into(), - Var::YearWeekReverse => { + Self::JulianDay => date.num_days_from_ce().into(), + Self::Year => date.year().into(), + Self::YearLength => util::year_length(date.year()).into(), + Self::YearDay => date.ordinal().into(), + Self::YearDayReverse => (util::year_length(date.year()) - date.ordinal0()).into(), + Self::YearWeek => (date.ordinal0().div_euclid(7) + 1).into(), + Self::YearWeekReverse => { #[allow(non_snake_case)] let yD = util::year_length(date.year()) - date.ordinal(); (yD.div_euclid(7) + 1).into() } - Var::Month => date.month().into(), - Var::MonthLength => util::month_length(date.year(), date.month()).into(), - Var::MonthWeek => (date.day0().div_euclid(7) + 1).into(), - Var::MonthWeekReverse => { + Self::Month => date.month().into(), + Self::MonthLength => util::month_length(date.year(), date.month()).into(), + Self::MonthWeek => (date.day0().div_euclid(7) + 1).into(), + Self::MonthWeekReverse => { #[allow(non_snake_case)] let mD = util::month_length(date.year(), date.month()) - date.day(); (mD.div_euclid(7) + 1).into() } - Var::Day => date.day().into(), - Var::DayReverse => { + Self::Day => date.day().into(), + Self::DayReverse => { let ml = util::month_length(date.year(), date.month()); (ml - date.day0()).into() } - Var::IsoYear => date.iso_week().year().into(), - Var::IsoYearLength => util::iso_year_length(date.iso_week().year()).into(), - Var::IsoWeek => date.iso_week().week().into(), - Var::Weekday => { + Self::IsoYear => date.iso_week().year().into(), + Self::IsoYearLength => util::iso_year_length(date.iso_week().year()).into(), + Self::IsoWeek => date.iso_week().week().into(), + Self::Weekday => { let wd: Weekday = date.weekday().into(); wd.num().into() } - Var::Easter(span) => { + Self::Easter(span) => { let e = computus::gregorian(date.year()).map_err(|e| Error::Easter { index, span, date, msg: e, })?; - NaiveDate::from_ymd(e.year, e.month, e.day).ordinal().into() + NaiveDate::from_ymd_opt(e.year, e.month, e.day) + .unwrap() + .ordinal() + .into() } - Var::IsWeekday => { + Self::IsWeekday => { let wd: Weekday = date.weekday().into(); b2i(!wd.is_weekend()) } - Var::IsWeekend => { + Self::IsWeekend => { let wd: Weekday = date.weekday().into(); b2i(wd.is_weekend()) } - Var::IsLeapYear => b2i(util::is_leap_year(date.year())), - Var::IsIsoLeapYear => b2i(util::is_iso_leap_year(date.year())), + Self::IsLeapYear => b2i(util::is_leap_year(date.year())), + Self::IsIsoLeapYear => b2i(util::is_iso_leap_year(date.year())), }) } } @@ -202,15 +207,15 @@ impl From for Expr { } impl Expr { - fn eval(&self, index: usize, date: NaiveDate) -> Result { + fn eval(&self, index: S, date: NaiveDate) -> Result> { Ok(match self { - Expr::Lit(l) => *l, - Expr::Var(v) => v.eval(index, date)?, - Expr::Neg(e) => -e.eval(index, date)?, - Expr::Add(a, b) => a.eval(index, date)? + b.eval(index, date)?, - Expr::Sub(a, b) => a.eval(index, date)? - b.eval(index, date)?, - Expr::Mul(a, b) => a.eval(index, date)? * b.eval(index, date)?, - Expr::Div(a, b, span) => { + Self::Lit(l) => *l, + Self::Var(v) => v.eval(index, date)?, + Self::Neg(e) => -e.eval(index, date)?, + Self::Add(a, b) => a.eval(index, date)? + b.eval(index, date)?, + Self::Sub(a, b) => a.eval(index, date)? - b.eval(index, date)?, + Self::Mul(a, b) => a.eval(index, date)? * b.eval(index, date)?, + Self::Div(a, b, span) => { let b = b.eval(index, date)?; if b == 0 { return Err(Error::DivByZero { @@ -221,7 +226,7 @@ impl Expr { } a.eval(index, date)?.div_euclid(b) } - Expr::Mod(a, b, span) => { + Self::Mod(a, b, span) => { let b = b.eval(index, date)?; if b == 0 { return Err(Error::ModByZero { @@ -232,16 +237,16 @@ impl Expr { } a.eval(index, date)?.rem_euclid(b) } - Expr::Eq(a, b) => b2i(a.eval(index, date)? == b.eval(index, date)?), - Expr::Neq(a, b) => b2i(a.eval(index, date)? != b.eval(index, date)?), - Expr::Lt(a, b) => b2i(a.eval(index, date)? < b.eval(index, date)?), - Expr::Lte(a, b) => b2i(a.eval(index, date)? <= b.eval(index, date)?), - Expr::Gt(a, b) => b2i(a.eval(index, date)? > b.eval(index, date)?), - Expr::Gte(a, b) => b2i(a.eval(index, date)? >= b.eval(index, date)?), - Expr::Not(e) => b2i(!i2b(e.eval(index, date)?)), - Expr::And(a, b) => b2i(i2b(a.eval(index, date)?) && i2b(b.eval(index, date)?)), - Expr::Or(a, b) => b2i(i2b(a.eval(index, date)?) || i2b(b.eval(index, date)?)), - Expr::Xor(a, b) => b2i(i2b(a.eval(index, date)?) ^ i2b(b.eval(index, date)?)), + Self::Eq(a, b) => b2i(a.eval(index, date)? == b.eval(index, date)?), + Self::Neq(a, b) => b2i(a.eval(index, date)? != b.eval(index, date)?), + Self::Lt(a, b) => b2i(a.eval(index, date)? < b.eval(index, date)?), + Self::Lte(a, b) => b2i(a.eval(index, date)? <= b.eval(index, date)?), + Self::Gt(a, b) => b2i(a.eval(index, date)? > b.eval(index, date)?), + Self::Gte(a, b) => b2i(a.eval(index, date)? >= b.eval(index, date)?), + Self::Not(e) => b2i(!i2b(e.eval(index, date)?)), + Self::And(a, b) => b2i(i2b(a.eval(index, date)?) && i2b(b.eval(index, date)?)), + Self::Or(a, b) => b2i(i2b(a.eval(index, date)?) || i2b(b.eval(index, date)?)), + Self::Xor(a, b) => b2i(i2b(a.eval(index, date)?) ^ i2b(b.eval(index, date)?)), }) } } @@ -324,16 +329,16 @@ impl From<&commands::WeekdaySpec> for FormulaSpec { impl FormulaSpec { fn range(&self, s: &CommandState<'_>) -> Option { let mut range = s - .range + .range_with_remind() .expand_by(&self.end_delta) .move_by(&self.start_delta); - if let Command::Task(_) = s.command.command { - if let Some(last_done_root) = s.last_done_root() { - range = range.with_from(last_done_root.succ())?; + if let EvalCommand::Task(_) = s.command { + if let Some(last_done_root) = s.command.last_done_root() { + range = range.with_from(last_done_root.succ_opt().unwrap())?; } else if let Some(from) = s.from { range = range.with_from(from)?; - } else if matches!(s.command.command, Command::Task(_)) { + } else if matches!(s.command, EvalCommand::Task(_)) { // We have no idea if we missed any tasks since the user hasn't // specified a `FROM`, so we just just look back one year. Any // task older than a year is probably not important anyways... @@ -344,7 +349,7 @@ impl FormulaSpec { s.limit_from_until(range) } - fn dates(&self, index: usize, start: NaiveDate) -> Result { + fn dates(&self, index: FileSource, start: NaiveDate) -> Result> { let root = self.start_delta.apply_date(index, start)?; Ok(if let Some(root_time) = self.start_time { let (other, other_time) = self.end_delta.apply_date_time(index, root, root_time)?; @@ -355,19 +360,19 @@ impl FormulaSpec { }) } - fn eval(&self, index: usize, date: NaiveDate) -> Result { + fn eval(&self, index: FileSource, date: NaiveDate) -> Result> { Ok(i2b(self.start.eval(index, date)?)) } } -impl<'a> CommandState<'a> { - pub fn eval_formula_spec(&mut self, spec: FormulaSpec) -> Result<()> { +impl CommandState<'_> { + pub fn eval_formula_spec(&mut self, spec: FormulaSpec) -> Result<(), Error> { if let Some(range) = spec.range(self) { - let index = self.command.source.file(); + let index = self.source.file(); for day in range.days() { if spec.eval(index, day)? { let dates = spec.dates(index, day)?; - self.add(self.kind(), Some(dates)); + self.add(self.entry_with_remind(self.command.kind(), Some(dates))?); } } } @@ -386,21 +391,31 @@ mod tests { use super::{Expr, Var}; fn expr(expr: &Expr, date: NaiveDate, target: i64) { - if let Ok(result) = expr.eval(0, date) { + if let Ok(result) = expr.eval((), date) { assert_eq!(result, target); } else { - panic!("formula produced error for day {}", date); + panic!("formula produced error for day {date}"); } } + fn expr_ymd(e: &Expr, ymd: (i32, u32, u32), target: i64) { + let (y, m, d) = ymd; + expr(e, NaiveDate::from_ymd_opt(y, m, d).unwrap(), target); + } + + fn expr_yo(e: &Expr, yo: (i32, u32), target: i64) { + let (y, o) = yo; + expr(e, NaiveDate::from_yo_opt(y, o).unwrap(), target); + } + #[test] fn julian_day() { let e = Expr::Var(Var::JulianDay); for delta in -1000..1000 { - let d1 = NaiveDate::from_ymd(2021, 12, 19); + let d1 = NaiveDate::from_ymd_opt(2021, 12, 19).unwrap(); let d2 = d1 + Duration::days(delta); - assert_eq!(e.eval(0, d2).unwrap() - e.eval(0, d1).unwrap(), delta); + assert_eq!(e.eval((), d2).unwrap() - e.eval((), d1).unwrap(), delta); } } @@ -409,21 +424,21 @@ mod tests { let e = Expr::Var(Var::Year); for y in -3000..=3000 { - expr(&e, NaiveDate::from_ymd(y, 2, 19), y.into()); + expr_ymd(&e, (y, 2, 19), y.into()); } - expr(&e, NaiveDate::from_ymd(2021, 1, 1), 2021); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 2021); + expr_ymd(&e, (2021, 1, 1), 2021); + expr_ymd(&e, (2021, 12, 31), 2021); } #[test] fn year_length() { let e = Expr::Var(Var::YearLength); - expr(&e, NaiveDate::from_ymd(2000, 12, 19), 366); - expr(&e, NaiveDate::from_ymd(2019, 12, 19), 365); - expr(&e, NaiveDate::from_ymd(2020, 12, 19), 366); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 365); + expr_ymd(&e, (2000, 12, 19), 366); + expr_ymd(&e, (2019, 12, 19), 365); + expr_ymd(&e, (2020, 12, 19), 366); + expr_ymd(&e, (2021, 12, 19), 365); } #[test] @@ -431,11 +446,11 @@ mod tests { let e = Expr::Var(Var::YearDay); for i in 1..=365 { - expr(&e, NaiveDate::from_yo(2020, i), i.into()); - expr(&e, NaiveDate::from_yo(2021, i), i.into()); + expr_yo(&e, (2020, i), i.into()); + expr_yo(&e, (2021, i), i.into()); } - expr(&e, NaiveDate::from_yo(2020, 366), 366); + expr_yo(&e, (2020, 366), 366); } #[test] @@ -443,14 +458,14 @@ mod tests { let e = Expr::Var(Var::YearDayReverse); for i in 1..=365 { - expr(&e, NaiveDate::from_yo(2020, i), (366 - i + 1).into()); - expr(&e, NaiveDate::from_yo(2021, i), (365 - i + 1).into()); + expr_yo(&e, (2020, i), (366 - i + 1).into()); + expr_yo(&e, (2021, i), (365 - i + 1).into()); } - expr(&e, NaiveDate::from_ymd(2020, 1, 1), 366); - expr(&e, NaiveDate::from_ymd(2021, 1, 1), 365); - expr(&e, NaiveDate::from_ymd(2020, 12, 31), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 1); + expr_ymd(&e, (2020, 1, 1), 366); + expr_ymd(&e, (2021, 1, 1), 365); + expr_ymd(&e, (2020, 12, 31), 1); + expr_ymd(&e, (2021, 12, 31), 1); } #[test] @@ -458,32 +473,32 @@ mod tests { let e = Expr::Var(Var::YearWeek); for y in 1000..3000 { - expr(&e, NaiveDate::from_ymd(y, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 2), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 3), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 4), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 5), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 6), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 7), 1); - expr(&e, NaiveDate::from_ymd(y, 1, 8), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 9), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 10), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 11), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 12), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 13), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 14), 2); - expr(&e, NaiveDate::from_ymd(y, 1, 15), 3); + expr_ymd(&e, (y, 1, 1), 1); + expr_ymd(&e, (y, 1, 2), 1); + expr_ymd(&e, (y, 1, 3), 1); + expr_ymd(&e, (y, 1, 4), 1); + expr_ymd(&e, (y, 1, 5), 1); + expr_ymd(&e, (y, 1, 6), 1); + expr_ymd(&e, (y, 1, 7), 1); + expr_ymd(&e, (y, 1, 8), 2); + expr_ymd(&e, (y, 1, 9), 2); + expr_ymd(&e, (y, 1, 10), 2); + expr_ymd(&e, (y, 1, 11), 2); + expr_ymd(&e, (y, 1, 12), 2); + expr_ymd(&e, (y, 1, 13), 2); + expr_ymd(&e, (y, 1, 14), 2); + expr_ymd(&e, (y, 1, 15), 3); } - expr(&e, NaiveDate::from_ymd(2020, 12, 28), 52); - expr(&e, NaiveDate::from_ymd(2020, 12, 29), 52); - expr(&e, NaiveDate::from_ymd(2020, 12, 30), 53); - expr(&e, NaiveDate::from_ymd(2020, 12, 31), 53); + expr_ymd(&e, (2020, 12, 28), 52); + expr_ymd(&e, (2020, 12, 29), 52); + expr_ymd(&e, (2020, 12, 30), 53); + expr_ymd(&e, (2020, 12, 31), 53); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 52); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 52); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 52); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 53); + expr_ymd(&e, (2021, 12, 28), 52); + expr_ymd(&e, (2021, 12, 29), 52); + expr_ymd(&e, (2021, 12, 30), 52); + expr_ymd(&e, (2021, 12, 31), 53); } #[test] @@ -491,32 +506,32 @@ mod tests { let e = Expr::Var(Var::YearWeekReverse); for y in 1000..3000 { - expr(&e, NaiveDate::from_ymd(y, 12, 31), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 30), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 29), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 28), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 27), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 26), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 25), 1); - expr(&e, NaiveDate::from_ymd(y, 12, 24), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 23), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 22), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 21), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 20), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 19), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 18), 2); - expr(&e, NaiveDate::from_ymd(y, 12, 17), 3); + expr_ymd(&e, (y, 12, 31), 1); + expr_ymd(&e, (y, 12, 30), 1); + expr_ymd(&e, (y, 12, 29), 1); + expr_ymd(&e, (y, 12, 28), 1); + expr_ymd(&e, (y, 12, 27), 1); + expr_ymd(&e, (y, 12, 26), 1); + expr_ymd(&e, (y, 12, 25), 1); + expr_ymd(&e, (y, 12, 24), 2); + expr_ymd(&e, (y, 12, 23), 2); + expr_ymd(&e, (y, 12, 22), 2); + expr_ymd(&e, (y, 12, 21), 2); + expr_ymd(&e, (y, 12, 20), 2); + expr_ymd(&e, (y, 12, 19), 2); + expr_ymd(&e, (y, 12, 18), 2); + expr_ymd(&e, (y, 12, 17), 3); } - expr(&e, NaiveDate::from_ymd(2020, 1, 1), 53); - expr(&e, NaiveDate::from_ymd(2020, 1, 2), 53); - expr(&e, NaiveDate::from_ymd(2020, 1, 3), 52); - expr(&e, NaiveDate::from_ymd(2020, 1, 4), 52); + expr_ymd(&e, (2020, 1, 1), 53); + expr_ymd(&e, (2020, 1, 2), 53); + expr_ymd(&e, (2020, 1, 3), 52); + expr_ymd(&e, (2020, 1, 4), 52); - expr(&e, NaiveDate::from_ymd(2021, 1, 1), 53); - expr(&e, NaiveDate::from_ymd(2021, 1, 2), 52); - expr(&e, NaiveDate::from_ymd(2021, 1, 3), 52); - expr(&e, NaiveDate::from_ymd(2021, 1, 4), 52); + expr_ymd(&e, (2021, 1, 1), 53); + expr_ymd(&e, (2021, 1, 2), 52); + expr_ymd(&e, (2021, 1, 3), 52); + expr_ymd(&e, (2021, 1, 4), 52); } #[test] @@ -524,7 +539,7 @@ mod tests { let e = Expr::Var(Var::Month); for y in -1000..=3000 { for m in 1..=12 { - expr(&e, NaiveDate::from_ymd(y, m, 13), m.into()); + expr_ymd(&e, (y, m, 13), m.into()); } } } @@ -533,96 +548,96 @@ mod tests { fn month_length() { let e = Expr::Var(Var::MonthLength); - expr(&e, NaiveDate::from_ymd(2021, 1, 5), 31); - expr(&e, NaiveDate::from_ymd(2021, 2, 5), 28); - expr(&e, NaiveDate::from_ymd(2021, 3, 5), 31); - expr(&e, NaiveDate::from_ymd(2021, 4, 5), 30); - expr(&e, NaiveDate::from_ymd(2021, 5, 5), 31); - expr(&e, NaiveDate::from_ymd(2021, 6, 5), 30); - expr(&e, NaiveDate::from_ymd(2021, 7, 5), 31); - expr(&e, NaiveDate::from_ymd(2021, 8, 5), 31); - expr(&e, NaiveDate::from_ymd(2021, 9, 5), 30); - expr(&e, NaiveDate::from_ymd(2021, 10, 5), 31); - expr(&e, NaiveDate::from_ymd(2021, 11, 5), 30); - expr(&e, NaiveDate::from_ymd(2021, 12, 5), 31); + expr_ymd(&e, (2021, 1, 5), 31); + expr_ymd(&e, (2021, 2, 5), 28); + expr_ymd(&e, (2021, 3, 5), 31); + expr_ymd(&e, (2021, 4, 5), 30); + expr_ymd(&e, (2021, 5, 5), 31); + expr_ymd(&e, (2021, 6, 5), 30); + expr_ymd(&e, (2021, 7, 5), 31); + expr_ymd(&e, (2021, 8, 5), 31); + expr_ymd(&e, (2021, 9, 5), 30); + expr_ymd(&e, (2021, 10, 5), 31); + expr_ymd(&e, (2021, 11, 5), 30); + expr_ymd(&e, (2021, 12, 5), 31); - expr(&e, NaiveDate::from_ymd(2020, 2, 5), 29); - expr(&e, NaiveDate::from_ymd(2019, 2, 5), 28); - expr(&e, NaiveDate::from_ymd(2000, 2, 5), 29); + expr_ymd(&e, (2020, 2, 5), 29); + expr_ymd(&e, (2019, 2, 5), 28); + expr_ymd(&e, (2000, 2, 5), 29); } #[test] fn month_week() { let e = Expr::Var(Var::MonthWeek); - expr(&e, NaiveDate::from_ymd(2021, 12, 1), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 2), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 3), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 4), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 5), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 6), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 7), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 8), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 9), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 10), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 11), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 12), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 13), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 14), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 15), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 16), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 17), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 18), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 20), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 21), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 22), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 23), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 24), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 25), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 26), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 27), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 5); + expr_ymd(&e, (2021, 12, 1), 1); + expr_ymd(&e, (2021, 12, 2), 1); + expr_ymd(&e, (2021, 12, 3), 1); + expr_ymd(&e, (2021, 12, 4), 1); + expr_ymd(&e, (2021, 12, 5), 1); + expr_ymd(&e, (2021, 12, 6), 1); + expr_ymd(&e, (2021, 12, 7), 1); + expr_ymd(&e, (2021, 12, 8), 2); + expr_ymd(&e, (2021, 12, 9), 2); + expr_ymd(&e, (2021, 12, 10), 2); + expr_ymd(&e, (2021, 12, 11), 2); + expr_ymd(&e, (2021, 12, 12), 2); + expr_ymd(&e, (2021, 12, 13), 2); + expr_ymd(&e, (2021, 12, 14), 2); + expr_ymd(&e, (2021, 12, 15), 3); + expr_ymd(&e, (2021, 12, 16), 3); + expr_ymd(&e, (2021, 12, 17), 3); + expr_ymd(&e, (2021, 12, 18), 3); + expr_ymd(&e, (2021, 12, 19), 3); + expr_ymd(&e, (2021, 12, 20), 3); + expr_ymd(&e, (2021, 12, 21), 3); + expr_ymd(&e, (2021, 12, 22), 4); + expr_ymd(&e, (2021, 12, 23), 4); + expr_ymd(&e, (2021, 12, 24), 4); + expr_ymd(&e, (2021, 12, 25), 4); + expr_ymd(&e, (2021, 12, 26), 4); + expr_ymd(&e, (2021, 12, 27), 4); + expr_ymd(&e, (2021, 12, 28), 4); + expr_ymd(&e, (2021, 12, 29), 5); + expr_ymd(&e, (2021, 12, 30), 5); + expr_ymd(&e, (2021, 12, 31), 5); } #[test] fn month_week_reverse() { let e = Expr::Var(Var::MonthWeekReverse); - expr(&e, NaiveDate::from_ymd(2021, 12, 1), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 2), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 3), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 4), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 5), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 6), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 7), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 8), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 9), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 10), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 11), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 12), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 13), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 14), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 15), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 16), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 17), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 18), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 20), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 21), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 22), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 23), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 24), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 25), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 26), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 27), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 1); + expr_ymd(&e, (2021, 12, 1), 5); + expr_ymd(&e, (2021, 12, 2), 5); + expr_ymd(&e, (2021, 12, 3), 5); + expr_ymd(&e, (2021, 12, 4), 4); + expr_ymd(&e, (2021, 12, 5), 4); + expr_ymd(&e, (2021, 12, 6), 4); + expr_ymd(&e, (2021, 12, 7), 4); + expr_ymd(&e, (2021, 12, 8), 4); + expr_ymd(&e, (2021, 12, 9), 4); + expr_ymd(&e, (2021, 12, 10), 4); + expr_ymd(&e, (2021, 12, 11), 3); + expr_ymd(&e, (2021, 12, 12), 3); + expr_ymd(&e, (2021, 12, 13), 3); + expr_ymd(&e, (2021, 12, 14), 3); + expr_ymd(&e, (2021, 12, 15), 3); + expr_ymd(&e, (2021, 12, 16), 3); + expr_ymd(&e, (2021, 12, 17), 3); + expr_ymd(&e, (2021, 12, 18), 2); + expr_ymd(&e, (2021, 12, 19), 2); + expr_ymd(&e, (2021, 12, 20), 2); + expr_ymd(&e, (2021, 12, 21), 2); + expr_ymd(&e, (2021, 12, 22), 2); + expr_ymd(&e, (2021, 12, 23), 2); + expr_ymd(&e, (2021, 12, 24), 2); + expr_ymd(&e, (2021, 12, 25), 1); + expr_ymd(&e, (2021, 12, 26), 1); + expr_ymd(&e, (2021, 12, 27), 1); + expr_ymd(&e, (2021, 12, 28), 1); + expr_ymd(&e, (2021, 12, 29), 1); + expr_ymd(&e, (2021, 12, 30), 1); + expr_ymd(&e, (2021, 12, 31), 1); } #[test] @@ -630,78 +645,78 @@ mod tests { let e = Expr::Var(Var::Day); for d in 1..=31 { - expr(&e, NaiveDate::from_ymd(2020, 1, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 3, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 5, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 7, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 8, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 10, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 12, d), d.into()); + expr_ymd(&e, (2020, 1, d), d.into()); + expr_ymd(&e, (2020, 3, d), d.into()); + expr_ymd(&e, (2020, 5, d), d.into()); + expr_ymd(&e, (2020, 7, d), d.into()); + expr_ymd(&e, (2020, 8, d), d.into()); + expr_ymd(&e, (2020, 10, d), d.into()); + expr_ymd(&e, (2020, 12, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 1, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 3, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 5, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 7, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 8, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 10, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 12, d), d.into()); + expr_ymd(&e, (2021, 1, d), d.into()); + expr_ymd(&e, (2021, 3, d), d.into()); + expr_ymd(&e, (2021, 5, d), d.into()); + expr_ymd(&e, (2021, 7, d), d.into()); + expr_ymd(&e, (2021, 8, d), d.into()); + expr_ymd(&e, (2021, 10, d), d.into()); + expr_ymd(&e, (2021, 12, d), d.into()); } for d in 1..=30 { - expr(&e, NaiveDate::from_ymd(2020, 4, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 6, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 9, d), d.into()); - expr(&e, NaiveDate::from_ymd(2020, 11, d), d.into()); + expr_ymd(&e, (2020, 4, d), d.into()); + expr_ymd(&e, (2020, 6, d), d.into()); + expr_ymd(&e, (2020, 9, d), d.into()); + expr_ymd(&e, (2020, 11, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 4, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 6, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 9, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 11, d), d.into()); + expr_ymd(&e, (2021, 4, d), d.into()); + expr_ymd(&e, (2021, 6, d), d.into()); + expr_ymd(&e, (2021, 9, d), d.into()); + expr_ymd(&e, (2021, 11, d), d.into()); } for d in 1..=28 { - expr(&e, NaiveDate::from_ymd(2020, 2, d), d.into()); - expr(&e, NaiveDate::from_ymd(2021, 2, d), d.into()); + expr_ymd(&e, (2020, 2, d), d.into()); + expr_ymd(&e, (2021, 2, d), d.into()); } - expr(&e, NaiveDate::from_ymd(2020, 2, 29), 29); + expr_ymd(&e, (2020, 2, 29), 29); } #[test] fn day_reverse() { let e = Expr::Var(Var::DayReverse); - expr(&e, NaiveDate::from_ymd(2021, 12, 1), 31); - expr(&e, NaiveDate::from_ymd(2021, 12, 2), 30); - expr(&e, NaiveDate::from_ymd(2021, 12, 3), 29); - expr(&e, NaiveDate::from_ymd(2021, 12, 4), 28); - expr(&e, NaiveDate::from_ymd(2021, 12, 5), 27); - expr(&e, NaiveDate::from_ymd(2021, 12, 6), 26); - expr(&e, NaiveDate::from_ymd(2021, 12, 7), 25); - expr(&e, NaiveDate::from_ymd(2021, 12, 8), 24); - expr(&e, NaiveDate::from_ymd(2021, 12, 9), 23); - expr(&e, NaiveDate::from_ymd(2021, 12, 10), 22); - expr(&e, NaiveDate::from_ymd(2021, 12, 11), 21); - expr(&e, NaiveDate::from_ymd(2021, 12, 12), 20); - expr(&e, NaiveDate::from_ymd(2021, 12, 13), 19); - expr(&e, NaiveDate::from_ymd(2021, 12, 14), 18); - expr(&e, NaiveDate::from_ymd(2021, 12, 15), 17); - expr(&e, NaiveDate::from_ymd(2021, 12, 16), 16); - expr(&e, NaiveDate::from_ymd(2021, 12, 17), 15); - expr(&e, NaiveDate::from_ymd(2021, 12, 18), 14); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 13); - expr(&e, NaiveDate::from_ymd(2021, 12, 20), 12); - expr(&e, NaiveDate::from_ymd(2021, 12, 21), 11); - expr(&e, NaiveDate::from_ymd(2021, 12, 22), 10); - expr(&e, NaiveDate::from_ymd(2021, 12, 23), 9); - expr(&e, NaiveDate::from_ymd(2021, 12, 24), 8); - expr(&e, NaiveDate::from_ymd(2021, 12, 25), 7); - expr(&e, NaiveDate::from_ymd(2021, 12, 26), 6); - expr(&e, NaiveDate::from_ymd(2021, 12, 27), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 1); + expr_ymd(&e, (2021, 12, 1), 31); + expr_ymd(&e, (2021, 12, 2), 30); + expr_ymd(&e, (2021, 12, 3), 29); + expr_ymd(&e, (2021, 12, 4), 28); + expr_ymd(&e, (2021, 12, 5), 27); + expr_ymd(&e, (2021, 12, 6), 26); + expr_ymd(&e, (2021, 12, 7), 25); + expr_ymd(&e, (2021, 12, 8), 24); + expr_ymd(&e, (2021, 12, 9), 23); + expr_ymd(&e, (2021, 12, 10), 22); + expr_ymd(&e, (2021, 12, 11), 21); + expr_ymd(&e, (2021, 12, 12), 20); + expr_ymd(&e, (2021, 12, 13), 19); + expr_ymd(&e, (2021, 12, 14), 18); + expr_ymd(&e, (2021, 12, 15), 17); + expr_ymd(&e, (2021, 12, 16), 16); + expr_ymd(&e, (2021, 12, 17), 15); + expr_ymd(&e, (2021, 12, 18), 14); + expr_ymd(&e, (2021, 12, 19), 13); + expr_ymd(&e, (2021, 12, 20), 12); + expr_ymd(&e, (2021, 12, 21), 11); + expr_ymd(&e, (2021, 12, 22), 10); + expr_ymd(&e, (2021, 12, 23), 9); + expr_ymd(&e, (2021, 12, 24), 8); + expr_ymd(&e, (2021, 12, 25), 7); + expr_ymd(&e, (2021, 12, 26), 6); + expr_ymd(&e, (2021, 12, 27), 5); + expr_ymd(&e, (2021, 12, 28), 4); + expr_ymd(&e, (2021, 12, 29), 3); + expr_ymd(&e, (2021, 12, 30), 2); + expr_ymd(&e, (2021, 12, 31), 1); } #[test] @@ -710,32 +725,32 @@ mod tests { // From https://en.wikipedia.org/wiki/ISO_week_date - expr(&e, NaiveDate::from_ymd(1977, 1, 1), 1976); - expr(&e, NaiveDate::from_ymd(1977, 1, 2), 1976); - expr(&e, NaiveDate::from_ymd(1977, 1, 3), 1977); + expr_ymd(&e, (1977, 1, 1), 1976); + expr_ymd(&e, (1977, 1, 2), 1976); + expr_ymd(&e, (1977, 1, 3), 1977); - expr(&e, NaiveDate::from_ymd(1977, 12, 31), 1977); - expr(&e, NaiveDate::from_ymd(1978, 1, 1), 1977); - expr(&e, NaiveDate::from_ymd(1978, 1, 2), 1978); + expr_ymd(&e, (1977, 12, 31), 1977); + expr_ymd(&e, (1978, 1, 1), 1977); + expr_ymd(&e, (1978, 1, 2), 1978); - expr(&e, NaiveDate::from_ymd(1978, 12, 31), 1978); - expr(&e, NaiveDate::from_ymd(1979, 1, 1), 1979); + expr_ymd(&e, (1978, 12, 31), 1978); + expr_ymd(&e, (1979, 1, 1), 1979); - expr(&e, NaiveDate::from_ymd(1979, 12, 30), 1979); - expr(&e, NaiveDate::from_ymd(1979, 12, 31), 1980); - expr(&e, NaiveDate::from_ymd(1980, 1, 1), 1980); + expr_ymd(&e, (1979, 12, 30), 1979); + expr_ymd(&e, (1979, 12, 31), 1980); + expr_ymd(&e, (1980, 1, 1), 1980); - expr(&e, NaiveDate::from_ymd(1980, 12, 28), 1980); - expr(&e, NaiveDate::from_ymd(1980, 12, 29), 1981); - expr(&e, NaiveDate::from_ymd(1980, 12, 30), 1981); - expr(&e, NaiveDate::from_ymd(1980, 12, 31), 1981); - expr(&e, NaiveDate::from_ymd(1981, 1, 1), 1981); + expr_ymd(&e, (1980, 12, 28), 1980); + expr_ymd(&e, (1980, 12, 29), 1981); + expr_ymd(&e, (1980, 12, 30), 1981); + expr_ymd(&e, (1980, 12, 31), 1981); + expr_ymd(&e, (1981, 1, 1), 1981); - expr(&e, NaiveDate::from_ymd(1981, 12, 31), 1981); - expr(&e, NaiveDate::from_ymd(1982, 1, 1), 1981); - expr(&e, NaiveDate::from_ymd(1982, 1, 2), 1981); - expr(&e, NaiveDate::from_ymd(1982, 1, 3), 1981); - expr(&e, NaiveDate::from_ymd(1982, 1, 4), 1982); + expr_ymd(&e, (1981, 12, 31), 1981); + expr_ymd(&e, (1982, 1, 1), 1981); + expr_ymd(&e, (1982, 1, 2), 1981); + expr_ymd(&e, (1982, 1, 3), 1981); + expr_ymd(&e, (1982, 1, 4), 1982); } #[test] @@ -743,37 +758,37 @@ mod tests { let e = Expr::Var(Var::IsoYearLength); // August 1st is definitely in the same year in both systems - expr(&e, NaiveDate::from_ymd(2000, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2001, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2002, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2003, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2004, 8, 1), 52 * 7 + 7); - expr(&e, NaiveDate::from_ymd(2005, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2006, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2007, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2008, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2009, 8, 1), 52 * 7 + 7); - expr(&e, NaiveDate::from_ymd(2010, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2011, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2012, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2013, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2014, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2015, 8, 1), 52 * 7 + 7); - expr(&e, NaiveDate::from_ymd(2016, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2017, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2018, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2019, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2020, 8, 1), 52 * 7 + 7); - expr(&e, NaiveDate::from_ymd(2021, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2022, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2023, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2024, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2025, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2026, 8, 1), 52 * 7 + 7); - expr(&e, NaiveDate::from_ymd(2027, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2028, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2029, 8, 1), 52 * 7); - expr(&e, NaiveDate::from_ymd(2030, 8, 1), 52 * 7); + expr_ymd(&e, (2000, 8, 1), 52 * 7); + expr_ymd(&e, (2001, 8, 1), 52 * 7); + expr_ymd(&e, (2002, 8, 1), 52 * 7); + expr_ymd(&e, (2003, 8, 1), 52 * 7); + expr_ymd(&e, (2004, 8, 1), 52 * 7 + 7); + expr_ymd(&e, (2005, 8, 1), 52 * 7); + expr_ymd(&e, (2006, 8, 1), 52 * 7); + expr_ymd(&e, (2007, 8, 1), 52 * 7); + expr_ymd(&e, (2008, 8, 1), 52 * 7); + expr_ymd(&e, (2009, 8, 1), 52 * 7 + 7); + expr_ymd(&e, (2010, 8, 1), 52 * 7); + expr_ymd(&e, (2011, 8, 1), 52 * 7); + expr_ymd(&e, (2012, 8, 1), 52 * 7); + expr_ymd(&e, (2013, 8, 1), 52 * 7); + expr_ymd(&e, (2014, 8, 1), 52 * 7); + expr_ymd(&e, (2015, 8, 1), 52 * 7 + 7); + expr_ymd(&e, (2016, 8, 1), 52 * 7); + expr_ymd(&e, (2017, 8, 1), 52 * 7); + expr_ymd(&e, (2018, 8, 1), 52 * 7); + expr_ymd(&e, (2019, 8, 1), 52 * 7); + expr_ymd(&e, (2020, 8, 1), 52 * 7 + 7); + expr_ymd(&e, (2021, 8, 1), 52 * 7); + expr_ymd(&e, (2022, 8, 1), 52 * 7); + expr_ymd(&e, (2023, 8, 1), 52 * 7); + expr_ymd(&e, (2024, 8, 1), 52 * 7); + expr_ymd(&e, (2025, 8, 1), 52 * 7); + expr_ymd(&e, (2026, 8, 1), 52 * 7 + 7); + expr_ymd(&e, (2027, 8, 1), 52 * 7); + expr_ymd(&e, (2028, 8, 1), 52 * 7); + expr_ymd(&e, (2029, 8, 1), 52 * 7); + expr_ymd(&e, (2030, 8, 1), 52 * 7); } #[test] @@ -782,52 +797,52 @@ mod tests { // From https://en.wikipedia.org/wiki/ISO_week_date - expr(&e, NaiveDate::from_ymd(1977, 1, 1), 53); - expr(&e, NaiveDate::from_ymd(1977, 1, 2), 53); - expr(&e, NaiveDate::from_ymd(1977, 1, 3), 1); + expr_ymd(&e, (1977, 1, 1), 53); + expr_ymd(&e, (1977, 1, 2), 53); + expr_ymd(&e, (1977, 1, 3), 1); - expr(&e, NaiveDate::from_ymd(1977, 12, 31), 52); - expr(&e, NaiveDate::from_ymd(1978, 1, 1), 52); - expr(&e, NaiveDate::from_ymd(1978, 1, 2), 1); + expr_ymd(&e, (1977, 12, 31), 52); + expr_ymd(&e, (1978, 1, 1), 52); + expr_ymd(&e, (1978, 1, 2), 1); - expr(&e, NaiveDate::from_ymd(1978, 12, 31), 52); - expr(&e, NaiveDate::from_ymd(1979, 1, 1), 1); + expr_ymd(&e, (1978, 12, 31), 52); + expr_ymd(&e, (1979, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(1979, 12, 30), 52); - expr(&e, NaiveDate::from_ymd(1979, 12, 31), 1); - expr(&e, NaiveDate::from_ymd(1980, 1, 1), 1); + expr_ymd(&e, (1979, 12, 30), 52); + expr_ymd(&e, (1979, 12, 31), 1); + expr_ymd(&e, (1980, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(1980, 12, 28), 52); - expr(&e, NaiveDate::from_ymd(1980, 12, 29), 1); - expr(&e, NaiveDate::from_ymd(1980, 12, 30), 1); - expr(&e, NaiveDate::from_ymd(1980, 12, 31), 1); - expr(&e, NaiveDate::from_ymd(1981, 1, 1), 1); + expr_ymd(&e, (1980, 12, 28), 52); + expr_ymd(&e, (1980, 12, 29), 1); + expr_ymd(&e, (1980, 12, 30), 1); + expr_ymd(&e, (1980, 12, 31), 1); + expr_ymd(&e, (1981, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(1981, 12, 31), 53); - expr(&e, NaiveDate::from_ymd(1982, 1, 1), 53); - expr(&e, NaiveDate::from_ymd(1982, 1, 2), 53); - expr(&e, NaiveDate::from_ymd(1982, 1, 3), 53); - expr(&e, NaiveDate::from_ymd(1982, 1, 4), 1); + expr_ymd(&e, (1981, 12, 31), 53); + expr_ymd(&e, (1982, 1, 1), 53); + expr_ymd(&e, (1982, 1, 2), 53); + expr_ymd(&e, (1982, 1, 3), 53); + expr_ymd(&e, (1982, 1, 4), 1); } #[test] fn weekday() { let e = Expr::Var(Var::Weekday); - expr(&e, NaiveDate::from_ymd(2021, 12, 18), 6); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 7); - expr(&e, NaiveDate::from_ymd(2021, 12, 20), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 21), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 22), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 23), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 24), 5); - expr(&e, NaiveDate::from_ymd(2021, 12, 25), 6); - expr(&e, NaiveDate::from_ymd(2021, 12, 26), 7); - expr(&e, NaiveDate::from_ymd(2021, 12, 27), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 2); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 3); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 4); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 5); + expr_ymd(&e, (2021, 12, 18), 6); + expr_ymd(&e, (2021, 12, 19), 7); + expr_ymd(&e, (2021, 12, 20), 1); + expr_ymd(&e, (2021, 12, 21), 2); + expr_ymd(&e, (2021, 12, 22), 3); + expr_ymd(&e, (2021, 12, 23), 4); + expr_ymd(&e, (2021, 12, 24), 5); + expr_ymd(&e, (2021, 12, 25), 6); + expr_ymd(&e, (2021, 12, 26), 7); + expr_ymd(&e, (2021, 12, 27), 1); + expr_ymd(&e, (2021, 12, 28), 2); + expr_ymd(&e, (2021, 12, 29), 3); + expr_ymd(&e, (2021, 12, 30), 4); + expr_ymd(&e, (2021, 12, 31), 5); } #[test] @@ -887,10 +902,10 @@ mod tests { ]; for (y, m, d) in dates { - expr( + expr_ymd( &e, - NaiveDate::from_ymd(y, 1, 1), - NaiveDate::from_ymd(y, m, d).ordinal().into(), + (y, 1, 1), + NaiveDate::from_ymd_opt(y, m, d).unwrap().ordinal().into(), ); } } @@ -899,77 +914,77 @@ mod tests { fn is_weekday() { let e = Expr::Var(Var::IsWeekday); - expr(&e, NaiveDate::from_ymd(2021, 12, 18), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 20), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 21), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 22), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 23), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 24), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 25), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 26), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 27), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 1); + expr_ymd(&e, (2021, 12, 18), 0); + expr_ymd(&e, (2021, 12, 19), 0); + expr_ymd(&e, (2021, 12, 20), 1); + expr_ymd(&e, (2021, 12, 21), 1); + expr_ymd(&e, (2021, 12, 22), 1); + expr_ymd(&e, (2021, 12, 23), 1); + expr_ymd(&e, (2021, 12, 24), 1); + expr_ymd(&e, (2021, 12, 25), 0); + expr_ymd(&e, (2021, 12, 26), 0); + expr_ymd(&e, (2021, 12, 27), 1); + expr_ymd(&e, (2021, 12, 28), 1); + expr_ymd(&e, (2021, 12, 29), 1); + expr_ymd(&e, (2021, 12, 30), 1); + expr_ymd(&e, (2021, 12, 31), 1); } #[test] fn is_weekend() { let e = Expr::Var(Var::IsWeekend); - expr(&e, NaiveDate::from_ymd(2021, 12, 18), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 19), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 20), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 21), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 22), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 23), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 24), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 25), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 26), 1); - expr(&e, NaiveDate::from_ymd(2021, 12, 27), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 28), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 29), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 30), 0); - expr(&e, NaiveDate::from_ymd(2021, 12, 31), 0); + expr_ymd(&e, (2021, 12, 18), 1); + expr_ymd(&e, (2021, 12, 19), 1); + expr_ymd(&e, (2021, 12, 20), 0); + expr_ymd(&e, (2021, 12, 21), 0); + expr_ymd(&e, (2021, 12, 22), 0); + expr_ymd(&e, (2021, 12, 23), 0); + expr_ymd(&e, (2021, 12, 24), 0); + expr_ymd(&e, (2021, 12, 25), 1); + expr_ymd(&e, (2021, 12, 26), 1); + expr_ymd(&e, (2021, 12, 27), 0); + expr_ymd(&e, (2021, 12, 28), 0); + expr_ymd(&e, (2021, 12, 29), 0); + expr_ymd(&e, (2021, 12, 30), 0); + expr_ymd(&e, (2021, 12, 31), 0); } #[test] fn is_leap_year() { let e = Expr::Var(Var::IsLeapYear); - expr(&e, NaiveDate::from_ymd(2000, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2001, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2002, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2003, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2004, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2005, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2006, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2007, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2008, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2009, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2010, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2011, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2012, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2013, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2014, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2015, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2016, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2017, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2018, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2019, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2020, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2021, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2022, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2023, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2024, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2025, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2026, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2027, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2028, 1, 1), 1); - expr(&e, NaiveDate::from_ymd(2029, 1, 1), 0); - expr(&e, NaiveDate::from_ymd(2030, 1, 1), 0); + expr_ymd(&e, (2000, 1, 1), 1); + expr_ymd(&e, (2001, 1, 1), 0); + expr_ymd(&e, (2002, 1, 1), 0); + expr_ymd(&e, (2003, 1, 1), 0); + expr_ymd(&e, (2004, 1, 1), 1); + expr_ymd(&e, (2005, 1, 1), 0); + expr_ymd(&e, (2006, 1, 1), 0); + expr_ymd(&e, (2007, 1, 1), 0); + expr_ymd(&e, (2008, 1, 1), 1); + expr_ymd(&e, (2009, 1, 1), 0); + expr_ymd(&e, (2010, 1, 1), 0); + expr_ymd(&e, (2011, 1, 1), 0); + expr_ymd(&e, (2012, 1, 1), 1); + expr_ymd(&e, (2013, 1, 1), 0); + expr_ymd(&e, (2014, 1, 1), 0); + expr_ymd(&e, (2015, 1, 1), 0); + expr_ymd(&e, (2016, 1, 1), 1); + expr_ymd(&e, (2017, 1, 1), 0); + expr_ymd(&e, (2018, 1, 1), 0); + expr_ymd(&e, (2019, 1, 1), 0); + expr_ymd(&e, (2020, 1, 1), 1); + expr_ymd(&e, (2021, 1, 1), 0); + expr_ymd(&e, (2022, 1, 1), 0); + expr_ymd(&e, (2023, 1, 1), 0); + expr_ymd(&e, (2024, 1, 1), 1); + expr_ymd(&e, (2025, 1, 1), 0); + expr_ymd(&e, (2026, 1, 1), 0); + expr_ymd(&e, (2027, 1, 1), 0); + expr_ymd(&e, (2028, 1, 1), 1); + expr_ymd(&e, (2029, 1, 1), 0); + expr_ymd(&e, (2030, 1, 1), 0); } #[test] @@ -977,36 +992,36 @@ mod tests { let e = Expr::Var(Var::IsIsoLeapYear); // August 1st is definitely in the same year in both systems - expr(&e, NaiveDate::from_ymd(2000, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2001, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2002, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2003, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2004, 8, 1), 1); - expr(&e, NaiveDate::from_ymd(2005, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2006, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2007, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2008, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2009, 8, 1), 1); - expr(&e, NaiveDate::from_ymd(2010, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2011, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2012, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2013, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2014, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2015, 8, 1), 1); - expr(&e, NaiveDate::from_ymd(2016, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2017, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2018, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2019, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2020, 8, 1), 1); - expr(&e, NaiveDate::from_ymd(2021, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2022, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2023, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2024, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2025, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2026, 8, 1), 1); - expr(&e, NaiveDate::from_ymd(2027, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2028, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2029, 8, 1), 0); - expr(&e, NaiveDate::from_ymd(2030, 8, 1), 0); + expr_ymd(&e, (2000, 8, 1), 0); + expr_ymd(&e, (2001, 8, 1), 0); + expr_ymd(&e, (2002, 8, 1), 0); + expr_ymd(&e, (2003, 8, 1), 0); + expr_ymd(&e, (2004, 8, 1), 1); + expr_ymd(&e, (2005, 8, 1), 0); + expr_ymd(&e, (2006, 8, 1), 0); + expr_ymd(&e, (2007, 8, 1), 0); + expr_ymd(&e, (2008, 8, 1), 0); + expr_ymd(&e, (2009, 8, 1), 1); + expr_ymd(&e, (2010, 8, 1), 0); + expr_ymd(&e, (2011, 8, 1), 0); + expr_ymd(&e, (2012, 8, 1), 0); + expr_ymd(&e, (2013, 8, 1), 0); + expr_ymd(&e, (2014, 8, 1), 0); + expr_ymd(&e, (2015, 8, 1), 1); + expr_ymd(&e, (2016, 8, 1), 0); + expr_ymd(&e, (2017, 8, 1), 0); + expr_ymd(&e, (2018, 8, 1), 0); + expr_ymd(&e, (2019, 8, 1), 0); + expr_ymd(&e, (2020, 8, 1), 1); + expr_ymd(&e, (2021, 8, 1), 0); + expr_ymd(&e, (2022, 8, 1), 0); + expr_ymd(&e, (2023, 8, 1), 0); + expr_ymd(&e, (2024, 8, 1), 0); + expr_ymd(&e, (2025, 8, 1), 0); + expr_ymd(&e, (2026, 8, 1), 1); + expr_ymd(&e, (2027, 8, 1), 0); + expr_ymd(&e, (2028, 8, 1), 0); + expr_ymd(&e, (2029, 8, 1), 0); + expr_ymd(&e, (2030, 8, 1), 0); } } diff --git a/src/eval/date.rs b/src/eval/date.rs index 61b2908..54eb2e1 100644 --- a/src/eval/date.rs +++ b/src/eval/date.rs @@ -103,11 +103,24 @@ impl Dates { } pub fn move_by(&self, delta: Duration) -> Self { - Self { - root: self.root + delta, - other: self.other + delta, - times: self.times, + let mut result = *self; + + // Modify dates + result.root += delta; + result.other += delta; + + // Modify times if necessary (may further modify dates) + const MINUTES_PER_DAY: i64 = 24 * 60; + let minutes = delta.num_minutes() % MINUTES_PER_DAY; // May be negative + if let Some(times) = self.times { + let (root_days, root) = times.root.add_minutes(minutes); + let (other_days, other) = times.other.add_minutes(minutes); + result.root += Duration::days(root_days); + result.other += Duration::days(other_days); + result.times = Some(Times { root, other }); } + + result } } @@ -136,32 +149,16 @@ impl From for Dates { impl From for DoneDate { fn from(dates: Dates) -> Self { - if dates.root == dates.other { - match dates.times { - Some(times) if times.root == times.other => Self::DateTime { - root: dates.root, - root_time: times.root, - }, - Some(times) => Self::DateTimeToTime { - root: dates.root, - root_time: times.root, - other_time: times.other, - }, - None => Self::Date { root: dates.root }, - } - } else { - match dates.times { - Some(times) => Self::DateTimeToDateTime { - root: dates.root, - root_time: times.root, - other: dates.other, - other_time: times.other, - }, - None => Self::DateToDate { - root: dates.root, - other: dates.other, - }, - } + let (root, other) = dates.dates(); + match dates.times() { + Some((root_time, other_time)) => Self::DateTimeToDateTime { + root, + root_time, + other, + other_time, + }, + None => Self::DateToDate { root, other }, } + .simplified() } } diff --git a/src/eval/delta.rs b/src/eval/delta.rs index ecde59b..b42d8a7 100644 --- a/src/eval/delta.rs +++ b/src/eval/delta.rs @@ -5,7 +5,7 @@ use chrono::{Datelike, Duration, NaiveDate}; use crate::files::commands; use crate::files::primitives::{Span, Spanned, Time, Weekday}; -use super::{util, Error, Result}; +use super::{util, Error}; /// Like [`commands::DeltaStep`] but includes a new constructor, /// [`DeltaStep::Time`]. @@ -43,84 +43,84 @@ impl DeltaStep { /// A lower bound on days fn lower_bound(&self) -> i32 { match self { - DeltaStep::Year(n) => { + Self::Year(n) => { if *n < 0 { *n * 366 } else { *n * 365 } } - DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => { + Self::Month(n) | Self::MonthReverse(n) => { if *n < 0 { *n * 31 } else { *n * 28 } } - DeltaStep::Day(n) => *n, - DeltaStep::Week(n) => *n * 7, - DeltaStep::Hour(n) => { + Self::Day(n) => *n, + Self::Week(n) => *n * 7, + Self::Hour(n) => { if *n < 0 { *n / 24 + (*n % 24).signum() } else { *n / 24 } } - DeltaStep::Minute(n) => { + Self::Minute(n) => { if *n < 0 { *n / (24 * 60) + (*n % (24 * 60)).signum() } else { *n / (24 * 60) } } - DeltaStep::Weekday(n, _) => match n.cmp(&0) { + Self::Weekday(n, _) => match n.cmp(&0) { Ordering::Less => *n * 7 - 1, Ordering::Equal => 0, Ordering::Greater => *n * 7 - 7, }, - DeltaStep::Time(_) => 0, + Self::Time(_) => 0, } } /// An upper bound on days fn upper_bound(&self) -> i32 { match self { - DeltaStep::Year(n) => { + Self::Year(n) => { if *n > 0 { *n * 366 } else { *n * 365 } } - DeltaStep::Month(n) | DeltaStep::MonthReverse(n) => { + Self::Month(n) | Self::MonthReverse(n) => { if *n > 0 { *n * 31 } else { *n * 28 } } - DeltaStep::Day(n) => *n, - DeltaStep::Week(n) => *n * 7, - DeltaStep::Hour(n) => { + Self::Day(n) => *n, + Self::Week(n) => *n * 7, + Self::Hour(n) => { if *n > 0 { *n / 24 + (*n % 24).signum() } else { *n / 24 } } - DeltaStep::Minute(n) => { + Self::Minute(n) => { if *n > 0 { *n / (24 * 60) + (*n % (24 * 60)).signum() } else { *n / (24 * 60) } } - DeltaStep::Weekday(n, _) => match n.cmp(&0) { + Self::Weekday(n, _) => match n.cmp(&0) { Ordering::Less => *n * 7 - 7, Ordering::Equal => 0, Ordering::Greater => *n * 7 - 1, }, - DeltaStep::Time(_) => 1, + Self::Time(_) => 1, } } } @@ -142,16 +142,16 @@ impl From<&commands::Delta> for Delta { } } -struct DeltaEval { - index: usize, +struct DeltaEval { + index: I, start: NaiveDate, start_time: Option