diff --git a/.gitignore b/.gitignore index 5e5bbbd..350f73e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -/output /target /untracked diff --git a/Cargo.lock b/Cargo.lock index 04362c2..f5d647f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -77,7 +62,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -88,7 +73,7 @@ checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -97,39 +82,12 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" version = "2.9.1" @@ -142,12 +100,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - [[package]] name = "cc" version = "1.2.32" @@ -185,7 +137,6 @@ checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", "phf", - "serde", ] [[package]] @@ -234,144 +185,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.3.3" @@ -381,164 +200,15 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - [[package]] name = "iana-time-zone" version = "0.1.63" @@ -578,150 +248,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -737,12 +263,6 @@ dependencies = [ "nom", ] -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - [[package]] name = "js-sys" version = "0.3.77" @@ -753,116 +273,24 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "maud" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e" -dependencies = [ - "itoa", - "maud_macros", -] - -[[package]] -name = "maud_macros" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "8.0.0" @@ -881,16 +309,6 @@ dependencies = [ "nom", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -900,15 +318,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -921,62 +330,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "phf" version = "0.12.1" @@ -995,33 +348,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - [[package]] name = "proc-macro2" version = "1.0.96" @@ -1031,18 +357,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", -] - [[package]] name = "quote" version = "1.0.40" @@ -1066,17 +380,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1087,75 +392,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "reqwest" -version = "0.12.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "rrule" version = "0.14.0" @@ -1169,102 +414,12 @@ dependencies = [ "thiserror", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustix" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.60.2", -] - -[[package]] -name = "rustls" -version = "0.23.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.219" @@ -1285,48 +440,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_json" -version = "1.0.142" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1339,46 +452,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "2.0.104" @@ -1390,60 +469,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "thiserror" version = "2.0.12" @@ -1464,256 +489,12 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "pin-project-lite", - "slab", - "socket2", - "windows-sys 0.59.0", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_parser" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -1726,44 +507,11 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.3", + "getrandom", "js-sys", "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -1799,19 +547,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -1844,59 +579,18 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wide-angle-calendar" version = "0.1.0" dependencies = [ "anyhow", - "base64", "chrono", "chrono-tz", "clap", "icalendar", - "maud", - "reqwest", "rrule", - "serde", - "tokio", - "toml", - "tracing", - "tracing-subscriber", - "url", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.61.2" @@ -1938,17 +632,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -1967,47 +650,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows-targets", ] [[package]] @@ -2017,118 +666,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" - [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -2137,93 +732,3 @@ checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 4271d6c..0d82415 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,17 +5,8 @@ edition = "2024" [dependencies] anyhow = "1.0.98" -base64 = "0.22.1" chrono = "0.4.41" -chrono-tz = { version = "0.10.4", features = ["serde"] } +chrono-tz = "0.10.4" clap = { version = "4.5.43", features = ["derive"] } icalendar = { version = "0.17.1", features = ["chrono-tz", "parser", "serde"] } -maud = "0.27.0" -reqwest = "0.12.22" rrule = "0.14.0" -serde = { version = "1.0.219", features = ["derive"] } -tokio = { version = "1.47.1", features = ["rt-multi-thread", "time"] } -toml = "0.9.5" -tracing = "0.1.41" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } -url = { version = "2.5.4", features = ["serde"] } diff --git a/README.md b/README.md index e91a064..bc5bb48 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,21 @@ -todo +Merges multiple ics files into one stream -- [ ] Rewrite README considering best practices -- [ ] Add dynamic OpenGraph meta tags -- [ ] HTML templating for faster styling -- [ ] Maybe put descriptions behind `details` tag -- [ ] Publish ICS to subscribe to? -- [ ] systemd unit or something +Try `cargo run -- ics-debug --tz America/New_York sample-data/nascar.ics sample-data/formula-1.ics` + +Expected output is the next month or so of NASCAR and Formula 1 racing: + +``` +2025-08-03 00:00:00 EDT - NASCAR Cup - Iowa Corn 350 +2025-08-03 00:00:00 EDT - F1 - Lenovo Hungarian Grand Prix +2025-08-10 00:00:00 EDT - NASCAR Cup - Go Bowling At The Glen (Watkins Glen) +2025-08-16 00:00:00 EDT - NASCAR Cup - Cook Out 400 (Richmond) +2025-08-23 00:00:00 EDT - NASCAR Cup - Coke Zero Sugar 400 (Daytona) +2025-08-31 00:00:00 EDT - NASCAR Cup -Playoff- Southern 500 (Darlington) +2025-08-31 00:00:00 EDT - F1 - Heineken Dutch Grand Prix +2025-09-07 00:00:00 EDT - NASCAR Cup -Playoff- Enjoy Illinois 300 (Gateway) +2025-09-07 00:00:00 EDT - F1 - Gran Premio d'Italia +2025-09-13 00:00:00 EDT - NASCAR Cup -Playoff- Bass Pro Shops Night Race (Bristol) +2025-09-21 00:00:00 EDT - F1 - Qatar Airways Azerbaijan Grand Prix +``` + +If the sample data is old, try the iCal links from , e.g. `curl https://calendar.google.com/calendar/ical/fa9bjl6tu13dd10b066stoo5do%40group.calendar.google.com/public/basic.ics > sample-data/formula-1.ics` diff --git a/src/main.rs b/src/main.rs index 56c87e0..4a75c07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,69 +1,27 @@ use anyhow::{Context as _, Result, anyhow, bail}; -use base64::Engine as _; use chrono::{DateTime, TimeZone as _, Utc}; use clap::Parser as _; -use icalendar::{Component as _, EventLike as _}; -use serde::Deserialize; +use icalendar::Component as _; use std::{ - collections::BTreeSet, io::Write as _, path::PathBuf, str::FromStr as _, time::Duration, + convert::TryInto as _, + path::{Path, PathBuf}, + str::FromStr, + time::Duration, }; -#[cfg(test)] -mod tests; - -#[derive(Clone, Deserialize)] -struct ConfigIcal { - /// Disk location to cache the ics file for debugging - file_path: PathBuf, - - /// Magical ID we pass to Google to deep-link to Google Calendar events - google_id: Option, - - /// A canonical webpage we can direct users to - html_url: Option, - - /// Very short name for putting on each event - short_name: String, - - /// URL to scrape to download the ics file - ics_url: url::Url, -} - -#[derive(Deserialize)] -struct ConfigOutput { - /// Used as the OpenGraph description in meta tags - description: String, - - /// Timezone to use for output (e.g. "Antarctica/South_Pole") - /// - /// - timezone: chrono_tz::Tz, - - /// Used as the page title and OpenGraph title in meta tags - title: String, -} - -#[derive(Deserialize)] -struct Config { - icals: Vec, - output: ConfigOutput, -} - -#[derive(clap::Parser)] -struct CliAuto { - #[arg(long)] - config: PathBuf, -} - #[derive(clap::Parser)] struct CliIcsDebug { + /// Timezone to use for output (e.g. "Antarctica/South_Pole") + /// + /// #[arg(long)] - config: PathBuf, + tz: String, + + ics_paths: Vec, } #[derive(clap::Subcommand)] enum Commands { - Auto(CliAuto), IcsDebug(CliIcsDebug), } @@ -74,580 +32,203 @@ struct Cli { command: Commands, } +struct EventInstance { + dtstart: DateTime, + summary: String, +} + struct Parameters { /// Events before this time will be ignored if they cause an error - ignore_before: DateTime, + ignore_before: DateTime, /// Events before this time will not be shown - output_start: DateTime, + output_start: DateTime, /// Events after this time will not be shown - output_stop: DateTime, + output_stop: DateTime, tz: chrono_tz::Tz, } -impl Parameters { - fn new(now: DateTime) -> Result { - // Snap the cutoffs to midnight so we won't present half of a day - let midnight = chrono::NaiveTime::default(); - let output_start = (now - Duration::from_secs(86_400 * 2)) - .with_time(midnight) - .single() - .context("output_start doesn't map to a single time in our timezone")?; - let output_stop = (now + Duration::from_secs(86_400 * 45)) - .with_time(midnight) - .single() - .context("output_stop doesn't map to a single time in our timezone")?; - - Ok(Parameters { - ignore_before: now - Duration::from_secs(86_400 * 365 * 2), - output_start, - output_stop, - tz: now.timezone(), - }) - } -} - -/// Similar to `icalendar::DatePerhapsTime` but doesn't allow Floating, and naive dates are stored as local midnight with an "all day" flag -#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)] -struct DatePerhapsTime { - dt: DateTime, - all_day: bool, -} - -impl DatePerhapsTime { - fn date_naive(&self) -> chrono::NaiveDate { - self.dt.date_naive() - } - - /// Returns None for all-day events - fn time(&self) -> Option { - if self.all_day { - None - } else { - Some(self.dt.time()) - } - } -} - -fn normalize_date_perhaps_time( - x: &icalendar::DatePerhapsTime, - tz: chrono_tz::Tz, -) -> Result { - Ok(match x { - icalendar::DatePerhapsTime::DateTime(x) => { - let dt = x - .try_into_utc() - .context("Data error - Could not convert event datetime to UTC")? - .with_timezone(&tz); - DatePerhapsTime { dt, all_day: false } - } - icalendar::DatePerhapsTime::Date(date) => { - let midnight = chrono::NaiveTime::default(); - let dt = tz.from_local_datetime(&date.and_time(midnight)).single().context("DateTime doesn't map to a single unambiguous datetime when converting to our timezone")?; - DatePerhapsTime { dt, all_day: true } - } - }) -} - -fn recurring_dates_opt( +fn _recurring_event( params: &Parameters, ev: &icalendar::Event, rrule: &icalendar::Property, -) -> Result>> { +) -> Result>> { + let dtstart = ev + .properties() + .get("DTSTART") + .context("Data error - Event has no DTSTART")?; + let dtstart_s: String = dtstart + .clone() + .try_into() + .context("Bug - Can't roundtrip DTSTART")?; + let rrule_s: String = rrule + .clone() + .try_into() + .context("Bug - Can't roundtrip RRULE")?; + + let set_s = format!("{dtstart_s}{rrule_s}"); + let rrule = rrule::RRuleSet::from_str(&set_s) + .with_context(|| format!("RRuleSet parse failed `{set_s}`"))?; + + let recurrences = rrule + .after(params.output_start) + .before(params.output_stop) + .all(10) + .dates; + let mut instances = vec![]; + for dtstart in recurrences { + let dtstart = dtstart.with_timezone(¶ms.tz); + instances.push(dtstart); + } + Ok(instances) +} + +fn recurring_event( + params: &Parameters, + ev: &icalendar::Event, + rrule: &icalendar::Property, +) -> Result>> { let dtstart = ev .get_start() .context("Data error - Event has no DTSTART")?; - let all_day = match &dtstart { - icalendar::DatePerhapsTime::Date(_) => true, - icalendar::DatePerhapsTime::DateTime(_) => false, - }; - let dtstart_norm = normalize_date_perhaps_time(&dtstart, params.tz)?; + let dtstart = normalize_date_perhaps_time(dtstart, ¶ms.tz)?; let rr = rrule::RRule::from_str(rrule.value()) .with_context(|| format!("RRule parse failed `{}`", rrule.value()))?; - if let Some(until) = rr.get_until() - && *until < params.output_start - { - // This skips over some bad data in our test set where we fail to parse a recurring event that's already ended before our output window starts - return Ok(None); + if let Some(until) = rr.get_until() { + if *until < params.output_start { + return Ok(vec![]); + } } - let rrule_tz = params.tz.into(); + let dtstart = dtstart.with_timezone(&rrule::Tz::Tz(params.tz)); - let rr = rr.build(dtstart_norm.dt.with_timezone(&rrule_tz))?; - let dates = rr - .after(params.output_start.with_timezone(&rrule_tz)) - .before(params.output_stop.with_timezone(&rrule_tz)) + let rr = rr.build(dtstart)?; + let recurrences = rr + .after(params.output_start) + .before(params.output_stop) .all(10) - .dates - .into_iter() - .map(move |dtstart| DatePerhapsTime { - dt: dtstart.with_timezone(¶ms.tz), - all_day, - }); - Ok(Some(dates)) -} - -fn recurring_dates( - params: &Parameters, - ev: &icalendar::Event, - rrule: &icalendar::Property, -) -> Result> { - Ok(recurring_dates_opt(params, ev, rrule)? - .into_iter() - .flatten()) -} - -/// An event that's been duplicated according to its recurrence rules, so we can sort by datetimes -struct EventInstance<'a> { - dtstart: DatePerhapsTime, - ev: &'a icalendar::Event, -} - -impl EventInstance<'_> { - fn google_url(&self, google_id: &str) -> Result> { - let uid = self.ev.get_uid().context("No UID")?; - if uid.len() > 100 { - // There's one event in one of our test Google calendars which originates from Microsoft Exchange and has a totally different UID format from any other event. I was not able to reverse it, so I'm skipping it for now. - return Ok(None); - } - - // Strip off the back part of the Google UID - let idx = uid.find(['@', '_']).unwrap_or(uid.len()); - let uid_2 = &uid[..idx]; - let utc_dtstart = self - .dtstart - .dt - .with_timezone(&chrono_tz::UTC) - .format("%Y%m%dT%H%M%SZ") - .to_string(); - let eid_plain = if self.ev.properties().get("RRULE").is_some() { - // Recurring events have an extra timestamp in their base64 to disambiguiate - format!("{uid_2}_{utc_dtstart} {google_id}") - } else { - format!("{uid_2} {google_id}") - }; - let eid = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&eid_plain); - let mut link = url::Url::parse("https://www.google.com/calendar/event").unwrap(); - link.query_pairs_mut().append_pair("eid", &eid); - Ok(Some(link.to_string())) + .dates; + let mut instances = vec![]; + for dtstart in recurrences { + let dtstart = dtstart.with_timezone(¶ms.tz); + instances.push(dtstart); } + Ok(instances) +} - fn url(&self, google_id: Option<&str>) -> Result> { - if let Some(url) = self.ev.get_url() { - return Ok(Some(url.to_string())); +fn normalize_date_perhaps_time( + x: icalendar::DatePerhapsTime, + tz: &chrono_tz::Tz, +) -> Result> { + Ok(match x { + icalendar::DatePerhapsTime::DateTime(x) => x + .try_into_utc() + .context("Data error - Could not convert event datetime to UTC")? + .with_timezone(tz), + icalendar::DatePerhapsTime::Date(date) => { + let midnight = chrono::NaiveTime::default(); + match tz.from_local_datetime(&date.and_time(midnight)) { + chrono::offset::MappedLocalTime::Single(x) => x, + _ => bail!( + "Datetime doesn't map to a single unambiguous datetime when converting to our timezone" + ), + } } - if let Some(google_id) = google_id { - return self.google_url(google_id); - } - Ok(None) - } + }) } -struct EventWithUrl<'a> { - calendar: &'a ConfigIcal, - dtstart: DatePerhapsTime, - ev: &'a icalendar::Event, - url: Option, -} - -impl<'a> EventWithUrl<'a> { - fn from_ei(calendar: &'a ConfigIcal, ei: EventInstance<'a>) -> Result> { - let url = ei.url(calendar.google_id.as_deref())?; - Ok(Self { - calendar, - dtstart: ei.dtstart, - ev: ei.ev, - url, - }) - } -} - -fn event_instances<'a>( - params: &Parameters, - ev: &'a icalendar::Event, -) -> Result>> { - let dates = if let Some(rrule) = ev.properties().get("RRULE") { - recurring_dates(params, ev, rrule)?.collect() +fn event_instances(params: &Parameters, ev: &icalendar::Event) -> Result> { + let instances = if let Some(rrule) = ev.properties().get("RRULE") { + recurring_event(params, ev, rrule)? } else { // Event that occurs once let dtstart = ev.get_start().context("Data error - Event has no start")?; - let dtstart_normalized = normalize_date_perhaps_time(&dtstart, params.tz)?; - if dtstart_normalized.dt < params.output_start || dtstart_normalized.dt > params.output_stop - { + let dtstart = normalize_date_perhaps_time(dtstart, ¶ms.tz)?; + if dtstart < params.output_start || dtstart > params.output_stop { return Ok(vec![]); } - vec![dtstart_normalized] + vec![dtstart] }; - let instances = dates + let summary = ev + .get_summary() + .unwrap_or("Data error BXH45NAR - No summary in event"); + + let instances = instances .into_iter() - .map(|dtstart| EventInstance { dtstart, ev }) + .map(|dtstart| EventInstance { + dtstart, + summary: summary.to_string(), + }) .collect(); Ok(instances) } -struct ICal { - /// The parsed ics file - cal: icalendar::Calendar, -} +fn read_ics(params: &Parameters, path: &Path) -> Result> { + let mut instances = vec![]; -/// Used to link recurrence exceptions to the original events they replace -#[derive(Eq, Ord, PartialOrd, PartialEq)] -struct RecurrenceKey<'a> { - recurrence_id: DatePerhapsTime, - uid: &'a str, -} + let s = std::fs::read_to_string(path)?; + let cal: icalendar::Calendar = s.parse().map_err(|s| anyhow!("parse error {s}"))?; -impl ICal { - fn read_from_str(s: &str) -> Result { - let cal = s.parse().map_err(|s| anyhow!("parse error {s}"))?; - let cal = Self { cal }; - Ok(cal) - } + for component in &cal.components { + let icalendar::CalendarComponent::Event(ev) = component else { + continue; + }; - fn read_from_config(config: &ConfigIcal) -> Result { - let s = std::fs::read_to_string(&config.file_path)?; - Self::read_from_str(&s) - } - - fn events(&self) -> impl Iterator { - self.cal.components.iter().filter_map(|comp| { - if let icalendar::CalendarComponent::Event(ev) = comp { - Some(ev) - } else { - None - } - }) - } - - /// Returns an unsorted list of event instances for this calendar - fn event_instances(&self, params: &Parameters) -> Result>> { - let mut instances = vec![]; - let mut recurrence_exceptions = BTreeSet::new(); - - for ev in self.events() { - let eis = match event_instances(params, ev) - .with_context(|| format!("Failed to process event with UID '{:?}'", ev.get_uid())) - { - Ok(x) => x, - Err(e) => { - if ev.get_last_modified().context("Event has no timestamp")? - < params.ignore_before - { - tracing::warn!("Ignoring error from very old event {e:?}"); - continue; - } else { - Err(e)? - } + let eis = match event_instances(params, ev) + .with_context(|| format!("Failed to process event with UID '{:?}'", ev.get_uid())) + { + Ok(x) => x, + Err(e) => { + if ev.get_last_modified().context("Event has no timestamp")? < params.ignore_before + { + eprintln!("Ignoring error from very old event {e:?}"); + continue; + } else { + Err(e)? } - }; - for ei in eis { - instances.push(ei); - } - - if let Some(recurrence_id) = ev.get_recurrence_id() { - // This is a recurrence exception and we must handle it specially by later deleting the original event it replaces - let recurrence_id = normalize_date_perhaps_time(&recurrence_id, params.tz) - .context("We should be able to normalize recurrence IDs")?; - let uid = ev - .get_uid() - .context("Every recurrence exception should have a UID")?; - - recurrence_exceptions.insert(RecurrenceKey { recurrence_id, uid }); } + }; + for ei in eis { + instances.push(ei); } - - // Find all recurring events that are replaced with recurrence exceptions and delete the originals. - // There is probably a not-linear-time way to do this, but this should be fine. - - instances.retain(|ev| { - if ev.ev.get_recurrence_id().is_some() { - // This is a recurrence exception, exceptions never delete themselves - return true; - } - - let uid = ev - .ev - .get_uid() - .context( - "Every recurring event should have a UID so we can apply recurrence exceptions", - ) - .unwrap(); - let key = RecurrenceKey { - recurrence_id: ev.dtstart, - uid, - }; - !recurrence_exceptions.contains(&key) - }); - - Ok(instances) - } -} - -#[derive(Default)] -struct Data { - icals: Vec<(ICal, ConfigIcal)>, -} - -fn read_data_from_disk(config: &Config) -> Result { - let mut data = Data::default(); - for config in &config.icals { - let cal = ICal::read_from_config(config)?; - data.icals.push((cal, config.clone())); } - Ok(data) + Ok(instances) } -fn process_data(data: &Data, now: DateTime) -> Result>> { - let params = Parameters::new(now)?; +fn main_ics_debug(cli: CliIcsDebug) -> Result<()> { + let tz = cli.tz.parse().context("Couldn't parse timezone name")?; + + let now = Utc::now().with_timezone(&rrule::Tz::Tz(chrono_tz::UTC)); + let params = Parameters { + ignore_before: now - Duration::from_secs(86_400 * 365 * 2), + output_start: now - Duration::from_secs(86_400 * 15), + output_stop: now + Duration::from_secs(86_400 * 45), + tz, + }; let mut instances = vec![]; - for (ical, config) in &data.icals { - for ei in ical.event_instances(¶ms)? { - let ei = EventWithUrl::from_ei(config, ei)?; + + for path in cli.ics_paths { + let eis = + read_ics(¶ms, &path).with_context(|| format!("Failed to parse file `{path:?}`"))?; + for ei in eis { instances.push(ei); } } instances.sort_by_key(|ei| ei.dtstart); - Ok(instances) -} -// FIXME: Don't print to stdout / stderr -fn output_html( - config: &ConfigOutput, - instances: &[EventWithUrl], - now: DateTime, -) -> Result<()> { - let today = now.date_naive(); - let mut last_month_printed: Option = None; - let mut last_date_printed = None; - let mut html_list = vec![]; - let mut day_list = vec![]; - for ei in instances { - let summary = ei - .ev - .get_summary() - .unwrap_or("Data error BXH45NAR - No summary in event"); - - let date = ei.dtstart.date_naive(); - let past = date < today; - let month = date.format("%B").to_string(); - match last_month_printed { - Some(ref x) if *x == month => {} - None | Some(_) => { - // FIXME: De-dupe - if !day_list.is_empty() { - html_list.push(maud::html! { - ul { @for entry in day_list { - (entry) - } } - }); - day_list = vec![]; - } - - html_list.push(maud::html! { - h2 { (month) } - }); - last_month_printed = Some(month); - } - } - if last_date_printed != Some(date) { - // println!("{date}"); - - // FIXME: De-dupe - if !day_list.is_empty() { - html_list.push(maud::html! { - ul { @for entry in day_list { - (entry) - } } - }); - day_list = vec![]; - } - if past { - html_list.push(maud::html! { - p class="past"{ s { (date.format("%-d %A")) } } - }); - } else { - html_list.push(maud::html! { - h3 { (date.format("%-d %A")) } - hr{} - }); - } - - last_date_printed = Some(date); - } - let time = ei.dtstart.time(); - let time = time - .map(|t| t.format("%l:%M %P").to_string()) - .unwrap_or_else(|| "All day".to_string()); - - // println!(" {time} - {summary}"); - let summary = if let Some(url) = &ei.url { - maud::html! {a href=(url) {(summary)}} - } else { - maud::html! {(summary)} - }; - - let location = ei.ev.get_location(); - - if past { - day_list.push(maud::html! { - li class="past" { (time) " - " (summary) } - }); - } else { - let calendar_link = if let Some(html_url) = &ei.calendar.html_url { - maud::html! { a href=(html_url) { (ei.calendar.short_name) } } - } else { - maud::html! { (ei.calendar.short_name)} - }; - - day_list.push(maud::html! { - li { p { (time) " - " (summary) } - ul { - li { (calendar_link) " calendar" } - @if let Some(location) = location { - li { "Location: " (location) } - } - } - } - }); - } + for instance in instances { + let EventInstance { dtstart, summary } = instance; + println!("{dtstart} - {summary}"); } - // FIXME: De-dupe - if !day_list.is_empty() { - html_list.push(maud::html! { - ul { @for entry in day_list { - (entry) - } } - }); - } - - std::fs::create_dir_all("output")?; - { - let temp_path = "output/index.html.tmp"; - let final_path = "output/index.html"; - let mut f = std::fs::File::create(temp_path)?; - let css = r#" - - "#; - - let description = &config.description; - let title = &config.title; - - let s = maud::html! { - html lang="en" { - head { - meta http-equiv="Content-Type" content="text/html; charset=utf-8" {} - meta name="viewport" content="width=device-width, initial-scale=1" {} - (maud::PreEscaped(css)) - - meta property="og:locale" content="en" {} - meta property="og:type" content="website" {} - - meta property="description" content=(description) {} - meta property="og:description" content=(description) {} - - title { (title) } - met property="og:title" content=(title) {} - } - body { - h1 { (title) } - p { "Written at: " (now.to_rfc3339()) } - @for entry in html_list { - (entry) - } - } - } - } - .into_string(); - - f.write_all(s.as_bytes())?; - std::fs::rename(temp_path, final_path)?; - } - - Ok(()) -} - -static APP_USER_AGENT: &str = concat!( - env!("CARGO_PKG_NAME"), - "_Z7FSRRA7/", - env!("CARGO_PKG_VERSION"), -); - -async fn do_everything(cli: &CliAuto) -> Result<()> { - let config = std::fs::read_to_string(&cli.config)?; - let config: Config = toml::from_str(&config)?; - - tracing::info!(?APP_USER_AGENT); - let client = reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .build()?; - for ical in &config.icals { - tracing::info!(url = ical.ics_url.to_string(), "requesting..."); - let resp = client.get(ical.ics_url.clone()).send().await?; - if resp.status() != 200 { - bail!("Bad status {}", resp.status()); - } - let bytes = resp.bytes().await?; - - let temp_path = ical.file_path.with_extension(".ics.temp"); - std::fs::write(&temp_path, &bytes)?; - std::fs::rename(&temp_path, &ical.file_path)?; - } - - let data = read_data_from_disk(&config)?; - - let tz = &config.output.timezone; - let now = Utc::now().with_timezone(tz); - let instances = process_data(&data, now)?; - output_html(&config.output, &instances, now)?; - Ok(()) -} - -/// Seconds to sleep between auto cycles -const SLEEP_SECS: u64 = 9000; - -fn main_auto(cli: CliAuto) -> Result<()> { - tracing_subscriber::fmt::init(); - loop { - let rt = tokio::runtime::Runtime::new()?; - rt.block_on(async { - do_everything(&cli).await?; - Ok::<_, anyhow::Error>(()) - })?; - rt.shutdown_timeout(Duration::from_secs(10)); - tracing::info!("The service is eeping"); - std::thread::sleep(Duration::from_secs(SLEEP_SECS)); - } -} - -fn main_ics_debug(cli: CliIcsDebug) -> Result<()> { - let config = std::fs::read_to_string(&cli.config)?; - let config: Config = toml::from_str(&config)?; - - let data = read_data_from_disk(&config)?; - - let tz = &config.output.timezone; - let now = Utc::now().with_timezone(tz); - let instances = process_data(&data, now)?; - output_html(&config.output, &instances, now)?; Ok(()) } @@ -656,7 +237,6 @@ fn main() -> Result<()> { let cli = Cli::try_parse()?; match cli.command { - Commands::Auto(x) => main_auto(x), Commands::IcsDebug(x) => main_ics_debug(x), } } diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index cc797f3..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,180 +0,0 @@ -use super::*; - -fn chicago_time( - year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, -) -> DateTime { - chrono_tz::America::Chicago - .with_ymd_and_hms(year, month, day, hour, minute, second) - .unwrap() -} - -fn dt_from_ts(ts: i64) -> DateTime { - DateTime::from_timestamp(ts, 0) - .unwrap() - .with_timezone(&chrono_tz::America::Chicago) -} - -/// Expect that parsing a calendar works -#[test] -fn calendar_from_str() -> Result<()> { - // Blank lines added for clarity - let s = r#" -BEGIN:VCALENDAR - -CALSCALE:GREGORIAN -X-WR-TIMEZONE:America/Chicago - -BEGIN:VTIMEZONE -TZID:America/Chicago -X-LIC-LOCATION:America/Chicago -BEGIN:DAYLIGHT -TZOFFSETFROM:-0600 -TZOFFSETTO:-0500 -TZNAME:CDT -DTSTART:19700308T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:-0500 -TZOFFSETTO:-0600 -TZNAME:CST -DTSTART:19701101T020000 -RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU -END:STANDARD -END:VTIMEZONE - -BEGIN:VEVENT -DTSTART;TZID=America/Chicago:20250908T180000 -DTEND;TZID=America/Chicago:20250908T200000 -UID:Redacted -RECURRENCE-ID;TZID=America/Chicago:20250901T180000 -CREATED:20241222T171032Z -LAST-MODIFIED:20250812T021726Z -SEQUENCE:1 -STATUS:CONFIRMED -SUMMARY:zero roman mummy hatch -TRANSP:OPAQUE -END:VEVENT - -END:VCALENDAR -"#; - - let ical = ICal::read_from_str(s)?; - let now = dt_from_ts(1755000000); - let params = Parameters::new(now)?; - let instances = ical.event_instances(¶ms)?; - assert_eq!(instances.len(), 1); - - let event = &instances[0]; - let expected_time = DatePerhapsTime { - dt: chicago_time(2025, 9, 8, 18, 0, 0), - all_day: false, - }; - assert_eq!(event.dtstart, expected_time); - assert_eq!(event.ev.get_summary(), Some("zero roman mummy hatch")); - Ok(()) -} - -/// Expect that recurrent exceptions work correctly and don't duplicate events -#[test] -fn recurrence_exceptions() -> Result<()> { - let s = r#" -BEGIN:VCALENDAR - -CALSCALE:GREGORIAN -X-WR-TIMEZONE:America/Chicago - -BEGIN:VTIMEZONE -TZID:America/Chicago -X-LIC-LOCATION:America/Chicago -BEGIN:DAYLIGHT -TZOFFSETFROM:-0600 -TZOFFSETTO:-0500 -TZNAME:CDT -DTSTART:19700308T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:-0500 -TZOFFSETTO:-0600 -TZNAME:CST -DTSTART:19701101T020000 -RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU -END:STANDARD -END:VTIMEZONE - -BEGIN:VEVENT -DTSTART;TZID=America/Chicago:20250708T180000 -DTEND;TZID=America/Chicago:20250708T200000 -RRULE:FREQ=MONTHLY;BYDAY=2TU -UID:jazz repay stout steam -CLASS:PUBLIC -CREATED:20250703T113806Z -LAST-MODIFIED:20250721T232331Z -SEQUENCE:1 -STATUS:CONFIRMED -SUMMARY:coil perm brush zippy -TRANSP:OPAQUE -END:VEVENT - -BEGIN:VEVENT -DTSTART;TZID=America/Chicago:20250814T180000 -DTEND;TZID=America/Chicago:20250814T200000 -UID:jazz repay stout steam -RECURRENCE-ID;TZID=America/Chicago:20250812T180000 -CLASS:PUBLIC -CREATED:20250703T113806Z -LAST-MODIFIED:20250721T232500Z -SEQUENCE:2 -STATUS:CONFIRMED -SUMMARY:coil perm brush zippy -TRANSP:OPAQUE -END:VEVENT - -END:VCALENDAR -"#; - - let ical = ICal::read_from_str(s)?; - let params = Parameters { - ignore_before: chicago_time(2025, 1, 1, 0, 0, 0), - output_start: chicago_time(2025, 7, 1, 0, 0, 0), - output_stop: chicago_time(2025, 10, 1, 0, 0, 0), - tz: chrono_tz::America::Chicago, - }; - let instances = ical.event_instances(¶ms)?; - - assert_eq!( - [ - instances[0].dtstart, - instances[1].dtstart, - instances[2].dtstart, - ], - [ - DatePerhapsTime { - dt: chicago_time(2025, 7, 8, 18, 0, 0), - all_day: false, - }, - DatePerhapsTime { - dt: chicago_time(2025, 9, 9, 18, 0, 0), - all_day: false, - }, - DatePerhapsTime { - dt: chicago_time(2025, 8, 14, 18, 0, 0), - all_day: false, - }, - ] - ); - - for instance in &instances { - assert_eq!(instance.ev.get_summary(), Some("coil perm brush zippy")); - } - - assert_eq!(instances.len(), 3); - - Ok(()) -}