diff --git a/CHANGELOG.md b/CHANGELOG.md index d8ab0ff..2380c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,21 +30,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Refactored IMAP and SMTP auth config API +- Improved error messages when missing cargo features. For example, if a TOML configuration uses the IMAP backend without the `imap` cargo features, the error `missing "imap" feature` is displayed. [#20](https://github.com/pimalaya/core/issues/20) +- Normalized enum-based configurations, using the [internally tagged representation](https://serde.rs/enum-representations.html#internally-tagged) `type =`. It should reduce issues due to misconfiguration, and improve othe error messages. Yet it is not perfect, see [#802](https://github.com/toml-rs/toml/issues/802): - The IMAP and SMTP auth config option is now explicit, in order to improve error messages: + - `imap.*`, `maildir.*` and `notmuch.*` moved to `backend.*`: - ```toml - # before - imap.password.cmd = "pass show example" - smtp.oauth2.method = "xoauth2" + ```toml + # before + imap.host = "localhost" + imap.port = 143 - # after - imap.auth.type = "password" - imap.auth.cmd = "pass show example" - smtp.auth.type = "oauth2" - smtp.auth.method = "xoauth2" - ``` + # after + backend.type = "imap" + backend.host = "localhost" + backend.port = 143 + ``` + + - `smtp.*` and `sendmail.*` moved to `message.send.backend.*`: + + ```toml + # before + smtp.host = "localhost" + smtp.port = 25 + + # after + message.send.backend.type = "smtp" + message.send.backend.host = "localhost" + message.send.backend.port = 25 + ``` + + - `pgp.backend` renamed `pgp.type`: + + ```toml + # before + pgp.backend = "commands" + pgp.encrypt-cmd = "gpg --encrypt --quiet --armor " + + # after + pgp.type = "commands" + pgp.encrypt-cmd = "gpg --encrypt --quiet --armor " + ``` + + - `{imap,smtp}.auth` moved as well: + + ```toml + # before + imap.password.cmd = "pass show example" + smtp.oauth2.method = "xoauth2" + + # after + backend.auth.type = "password" + backend.auth.cmd = "pass show example" + message.send.backend.auth.type = "oauth2" + message.send.backend.auth.method = "xoauth2" + ``` ## [1.0.0-beta.4] - 2024-04-16 @@ -849,18 +888,3 @@ Few major concepts changed: [0.2.1]: https://github.com/soywod/himalaya/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/soywod/himalaya/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/soywod/himalaya/releases/tag/v0.1.0 - -[#39]: https://todo.sr.ht/~soywod/pimalaya/39 -[#41]: https://todo.sr.ht/~soywod/pimalaya/41 -[#43]: https://todo.sr.ht/~soywod/pimalaya/43 -[#54]: https://todo.sr.ht/~soywod/pimalaya/54 -[#58]: https://todo.sr.ht/~soywod/pimalaya/58 -[#59]: https://todo.sr.ht/~soywod/pimalaya/59 -[#60]: https://todo.sr.ht/~soywod/pimalaya/60 -[#95]: https://todo.sr.ht/~soywod/pimalaya/95 -[#172]: https://todo.sr.ht/~soywod/pimalaya/172 -[#173]: https://todo.sr.ht/~soywod/pimalaya/173 -[#184]: https://todo.sr.ht/~soywod/pimalaya/184 -[#188]: https://todo.sr.ht/~soywod/pimalaya/188 -[#194]: https://todo.sr.ht/~soywod/pimalaya/194 -[#195]: https://todo.sr.ht/~soywod/pimalaya/195 diff --git a/Cargo.lock b/Cargo.lock index 740f588..5483c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", @@ -114,36 +114,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -158,12 +158,14 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.5.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ - "event-listener 2.5.3", + "event-listener", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] @@ -179,23 +181,27 @@ dependencies = [ ] [[package]] -name = "async-io" -version = "1.13.0" +name = "async-executor" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", + "async-task", "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", + "fastrand", + "futures-lite", "slab", - "socket2 0.4.10", - "waker-fn", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", ] [[package]] @@ -204,54 +210,47 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "async-lock 3.4.0", + "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "parking", - "polling 3.7.3", - "rustix 0.38.37", + "polling", + "rustix", "slab", "tracing", "windows-sys 0.59.0", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.3.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" -version = "1.8.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", + "async-channel", + "async-io", + "async-lock", "async-signal", + "async-task", "blocking", "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.37", - "windows-sys 0.48.0", + "event-listener", + "futures-lite", + "rustix", + "tracing", ] [[package]] @@ -262,7 +261,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -271,13 +270,13 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.4", - "async-lock 3.4.0", + "async-io", + "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.37", + "rustix", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -297,7 +296,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -405,7 +404,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "piper", ] @@ -433,7 +432,7 @@ checksum = "e0af050e27e5d57aa14975f97fe47a134c46a390f91819f23a625319a7111bfa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -443,7 +442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.8", "serde", ] @@ -476,9 +475,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "camellia" @@ -510,13 +509,19 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.22" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfb-mode" version = "0.8.2" @@ -542,6 +547,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -563,7 +574,7 @@ version = "1.0.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7b80276986f86789dc56ca6542d53bba9cda3c66091ebbe7bd96fc1bdf20f1f" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", "regex-automata 0.3.9", "serde", "unicode-ident", @@ -581,9 +592,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -591,9 +602,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -604,9 +615,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.29" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8937760c3f4c60871870b8c3ee5f9b30771f792a7045c48bcbba999d7d6b3b8e" +checksum = "07a13ab5b8cb13dbe35e68b83f6c12f9293b2f601797b71bc9f23befdb329feb" dependencies = [ "clap", ] @@ -620,7 +631,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -631,9 +642,9 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clap_mangen" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17415fd4dfbea46e3274fcd8d368284519b358654772afb700dc2e8d2b24eeb" +checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" dependencies = [ "clap", "roff", @@ -668,9 +679,19 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "comfy-table" @@ -718,6 +739,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -871,7 +902,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -956,6 +987,35 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "dbus-secret-service" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42a16374481d92aed73ae45b1f120207d8e71d24fb89f357fadbd8f946fd84b" +dependencies = [ + "aes", + "block-padding", + "cbc", + "dbus", + "futures-util", + "hkdf", + "num", + "once_cell", + "rand", + "sha2", +] + [[package]] name = "der" version = "0.7.9" @@ -967,17 +1027,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "derive_builder" version = "0.12.0" @@ -1144,8 +1193,9 @@ dependencies = [ [[package]] name = "email-lib" -version = "0.25.0" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdb5d972806dac8ab65f659d7c7ec59fc683bd2b9e149bdc77b18ae95a16fd9" dependencies = [ "async-trait", "chrono", @@ -1154,10 +1204,7 @@ dependencies = [ "email_address", "futures", "hickory-resolver", - "http-body-util", - "hyper 1.4.1", - "hyper-rustls 0.27.3", - "hyper-util", + "http-lib", "imap-client", "imap-next", "keyring-lib", @@ -1197,7 +1244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f24a09fd651027f8764f8a12c12358715cb9bab622ab3125ede3dd6ae047c95" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -1205,19 +1252,22 @@ name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" -dependencies = [ - "serde", -] [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1227,7 +1277,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -1248,7 +1298,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -1267,23 +1317,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -1301,7 +1334,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.3.1", + "event-listener", "pin-project-lite", ] @@ -1315,15 +1348,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.1.1" @@ -1422,9 +1446,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1437,9 +1461,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1447,15 +1471,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1464,24 +1488,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1489,38 +1498,41 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ + "fastrand", "futures-core", + "futures-io", + "parking", "pin-project-lite", ] [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1594,9 +1606,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gpg-error" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29dd79aa3d28d1759188a70e42f3084484d9933d7afd1a50decd84314b84caba" +checksum = "545aae14d0e95734d639c8076304e6e86de765c19c76bead3648583d9caed919" dependencies = [ "libgpg-error-sys", ] @@ -1644,44 +1656,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1692,6 +1666,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -1771,29 +1751,19 @@ name = "himalaya" version = "1.0.0" dependencies = [ "ariadne", - "async-trait", "clap", "clap_complete", "clap_mangen", "color-eyre", - "comfy-table", - "crossterm 0.27.0", - "dirs 4.0.0", "email-lib", - "email_address", "mail-builder", - "md5", "mml-lib", - "oauth-lib", "once_cell", - "petgraph", "pimalaya-tui", - "process-lib", "secret-lib", "serde", "serde_json", "shellexpand-utils", - "sled", "tokio", "toml", "tracing", @@ -1820,12 +1790,15 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.9" +name = "hoot" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "6a018c1f2075066355e95ac5ab7909d35a84c9d14d1fae84c4bacdf6dec188d8" dependencies = [ - "windows-sys 0.52.0", + "http", + "httparse", + "log", + "url", ] [[package]] @@ -1839,17 +1812,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -1862,148 +1824,22 @@ dependencies = [ ] [[package]] -name = "http-body" -version = "0.4.6" +name = "http-lib" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "994cbd23c90551cb5821d1c9d9b1e41383f338b31fc122671edc7d1695a61338" dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.1.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "pin-project-lite", + "thiserror", + "tokio", + "tracing", + "ureq", ] [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.6", - "http 1.1.0", - "http-body 1.0.1", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.30", - "log", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", - "tokio", - "tokio-rustls 0.24.1", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.4.1", - "hyper-util", - "log", - "rustls 0.23.13", - "rustls-native-certs 0.8.0", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.0", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.4.1", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", -] +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "iana-time-zone" @@ -2065,8 +1901,9 @@ dependencies = [ [[package]] name = "imap-client" -version = "0.1.4" -source = "git+https://github.com/pimalaya/imap-client#c1f0dfcfb3e5f22763324d6d2497e195d44c9ec8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e667382fe3b5f75c78af4f7028a44435ed5f6da8e91500037115cee1569d8c5" dependencies = [ "imap-next", "once_cell", @@ -2093,8 +1930,9 @@ dependencies = [ [[package]] name = "imap-next" -version = "0.2.0" -source = "git+https://github.com/duesee/imap-next#75671ca68e067e82a8846bef0e9396809ca93ffa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793a41b36eb0fbe7eff9d8715717be264c9429d1989a60fd3d7893476b966506" dependencies = [ "bytes", "imap-codec", @@ -2126,12 +1964,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -2190,24 +2028,13 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.7", + "socket2", "widestring", "windows-sys 0.48.0", "winreg 0.50.0", @@ -2215,9 +2042,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" @@ -2232,10 +2059,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "js-sys" -version = "0.3.70" +name = "jni" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2251,26 +2098,33 @@ dependencies = [ [[package]] name = "keyring" -version = "2.3.3" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363387f0019d714aa60cc30ab4fe501a747f4c08fc58f069dd14be971bd495a0" +checksum = "2f8fe839464d4e4b37d756d7e910063696af79a7e877282cb1825e4ec5f10833" dependencies = [ - "lazy_static", + "byteorder", + "dbus-secret-service", "linux-keyutils", + "log", "secret-service", + "security-framework 2.11.1", + "security-framework 3.0.0", + "windows-sys 0.59.0", + "zbus", ] [[package]] name = "keyring-lib" -version = "0.4.3" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56921558c465f33d51c6047b86b76764cd2a86b69b99653b43ba1f9a32965218" dependencies = [ "keyring", - "log", "once_cell", "serde", "thiserror", "tokio", + "tracing", ] [[package]] @@ -2304,15 +2158,24 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "pkg-config", +] [[package]] name = "libgpg-error-sys" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc23cc53e9b81f56330826fe36b6cc651543a7661748d5259f446de2ef4fdac9" +checksum = "500a4cbc0816ed820a5bcf73a19e74dd6df4bedeabc0f64471c61186938b6c82" dependencies = [ "build-rs", "system-deps", @@ -2321,9 +2184,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "a00419de735aac21d53b0de5ce2c03bd3627277cf471300f27ebc89f7d828047" [[package]] name = "libredox" @@ -2333,7 +2196,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.6", + "redox_syscall 0.5.7", ] [[package]] @@ -2352,12 +2215,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2415,12 +2272,12 @@ checksum = "7a575d25cf00ed68e5790b473b29242a47e991c6187785d47b45e31fc5816554" dependencies = [ "base64 0.22.1", "gethostname", - "rustls 0.23.13", + "rustls 0.23.15", "rustls-pki-types", "smtp-proto", "tokio", "tokio-rustls 0.26.0", - "webpki-roots 0.26.6", + "webpki-roots", ] [[package]] @@ -2489,12 +2346,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2545,14 +2396,13 @@ dependencies = [ [[package]] name = "mml-lib" -version = "1.0.14" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875723012f6a5fb47a6b42ae947e44286ba61498007836ebe4fc60bc84c6899f" +checksum = "d4ac497207f2c20c5f29503ad6e04a7d5a15e29583526764a03d46239d5b80fa" dependencies = [ "async-recursion", "chumsky", "gpgme", - "log", "mail-builder", "mail-parser", "nanohtml2text", @@ -2562,6 +2412,7 @@ dependencies = [ "serde", "shellexpand-utils", "thiserror", + "tracing", "tree_magic_mini", ] @@ -2582,14 +2433,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "cfg-if", + "cfg_aliases", "libc", - "memoffset 0.7.1", + "memoffset 0.9.1", ] [[package]] @@ -2698,7 +2550,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2744,29 +2596,28 @@ dependencies = [ [[package]] name = "oauth-lib" -version = "0.1.1" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d936e2859cd8769d97a035df0886e0a8c487d916415b4ad3802ecd5a86664452" dependencies = [ - "log", + "http-lib", "oauth2", - "reqwest", "thiserror", "tokio", - "url", + "tracing", ] [[package]] name = "oauth2" -version = "4.4.2" +version = "5.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +checksum = "23d385da3c602d29036d2f70beed71c36604df7570be17fed4c5b839616785bf" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "chrono", "getrandom", - "http 0.2.12", + "http", "rand", - "reqwest", "serde", "serde_json", "serde_path_to_error", @@ -2786,9 +2637,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl-probe" @@ -2929,7 +2780,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.6", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -3019,46 +2870,53 @@ dependencies = [ [[package]] name = "pgp-lib" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6802b1ef0dfc50217185a1eda6ddd546b65ffa8b80f942aa1feda6536adf165" +checksum = "62b1a64cb843daaa31d7109d77871ba6fcaa25be5b488c97eeb28fc378e5fed7" dependencies = [ "async-recursion", "futures", - "hyper 0.14.30", - "hyper-rustls 0.24.2", - "log", + "http-lib", "pgp", "rand", "sha1", "smallvec", "thiserror", "tokio", - "url", + "tracing", "z-base-32", ] [[package]] name = "pimalaya-tui" version = "0.1.0" -source = "git+https://github.com/pimalaya/tui#b40ca9d9e161b91e9efcb762fa05f06e3fe46f63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d72a8bba0e012700dfb3a7611fffaa6b9ec9dc747134b1a1d34d2fd9f8ddce5" dependencies = [ + "async-trait", "clap", "color-eyre", - "crossterm 0.25.0", + "comfy-table", + "crossterm 0.27.0", "dirs 4.0.0", "email-lib", "email_address", "inquire", + "md5", + "mml-lib", "oauth-lib", + "petgraph", + "process-lib", "secret-lib", "serde", "serde-toml-merge", "serde_json", "shellexpand-utils", + "sled", "thiserror", + "tokio", "toml", - "toml_edit 0.22.22", + "toml_edit", "tracing", "tracing-error", "tracing-subscriber", @@ -3066,9 +2924,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -3083,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.1", + "fastrand", "futures-io", ] @@ -3114,22 +2972,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.7.3" @@ -3140,7 +2982,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.37", + "rustix", "tracing", "windows-sys 0.59.0", ] @@ -3165,12 +3007,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "toml_edit", ] [[package]] @@ -3199,22 +3040,23 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "process-lib" -version = "0.4.2" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb47ed33aeaf6b32cecbbde6f56dde6c8740f2dac4a146179cc82f797918c46" dependencies = [ - "log", "serde", "thiserror", "tokio", + "tracing", ] [[package]] @@ -3293,9 +3135,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -3313,14 +3155,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3345,13 +3187,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -3368,50 +3210,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg 0.50.0", -] +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "resolv-conf" @@ -3500,28 +3301,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.14", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -3539,9 +3326,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "log", "once_cell", @@ -3554,14 +3341,15 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.4", + "rustls-pemfile 2.2.0", + "rustls-pki-types", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] @@ -3571,10 +3359,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] @@ -3588,19 +3376,45 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +dependencies = [ + "core-foundation 0.9.4", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.15", + "rustls-native-certs 0.7.3", + "rustls-platform-verifier-android", + "rustls-webpki 0.102.8", + "security-framework 2.11.1", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" @@ -3625,9 +3439,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -3646,9 +3460,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -3685,21 +3499,22 @@ dependencies = [ [[package]] name = "secret-lib" -version = "0.4.6" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba34b385def61154faed219ac86f80ad8c72877a278208bac2ecdd85d68f962f" dependencies = [ "keyring-lib", - "log", "process-lib", "serde", "thiserror", + "tracing", ] [[package]] name = "secret-service" -version = "3.1.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5204d39df37f06d1944935232fd2dfe05008def7ca599bf28c0800366c8a8f9" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" dependencies = [ "aes", "cbc", @@ -3721,7 +3536,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "num-bigint", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d0283c0a4a22a0f1b0e4edca251aa20b92fc96eaa09b84bec052f9415e9d71" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -3745,9 +3574,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] @@ -3775,20 +3604,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -3814,7 +3643,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -3826,18 +3655,6 @@ 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 = "sha1" version = "0.10.6" @@ -3984,16 +3801,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b8ad3dd187f0d4debab02ad65405a9919d6a4f7bce25bd64a258781063a53a" -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.7" @@ -4060,7 +3867,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -4082,42 +3889,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -4139,45 +3919,45 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", - "fastrand 2.1.1", + "fastrand", "once_cell", - "rustix 0.38.37", + "rustix", "windows-sys 0.59.0", ] [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.37", - "windows-sys 0.48.0", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -4207,9 +3987,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -4217,7 +3997,7 @@ dependencies = [ "mio 1.0.2", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "tokio-macros", "tracing", "windows-sys 0.52.0", @@ -4231,7 +4011,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -4250,24 +4030,11 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.15", "rustls-pki-types", "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.8.19" @@ -4277,7 +4044,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit", ] [[package]] @@ -4289,17 +4056,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.22" @@ -4310,15 +4066,9 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.20", + "winnow", ] -[[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.40" @@ -4338,7 +4088,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -4392,24 +4142,17 @@ dependencies = [ [[package]] name = "tree_magic_mini" -version = "3.1.5" +version = "3.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469a727cac55b41448315cc10427c069c618ac59bb6a4480283fcd811749bdc2" +checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63" dependencies = [ "fnv", - "home", "memchr", "nom", "once_cell", "petgraph", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "twofish" version = "0.7.1" @@ -4438,9 +4181,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -4475,6 +4218,28 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.0.0-rc2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a139c7262528ca928e9a4368281577453ae93953159d7d7b922165bd083049f" +dependencies = [ + "base64 0.22.1", + "cc", + "flate2", + "hoot", + "http", + "log", + "once_cell", + "percent-encoding", + "rustls 0.23.15", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "rustls-platform-verifier", + "utf-8", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.2" @@ -4493,6 +4258,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf7-imap" version = "0.3.2" @@ -4537,12 +4308,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -4553,15 +4318,6 @@ dependencies = [ "winapi-util", ] -[[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.0+wasi-snapshot-preview1" @@ -4570,9 +4326,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -4581,36 +4337,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4618,38 +4362,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "webpki-roots" @@ -4854,15 +4582,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.6.20" @@ -4943,24 +4662,27 @@ checksum = "21bf7b4a78668416e1e8a332334e26fb2f377afe707f0c6feaf6ed5f9100133b" [[package]] name = "zbus" -version = "3.15.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", "async-process", "async-recursion", + "async-task", "async-trait", - "byteorder", - "derivative", + "blocking", "enumflags2", - "event-listener 2.5.3", + "event-listener", "futures-core", "futures-sink", "futures-util", "hex", "nix", - "once_cell", "ordered-stream", "rand", "serde", @@ -4970,7 +4692,7 @@ dependencies = [ "tokio", "tracing", "uds_windows", - "winapi", + "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", @@ -4979,23 +4701,22 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.15.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "regex", - "syn 1.0.109", + "syn 2.0.85", "zvariant_utils", ] [[package]] name = "zbus_names" -version = "2.6.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", @@ -5020,7 +4741,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -5040,18 +4761,17 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] name = "zvariant" -version = "3.15.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ - "byteorder", + "endi", "enumflags2", - "libc", "serde", "static_assertions", "zvariant_derive", @@ -5059,24 +4779,24 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.15.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.85", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.85", ] diff --git a/Cargo.toml b/Cargo.toml index d24d772..7ffb918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,24 +12,22 @@ documentation = "https://github.com/pimalaya/himalaya/" repository = "https://github.com/pimalaya/himalaya/" [package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs", "--document-private-items"] +features = ["imap", "maildir", "smtp", "sendmail", "oauth2", "wizard", "pgp-commands", "pgp-native"] +rustdoc-args = ["--cfg", "docsrs"] [features] default = [ "imap", "maildir", - # "notmuch", + #"notmuch", "smtp", - "sendmail", - - # "keyring", - # "oauth2", + #"sendmail", + #"keyring", + #"oauth2", "wizard", - - # "pgp-commands", - # "pgp-gpg", - # "pgp-native", + #"pgp-commands", + #"pgp-gpg", + #"pgp-native", ] imap = ["email-lib/imap", "pimalaya-tui/imap"] @@ -38,57 +36,32 @@ notmuch = ["email-lib/notmuch", "pimalaya-tui/notmuch"] smtp = ["email-lib/smtp", "pimalaya-tui/smtp"] sendmail = ["email-lib/sendmail", "pimalaya-tui/sendmail"] -keyring = ["email-lib/keyring", "pimalaya-tui/keyring", "secret-lib?/keyring-tokio"] -oauth2 = ["dep:oauth-lib", "email-lib/oauth2", "pimalaya-tui/oauth2", "keyring"] -wizard = ["dep:email_address", "dep:secret-lib", "email-lib/autoconfig"] +keyring = ["email-lib/keyring", "pimalaya-tui/keyring", "secret-lib/keyring"] +oauth2 = ["email-lib/oauth2", "pimalaya-tui/oauth2", "keyring"] +wizard = ["email-lib/autoconfig", "pimalaya-tui/wizard"] pgp = [] -pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pgp"] -pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pgp"] -pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pgp"] +pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pimalaya-tui/pgp-commands", "pgp"] +pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pimalaya-tui/pgp-gpg", "pgp"] +pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pimalaya-tui/pgp-native", "pgp"] [dependencies] ariadne = "0.2" -async-trait = "0.1" clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } clap_complete = "4.4" clap_mangen = "0.2" color-eyre = "0.6" -comfy-table = "7.1" -crossterm = { version = "0.27", features = ["serde"] } -dirs = "4" -email-lib = { version = "=0.25.0", default-features = false, features = ["derive", "thread", "tracing"] } -email_address = { version = "0.2", optional = true } +email-lib = { version = "=0.26", default-features = false, features = ["tokio-rustls", "derive", "thread"] } mail-builder = "0.3" -md5 = "0.7" -mml-lib = { version = "=1.0.14", default-features = false, features = ["derive"] } -oauth-lib = { version = "=0.1.1", optional = true } +mml-lib = { version = "1", default-features = false, features = ["compiler", "interpreter", "derive"] } once_cell = "1.16" -petgraph = "0.6" -pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path", "cli", "config", "tracing"] } -process-lib = { version = "=0.4.2", features = ["derive"] } -secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true } +pimalaya-tui = { version = "=0.1", default-features = false, features = ["email", "path", "cli", "himalaya", "tracing", "sled"] } +secret-lib = { version = "1", default-features = false, features = ["tokio", "rustls", "command", "derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" shellexpand-utils = "=0.2.1" -sled = "=0.34.7" tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] } toml = "0.8" tracing = "0.1" url = "2.2" uuid = { version = "0.8", features = ["v4"] } - -[patch.crates-io] -# IMAP -imap-next = { git = "https://github.com/duesee/imap-next" } -imap-client = { git = "https://github.com/pimalaya/imap-client" } - -# Pimalaya core -email-lib = { git = "https://github.com/pimalaya/core" } -keyring-lib = { git = "https://github.com/pimalaya/core" } -oauth-lib = { git = "https://github.com/pimalaya/core" } -process-lib = { git = "https://github.com/pimalaya/core" } -secret-lib = { git = "https://github.com/pimalaya/core" } - -# Pimalaya TUI -pimalaya-tui = { git = "https://github.com/pimalaya/tui" } diff --git a/README.md b/README.md index f68c967..51ed3e4 100644 --- a/README.md +++ b/README.md @@ -621,12 +621,13 @@ You can also manually edit your own configuration, from scratch: ## Sponsoring -[![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/project/Himalaya/index.html) +[![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/) -Special thanks to the [NLnet foundation](https://nlnet.nl/project/Himalaya/index.html) and the [European Commission](https://www.ngi.eu/) that helped the project to receive financial support from: +Special thanks to the [NLnet foundation](https://nlnet.nl/) and the [European Commission](https://www.ngi.eu/) that helped the project to receive financial support from various programs: -- [NGI Assure](https://nlnet.nl/assure/) in 2022 -- [NGI Zero Entrust](https://nlnet.nl/entrust/) in 2023 +- [NGI Assure](https://nlnet.nl/project/Himalaya/) in 2022 +- [NGI Zero Entrust](https://nlnet.nl/project/Pimalaya/) in 2023 +- [NGI Zero Core](https://nlnet.nl/project/Pimalaya-PIM/) in 2024 *(still ongoing)* If you appreciate the project, feel free to donate using one of the following providers: diff --git a/config.sample.toml b/config.sample.toml index a24293f..4dc72c0 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -288,13 +288,80 @@ template.forward.signature-style = "inlined" template.forward.quote-headline = "-------- Forwarded Message --------\n" ######################################## -#### PGP configuration ################# +#### GPG-based PGP configuration ####### ######################################## -# TODO -#pgp.backend = "commands" -#pgp.backend = "gpg" -#pgp.backend = "native" +# Enables PGP using GPG bindings. It requires the GPG lib to be +# installed on the system, and the `pgp-gpg` cargo feature on. +# +#pgp.type = "gpg" + +######################################## +#### Command-based PGP configuration ### +######################################## + +# Enables PGP using shell commands. A PGP client needs to be installed +# on the system, like gpg. It also requires the `pgp-commands` cargo +# feature. +# +#pgp.type = "commands" + +# Defines the encrypt command. The special placeholder `` +# represents the list of recipients, formatted by +# `pgp.encrypt-recipient-fmt`. +# +#pgp.encrypt-cmd = "gpg --encrypt --quiet --armor " + +# Formats recipients for `pgp.encrypt-cmd`. The special placeholder +# `` is replaced by an actual recipient at runtime. +# +#pgp.encrypt-recipient-fmt = "--recipient " + +# Defines the separator used between formatted recipients +# `pgp.encrypt-recipient-fmt`. +# +#pgp.encrypt-recipients-sep = " " + +# Defines the decrypt command. +# +#pgp.decrypt-cmd = "gpg --decrypt --quiet" + +# Defines the sign command. +# +#pgp.sign-cmd = "gpg --sign --quiet --armor" + +# Defines the verify command. +# +#pgp.verify-cmd = "gpg --verify --quiet" + +######################################## +#### Native PGP configuration ########## +######################################## + +# Enables the native Rust implementation of PGP. It requires the +# `pgp-native` cargo feature. +# +#pgp.type = "native" + +# Defines where to find the PGP secret key. +# +#pgp.secret-key.path = "/path/to/secret.key" +#pgp.secret-key.keyring = "my-pgp-secret-key" + +# Defines how to retrieve the PGP secret key passphrase. +# +#pgp.secret-key-passphrase.raw = "p@assw0rd" +#pgp.secret-key-passphrase.keyring = "my-pgp-passphrase" +#pgp.secret-key-passphrase.cmd = "pass show pgp-passphrase" + +# Enables the Web Key Discovery protocol to discover recipients' +# public key based on their email address. +# +#pgp.wkd = true + +# Enables public key servers discovery. +# +#pgp.key-servers = ["hkps://keys.openpgp.org", "hkps://keys.mailvelope.com"] ######################################## #### IMAP configuration ################ @@ -302,54 +369,54 @@ template.forward.quote-headline = "-------- Forwarded Message --------\n" # Defines the IMAP backend as the default one for all features. # -backend = "imap" +backend.type = "imap" # IMAP server host name. # -imap.host = "localhost" +backend.host = "localhost" # IMAP server port. # -#imap.port = 143 -imap.port = 993 +#backend.port = 143 +backend.port = 993 # IMAP server encryption. # -#imap.encryption = "none" # or false -#imap.encryption = "start-tls" -imap.encryption = "tls" # or true +#backend.encryption = "none" # or false +#backend.encryption = "start-tls" +backend.encryption = "tls" # or true # IMAP server login. # -imap.login = "example@localhost" +backend.login = "example@localhost" # IMAP server password authentication configuration. # -imap.auth.type = "password" +backend.auth.type = "password" # # Password can be inlined (not recommended). # -#imap.auth.raw = "p@assw0rd" +#backend.auth.raw = "p@assw0rd" # # Password can be stored inside your system global keyring (requires # the keyring cargo feature). You must run at least once `himalaya # account configure` to set up the password. # -#imap.auth.keyring = "example-imap" +#backend.auth.keyring = "example-imap" # # Password can be retrieved from a shell command. # -imap.auth.cmd = "pass show example-imap" +backend.auth.cmd = "pass show example-imap" # IMAP server OAuth 2.0 authorization configuration. # -#imap.auth.type = "oauth2" +#backend.auth.type = "oauth2" # # Client identifier issued to the client during the registration # process described in RFC6749. # See . # -#imap.auth.client-id = "client-id" +#backend.auth.client-id = "client-id" # # Client password issued to the client during the registration process # described in RFC6749. @@ -357,23 +424,23 @@ imap.auth.cmd = "pass show example-imap" # Defaults to keyring "-imap-client-secret". # See . # -#imap.auth.client-secret.raw = "" -#imap.auth.client-secret.keyring = "example-imap-client-secret" -#imap.auth.client-secret.cmd = "pass show example-imap-client-secret" +#backend.auth.client-secret.raw = "" +#backend.auth.client-secret.keyring = "example-imap-client-secret" +#backend.auth.client-secret.cmd = "pass show example-imap-client-secret" # # Method for presenting an OAuth 2.0 bearer token to a service for # authentication. # -#imap.auth.method = "oauthbearer" -#imap.auth.method = "xoauth2" +#backend.auth.method = "oauthbearer" +#backend.auth.method = "xoauth2" # # URL of the authorization server's authorization endpoint. # -#imap.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth" +#backend.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth" # # URL of the authorization server's token endpoint. # -#imap.auth.token-url = "https://www.googleapis.com/oauth2/v3/token" +#backend.auth.token-url = "https://www.googleapis.com/oauth2/v3/token" # # Access token returned by the token endpoint and used to access # protected resources. It is recommended to use the keyring variant, @@ -381,9 +448,9 @@ imap.auth.cmd = "pass show example-imap" # # Defaults to keyring "-imap-access-token". # -#imap.auth.access-token.raw = "" -#imap.auth.access-token.keyring = "example-imap-access-token" -#imap.auth.access-token.cmd = "pass show example-imap-access-token" +#backend.auth.access-token.raw = "" +#backend.auth.access-token.keyring = "example-imap-access-token" +#backend.auth.access-token.cmd = "pass show example-imap-access-token" # # Refresh token used to obtain a new access token (if supported by the # authorization server). It is recommended to use the keyring variant, @@ -391,30 +458,35 @@ imap.auth.cmd = "pass show example-imap" # # Defaults to keyring "-imap-refresh-token". # -#imap.auth.refresh-token.raw = "" -#imap.auth.refresh-token.keyring = "example-imap-refresh-token" -#imap.auth.refresh-token.cmd = "pass show example-imap-refresh-token" +#backend.auth.refresh-token.raw = "" +#backend.auth.refresh-token.keyring = "example-imap-refresh-token" +#backend.auth.refresh-token.cmd = "pass show example-imap-refresh-token" # # Enable the protection, as defined in RFC7636. # # See . # -#imap.auth.pkce = true +#backend.auth.pkce = true # # Access token scope(s), as defined by the authorization server. # -#imap.auth.scope = "unique scope" -#imap.auth.scopes = ["multiple", "scopes"] +#backend.auth.scope = "unique scope" +#backend.auth.scopes = ["multiple", "scopes"] +# +# URL scheme of the redirect server. +# Defaults to http. +# +#backend.auth.redirect-scheme = "http" # # Host name of the redirect server. # Defaults to localhost. # -#imap.auth.redirect-host = "localhost" +#backend.auth.redirect-host = "localhost" # # Port of the redirect server. # Defaults to the first available one. # -#imap.auth.redirect-port = 9999 +#backend.auth.redirect-port = 9999 ######################################## #### Maildir configuration ############# @@ -422,18 +494,18 @@ imap.auth.cmd = "pass show example-imap" # Defines the Maildir backend as the default one for all features. # -#backend = "maildir" +#backend.type = "maildir" # The Maildir root directory. The path should point to the root level # of the Maildir directory. # -#maildir.root-dir = "~/.Mail/example" +#backend.root-dir = "~/.Mail/example" # Does the Maildir folder follows the Maildir++ standard? # # See . # -#maildir.maildirpp = false +#backend.maildirpp = false ######################################## #### Notmuch configuration ############# @@ -441,25 +513,25 @@ imap.auth.cmd = "pass show example-imap" # Defines the Notmuch backend as the default one for all features. # -#backend = "notmuch" +#backend.type = "notmuch" # The path to the Notmuch database. The path should point to the root # directory containing the Notmuch database (usually the root Maildir # directory). # -#notmuch.db-path = "~/.Mail/example" +#backend.db-path = "~/.Mail/example" # Overrides the default path to the Maildir folder. # -#notmuch.maildir-path = "~/.Mail/example" +#backend.maildir-path = "~/.Mail/example" # Overrides the default Notmuch configuration file path. # -#notmuch.config-path = "~/.notmuchrc" +#backend.config-path = "~/.notmuchrc" # Override the default Notmuch profile name. # -#notmuch.profile = "example" +#backend.profile = "example" ######################################## #### SMTP configuration ################ @@ -467,55 +539,55 @@ imap.auth.cmd = "pass show example-imap" # Defines the SMTP backend for the message sending feature. # -message.send.backend = "smtp" +message.send.backend.type = "smtp" # SMTP server host name. # -smtp.host = "localhost" +message.send.backend.host = "localhost" # SMTP server port. # -#smtp.port = 25 -#smtp.port = 465 -smtp.port = 587 +#message.send.backend.port = 25 +#message.send.backend.port = 465 +message.send.backend.port = 587 # SMTP server encryption. # -#smtp.encryption = "none" # or false -#smtp.encryption = "start-tls" -smtp.encryption = "tls" # or true +#message.send.backend.encryption = "none" # or false +#message.send.backend.encryption = "start-tls" +message.send.backend.encryption = "tls" # or true # SMTP server login. # -smtp.login = "example@localhost" +message.send.backend.login = "example@localhost" # SMTP server password authentication configuration. # -smtp.auth.type = "password" +message.send.backend.auth.type = "password" # # Password can be inlined (not recommended). # -#smtp.auth.raw = "p@assw0rd" +#message.send.backend.auth.raw = "p@assw0rd" # # Password can be stored inside your system global keyring (requires # the keyring cargo feature). You must run at least once `himalaya # account configure` to set up the password. # -#smtp.auth.keyring = "example-smtp" +#message.send.backend.auth.keyring = "example-smtp" # # Password can be retrieved from a shell command. # -smtp.auth.cmd = "pass show example-smtp" +message.send.backend.auth.cmd = "pass show example-smtp" # SMTP server OAuth 2.0 authorization configuration. # -#smtp.auth.type = "oauth2" +#message.send.backend.auth.type = "oauth2" # # Client identifier issued to the client during the registration # process described in RFC6749. # See . # -#smtp.auth.client-id = "client-id" +#message.send.backend.auth.client-id = "client-id" # # Client password issued to the client during the registration process # described in RFC6749. @@ -523,23 +595,23 @@ smtp.auth.cmd = "pass show example-smtp" # Defaults to keyring "-smtp-client-secret". # See . # -#smtp.auth.client-secret.raw = "" -#smtp.auth.client-secret.keyring = "example-smtp-client-secret" -#smtp.auth.client-secret.cmd = "pass show example-smtp-client-secret" +#message.send.backend.auth.client-secret.raw = "" +#message.send.backend.auth.client-secret.keyring = "example-smtp-client-secret" +#message.send.backend.auth.client-secret.cmd = "pass show example-smtp-client-secret" # # Method for presenting an OAuth 2.0 bearer token to a service for # authentication. # -#smtp.auth.method = "oauthbearer" -#smtp.auth.method = "xoauth2" +#message.send.backend.auth.method = "oauthbearer" +#message.send.backend.auth.method = "xoauth2" # # URL of the authorization server's authorization endpoint. # -#smtp.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth" +#message.send.backend.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth" # # URL of the authorization server's token endpoint. # -#smtp.auth.token-url = "https://www.googleapis.com/oauth2/v3/token" +#message.send.backend.auth.token-url = "https://www.googleapis.com/oauth2/v3/token" # # Access token returned by the token endpoint and used to access # protected resources. It is recommended to use the keyring variant, @@ -547,9 +619,9 @@ smtp.auth.cmd = "pass show example-smtp" # # Defaults to keyring "-smtp-access-token". # -#smtp.auth.access-token.raw = "" -#smtp.auth.access-token.keyring = "example-smtp-access-token" -#smtp.auth.access-token.cmd = "pass show example-smtp-access-token" +#message.send.backend.auth.access-token.raw = "" +#message.send.backend.auth.access-token.keyring = "example-smtp-access-token" +#message.send.backend.auth.access-token.cmd = "pass show example-smtp-access-token" # # Refresh token used to obtain a new access token (if supported by the # authorization server). It is recommended to use the keyring variant, @@ -557,30 +629,35 @@ smtp.auth.cmd = "pass show example-smtp" # # Defaults to keyring "-smtp-refresh-token". # -#smtp.auth.refresh-token.raw = "" -#smtp.auth.refresh-token.keyring = "example-smtp-refresh-token" -#smtp.auth.refresh-token.cmd = "pass show example-smtp-refresh-token" +#message.send.backend.auth.refresh-token.raw = "" +#message.send.backend.auth.refresh-token.keyring = "example-smtp-refresh-token" +#message.send.backend.auth.refresh-token.cmd = "pass show example-smtp-refresh-token" # # Enable the protection, as defined in RFC7636. # # See . # -#smtp.auth.pkce = true +#message.send.backend.auth.pkce = true # # Access token scope(s), as defined by the authorization server. # -#smtp.auth.scope = "unique scope" -#smtp.auth.scopes = ["multiple", "scopes"] +#message.send.backend.auth.scope = "unique scope" +#message.send.backend.auth.scopes = ["multiple", "scopes"] +# +# URL scheme of the redirect server. +# Defaults to http. +# +#message.send.backend.auth.redirect-scheme = "http" # # Host name of the redirect server. # Defaults to localhost. # -#smtp.auth.redirect-host = "localhost" +#message.send.backend.auth.redirect-host = "localhost" # # Port of the redirect server. # Defaults to the first available one. # -#smtp.auth.redirect-port = 9999 +#message.send.backend.auth.redirect-port = 9999 ######################################## #### Sendmail configuration ############ @@ -588,8 +665,8 @@ smtp.auth.cmd = "pass show example-smtp" # Defines the Sendmail backend for the message sending feature. # -#message.send.backend = "sendmail" +#message.send.backend.type = "sendmail" # Customizes the sendmail shell command. # -#sendmail.cmd = "/usr/bin/sendmail" +#message.send.backend.cmd = "/usr/bin/sendmail" diff --git a/src/account/command/check_up.rs b/src/account/command/check_up.rs index 803bd47..72f4d12 100644 --- a/src/account/command/check_up.rs +++ b/src/account/command/check_up.rs @@ -1,11 +1,25 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; -use email::backend::context::BackendContextBuilder; +use email::backend::BackendBuilder; +#[cfg(feature = "imap")] +use email::imap::ImapContextBuilder; +#[cfg(feature = "maildir")] +use email::maildir::MaildirContextBuilder; +#[cfg(feature = "notmuch")] +use email::notmuch::NotmuchContextBuilder; +#[cfg(feature = "sendmail")] +use email::sendmail::SendmailContextBuilder; +#[cfg(feature = "smtp")] +use email::smtp::SmtpContextBuilder; +use pimalaya_tui::{ + himalaya::config::{Backend, SendingBackend}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; -use crate::{ - account::arg::name::OptionalAccountNameArg, backend, config::Config, printer::Printer, -}; +use crate::{account::arg::name::OptionalAccountNameArg, config::TomlConfig}; /// Check up the given account. /// @@ -19,102 +33,77 @@ pub struct AccountCheckUpCommand { } impl AccountCheckUpCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing check up account command"); let account = self.account.name.as_ref().map(String::as_str); - printer.log("Checking configuration integrity…")?; + printer.log("Checking configuration integrity…\n")?; let (toml_account_config, account_config) = config.clone().into_account_configs(account)?; - let used_backends = toml_account_config.get_used_backends(); + let account_config = Arc::new(account_config); - printer.log("Checking backend context integrity…")?; + match toml_account_config.backend { + #[cfg(feature = "maildir")] + Some(Backend::Maildir(mdir_config)) => { + printer.log("Checking Maildir integrity…\n")?; - let ctx_builder = backend::BackendContextBuilder::new( - toml_account_config.clone(), - account_config, - Vec::from_iter(used_backends), - ) - .await?; - - let ctx = ctx_builder.clone().build().await?; - - #[cfg(feature = "maildir")] - { - printer.log("Checking Maildir integrity…")?; - - let maildir = ctx_builder - .maildir - .as_ref() - .and_then(|maildir| maildir.check_up()) - .and_then(|f| ctx.maildir.as_ref().and_then(|ctx| f(ctx))); - - if let Some(maildir) = maildir.as_ref() { - maildir.check_up().await?; + let ctx = MaildirContextBuilder::new(account_config.clone(), Arc::new(mdir_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; } + #[cfg(feature = "imap")] + Some(Backend::Imap(imap_config)) => { + printer.log("Checking IMAP integrity…\n")?; + + let ctx = ImapContextBuilder::new(account_config.clone(), Arc::new(imap_config)) + .with_pool_size(1); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; + } + #[cfg(feature = "notmuch")] + Some(Backend::Notmuch(notmuch_config)) => { + printer.log("Checking Notmuch integrity…\n")?; + + let ctx = + NotmuchContextBuilder::new(account_config.clone(), Arc::new(notmuch_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; + } + _ => (), } - #[cfg(feature = "imap")] - { - printer.log("Checking IMAP integrity…")?; + let sending_backend = toml_account_config + .message + .and_then(|msg| msg.send) + .and_then(|send| send.backend); - let imap = ctx_builder - .imap - .as_ref() - .and_then(|imap| imap.check_up()) - .and_then(|f| ctx.imap.as_ref().and_then(|ctx| f(ctx))); + match sending_backend { + #[cfg(feature = "smtp")] + Some(SendingBackend::Smtp(smtp_config)) => { + printer.log("Checking SMTP integrity…\n")?; - if let Some(imap) = imap.as_ref() { - imap.check_up().await?; + let ctx = SmtpContextBuilder::new(account_config.clone(), Arc::new(smtp_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; } + #[cfg(feature = "sendmail")] + Some(SendingBackend::Sendmail(sendmail_config)) => { + printer.log("Checking Sendmail integrity…\n")?; + + let ctx = + SendmailContextBuilder::new(account_config.clone(), Arc::new(sendmail_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; + } + _ => (), } - #[cfg(feature = "notmuch")] - { - printer.log("Checking Notmuch integrity…")?; - - let notmuch = ctx_builder - .notmuch - .as_ref() - .and_then(|notmuch| notmuch.check_up()) - .and_then(|f| ctx.notmuch.as_ref().and_then(|ctx| f(ctx))); - - if let Some(notmuch) = notmuch.as_ref() { - notmuch.check_up().await?; - } - } - - #[cfg(feature = "smtp")] - { - printer.log("Checking SMTP integrity…")?; - - let smtp = ctx_builder - .smtp - .as_ref() - .and_then(|smtp| smtp.check_up()) - .and_then(|f| ctx.smtp.as_ref().and_then(|ctx| f(ctx))); - - if let Some(smtp) = smtp.as_ref() { - smtp.check_up().await?; - } - } - - #[cfg(feature = "sendmail")] - { - printer.log("Checking Sendmail integrity…")?; - - let sendmail = ctx_builder - .sendmail - .as_ref() - .and_then(|sendmail| sendmail.check_up()) - .and_then(|f| ctx.sendmail.as_ref().and_then(|ctx| f(ctx))); - - if let Some(sendmail) = sendmail.as_ref() { - sendmail.check_up().await?; - } - } - - printer.out("Checkup successfully completed!") + printer.out("Checkup successfully completed!\n") } } diff --git a/src/account/command/configure.rs b/src/account/command/configure.rs index bb40e83..1d01e42 100644 --- a/src/account/command/configure.rs +++ b/src/account/command/configure.rs @@ -5,12 +5,13 @@ use email::imap::config::ImapAuthConfig; #[cfg(feature = "smtp")] use email::smtp::config::SmtpAuthConfig; #[cfg(any(feature = "imap", feature = "smtp", feature = "pgp"))] -use pimalaya_tui::prompt; +use pimalaya_tui::terminal::prompt; +use pimalaya_tui::terminal::{cli::printer::Printer, config::TomlConfig as _}; use tracing::info; #[cfg(any(feature = "imap", feature = "smtp"))] use tracing::{debug, warn}; -use crate::{account::arg::name::AccountNameArg, config::Config, printer::Printer}; +use crate::{account::arg::name::AccountNameArg, config::TomlConfig}; /// Configure an account. /// @@ -31,20 +32,22 @@ pub struct AccountConfigureCommand { } impl AccountConfigureCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing configure account command"); let account = &self.account.name; - let (_, account_config) = config.into_toml_account_config(Some(account))?; + let (_, toml_account_config) = config.to_toml_account_config(Some(account))?; if self.reset { #[cfg(feature = "imap")] - if let Some(ref config) = account_config.imap { - let reset = match &config.auth { - ImapAuthConfig::Passwd(config) => config.reset().await, + { + let reset = match toml_account_config.imap_auth_config() { + Some(ImapAuthConfig::Password(config)) => config.reset().await, #[cfg(feature = "oauth2")] - ImapAuthConfig::OAuth2(config) => config.reset().await, + Some(ImapAuthConfig::OAuth2(config)) => config.reset().await, + _ => Ok(()), }; + if let Err(err) = reset { warn!("error while resetting imap secrets: {err}"); debug!("error while resetting imap secrets: {err:?}"); @@ -52,12 +55,14 @@ impl AccountConfigureCommand { } #[cfg(feature = "smtp")] - if let Some(ref config) = account_config.smtp { - let reset = match &config.auth { - SmtpAuthConfig::Passwd(config) => config.reset().await, + { + let reset = match toml_account_config.smtp_auth_config() { + Some(SmtpAuthConfig::Password(config)) => config.reset().await, #[cfg(feature = "oauth2")] - SmtpAuthConfig::OAuth2(config) => config.reset().await, + Some(SmtpAuthConfig::OAuth2(config)) => config.reset().await, + _ => Ok(()), }; + if let Err(err) = reset { warn!("error while resetting smtp secrets: {err}"); debug!("error while resetting smtp secrets: {err:?}"); @@ -65,56 +70,54 @@ impl AccountConfigureCommand { } #[cfg(feature = "pgp")] - if let Some(ref config) = account_config.pgp { + if let Some(config) = &toml_account_config.pgp { config.reset().await?; } } #[cfg(feature = "imap")] - if let Some(ref config) = account_config.imap { - match &config.auth { - ImapAuthConfig::Passwd(config) => { - config - .configure(|| Ok(prompt::password("IMAP password")?)) - .await - } - #[cfg(feature = "oauth2")] - ImapAuthConfig::OAuth2(config) => { - config - .configure(|| Ok(prompt::secret("IMAP OAuth 2.0 clientsecret")?)) - .await - } - }?; - } + match toml_account_config.imap_auth_config() { + Some(ImapAuthConfig::Password(config)) => { + config + .configure(|| Ok(prompt::password("IMAP password")?)) + .await + } + #[cfg(feature = "oauth2")] + Some(ImapAuthConfig::OAuth2(config)) => { + config + .configure(|| Ok(prompt::secret("IMAP OAuth 2.0 client secret")?)) + .await + } + _ => Ok(()), + }?; #[cfg(feature = "smtp")] - if let Some(ref config) = account_config.smtp { - match &config.auth { - SmtpAuthConfig::Passwd(config) => { - config - .configure(|| Ok(prompt::password("SMTP password")?)) - .await - } - #[cfg(feature = "oauth2")] - SmtpAuthConfig::OAuth2(config) => { - config - .configure(|| Ok(prompt::secret("SMTP OAuth 2.0 client secret")?)) - .await - } - }?; - } + match toml_account_config.smtp_auth_config() { + Some(SmtpAuthConfig::Password(config)) => { + config + .configure(|| Ok(prompt::password("SMTP password")?)) + .await + } + #[cfg(feature = "oauth2")] + Some(SmtpAuthConfig::OAuth2(config)) => { + config + .configure(|| Ok(prompt::secret("SMTP OAuth 2.0 client secret")?)) + .await + } + _ => Ok(()), + }?; #[cfg(feature = "pgp")] - if let Some(ref config) = account_config.pgp { + if let Some(config) = &toml_account_config.pgp { config - .configure(&account_config.email, || { + .configure(&toml_account_config.email, || { Ok(prompt::password("PGP secret key password")?) }) .await?; } printer.out(format!( - "Account {account} successfully {}configured!", + "Account {account} successfully {}configured!\n", if self.reset { "re" } else { "" } )) } diff --git a/src/account/command/list.rs b/src/account/command/list.rs index a454b34..a387729 100644 --- a/src/account/command/list.rs +++ b/src/account/command/list.rs @@ -1,12 +1,12 @@ use clap::Parser; use color_eyre::Result; +use pimalaya_tui::{ + himalaya::config::{Accounts, AccountsTable}, + terminal::cli::printer::Printer, +}; use tracing::info; -use crate::{ - account::{Accounts, AccountsTable}, - config::Config, - printer::Printer, -}; +use crate::config::TomlConfig; /// List all accounts. /// @@ -24,7 +24,7 @@ pub struct AccountListCommand { } impl AccountListCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing list accounts command"); let accounts = Accounts::from(config.accounts.iter()); diff --git a/src/account/command/mod.rs b/src/account/command/mod.rs index dca5739..444a456 100644 --- a/src/account/command/mod.rs +++ b/src/account/command/mod.rs @@ -4,8 +4,9 @@ mod list; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::{ check_up::AccountCheckUpCommand, configure::AccountConfigureCommand, list::AccountListCommand, @@ -30,7 +31,7 @@ pub enum AccountSubcommand { impl AccountSubcommand { #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::CheckUp(cmd) => cmd.execute(printer, config).await, Self::Configure(cmd) => cmd.execute(printer, config).await, diff --git a/src/account/config.rs b/src/account/config.rs index 6581130..daf6f38 100644 --- a/src/account/config.rs +++ b/src/account/config.rs @@ -1,363 +1,3 @@ -//! Deserialized account config module. -//! -//! This module contains the raw deserialized representation of an -//! account in the accounts section of the user configuration file. +use pimalaya_tui::himalaya::config::HimalayaTomlAccountConfig; -use comfy_table::presets; -use crossterm::style::Color; -#[cfg(feature = "pgp")] -use email::account::config::pgp::PgpConfig; -#[cfg(feature = "imap")] -use email::imap::config::ImapConfig; -#[cfg(feature = "maildir")] -use email::maildir::config::MaildirConfig; -#[cfg(feature = "notmuch")] -use email::notmuch::config::NotmuchConfig; -#[cfg(feature = "sendmail")] -use email::sendmail::config::SendmailConfig; -#[cfg(feature = "smtp")] -use email::smtp::config::SmtpConfig; -use email::template::config::TemplateConfig; -use serde::{Deserialize, Serialize}; -use std::{collections::HashSet, path::PathBuf}; - -use crate::{ - backend::BackendKind, envelope::config::EnvelopeConfig, flag::config::FlagConfig, - folder::config::FolderConfig, message::config::MessageConfig, ui::map_color, -}; - -/// Represents all existing kind of account config. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct TomlAccountConfig { - pub default: Option, - pub email: String, - pub display_name: Option, - pub signature: Option, - pub signature_delim: Option, - pub downloads_dir: Option, - pub backend: Option, - - #[cfg(feature = "pgp")] - pub pgp: Option, - - pub folder: Option, - pub envelope: Option, - pub flag: Option, - pub message: Option, - pub template: Option, - - #[cfg(feature = "imap")] - pub imap: Option, - #[cfg(feature = "maildir")] - pub maildir: Option, - #[cfg(feature = "notmuch")] - pub notmuch: Option, - #[cfg(feature = "smtp")] - pub smtp: Option, - #[cfg(feature = "sendmail")] - pub sendmail: Option, -} - -impl TomlAccountConfig { - pub fn folder_list_table_preset(&self) -> Option { - self.folder - .as_ref() - .and_then(|folder| folder.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.preset.clone()) - } - - pub fn folder_list_table_name_color(&self) -> Option { - self.folder - .as_ref() - .and_then(|folder| folder.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.name_color) - } - - pub fn folder_list_table_desc_color(&self) -> Option { - self.folder - .as_ref() - .and_then(|folder| folder.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.desc_color) - } - - pub fn envelope_list_table_preset(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.preset.clone()) - } - - pub fn envelope_list_table_unseen_char(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.unseen_char) - } - - pub fn envelope_list_table_replied_char(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.replied_char) - } - - pub fn envelope_list_table_flagged_char(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.flagged_char) - } - - pub fn envelope_list_table_attachment_char(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.attachment_char) - } - - pub fn envelope_list_table_id_color(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.id_color) - } - - pub fn envelope_list_table_flags_color(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.flags_color) - } - - pub fn envelope_list_table_subject_color(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.subject_color) - } - - pub fn envelope_list_table_sender_color(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.sender_color) - } - - pub fn envelope_list_table_date_color(&self) -> Option { - self.envelope - .as_ref() - .and_then(|env| env.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.date_color) - } - - pub fn add_folder_kind(&self) -> Option<&BackendKind> { - self.folder - .as_ref() - .and_then(|folder| folder.add.as_ref()) - .and_then(|add| add.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn list_folders_kind(&self) -> Option<&BackendKind> { - self.folder - .as_ref() - .and_then(|folder| folder.list.as_ref()) - .and_then(|list| list.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn expunge_folder_kind(&self) -> Option<&BackendKind> { - self.folder - .as_ref() - .and_then(|folder| folder.expunge.as_ref()) - .and_then(|expunge| expunge.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn purge_folder_kind(&self) -> Option<&BackendKind> { - self.folder - .as_ref() - .and_then(|folder| folder.purge.as_ref()) - .and_then(|purge| purge.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn delete_folder_kind(&self) -> Option<&BackendKind> { - self.folder - .as_ref() - .and_then(|folder| folder.delete.as_ref()) - .and_then(|delete| delete.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn get_envelope_kind(&self) -> Option<&BackendKind> { - self.envelope - .as_ref() - .and_then(|envelope| envelope.get.as_ref()) - .and_then(|get| get.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn list_envelopes_kind(&self) -> Option<&BackendKind> { - self.envelope - .as_ref() - .and_then(|envelope| envelope.list.as_ref()) - .and_then(|list| list.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn thread_envelopes_kind(&self) -> Option<&BackendKind> { - self.envelope - .as_ref() - .and_then(|envelope| envelope.thread.as_ref()) - .and_then(|thread| thread.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn add_flags_kind(&self) -> Option<&BackendKind> { - self.flag - .as_ref() - .and_then(|flag| flag.add.as_ref()) - .and_then(|add| add.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn set_flags_kind(&self) -> Option<&BackendKind> { - self.flag - .as_ref() - .and_then(|flag| flag.set.as_ref()) - .and_then(|set| set.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn remove_flags_kind(&self) -> Option<&BackendKind> { - self.flag - .as_ref() - .and_then(|flag| flag.remove.as_ref()) - .and_then(|remove| remove.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn add_message_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|msg| msg.write.as_ref()) - .and_then(|add| add.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn peek_messages_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|message| message.peek.as_ref()) - .and_then(|peek| peek.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn get_messages_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|message| message.read.as_ref()) - .and_then(|get| get.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn copy_messages_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|message| message.copy.as_ref()) - .and_then(|copy| copy.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn move_messages_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|message| message.r#move.as_ref()) - .and_then(|move_| move_.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn delete_messages_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|message| message.delete.as_ref()) - .and_then(|delete| delete.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn send_message_kind(&self) -> Option<&BackendKind> { - self.message - .as_ref() - .and_then(|msg| msg.send.as_ref()) - .and_then(|send| send.backend.as_ref()) - .or(self.backend.as_ref()) - } - - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut used_backends = HashSet::default(); - - if let Some(ref kind) = self.backend { - used_backends.insert(kind); - } - - if let Some(ref folder) = self.folder { - used_backends.extend(folder.get_used_backends()); - } - - if let Some(ref envelope) = self.envelope { - used_backends.extend(envelope.get_used_backends()); - } - - if let Some(ref flag) = self.flag { - used_backends.extend(flag.get_used_backends()); - } - - if let Some(ref msg) = self.message { - used_backends.extend(msg.get_used_backends()); - } - - used_backends - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ListAccountsTableConfig { - pub preset: Option, - pub name_color: Option, - pub backends_color: Option, - pub default_color: Option, -} - -impl ListAccountsTableConfig { - pub fn preset(&self) -> &str { - self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) - } - - pub fn name_color(&self) -> comfy_table::Color { - map_color(self.name_color.unwrap_or(Color::Green)) - } - - pub fn backends_color(&self) -> comfy_table::Color { - map_color(self.backends_color.unwrap_or(Color::Blue)) - } - - pub fn default_color(&self) -> comfy_table::Color { - map_color(self.default_color.unwrap_or(Color::Reset)) - } -} +pub type TomlAccountConfig = HimalayaTomlAccountConfig; diff --git a/src/account/mod.rs b/src/account/mod.rs index d058a05..7b1d26b 100644 --- a/src/account/mod.rs +++ b/src/account/mod.rs @@ -1,198 +1,3 @@ pub mod arg; pub mod command; pub mod config; -#[cfg(feature = "wizard")] -pub(crate) mod wizard; - -use comfy_table::{Cell, ContentArrangement, Row, Table}; -use crossterm::style::Color; -use serde::{Serialize, Serializer}; -use std::{collections::hash_map::Iter, fmt, ops::Deref}; - -use self::config::{ListAccountsTableConfig, TomlAccountConfig}; - -/// Represents the printable account. -#[derive(Debug, Default, PartialEq, Eq, Serialize)] -pub struct Account { - /// Represents the account name. - pub name: String, - /// Represents the backend name of the account. - pub backend: String, - /// Represents the default state of the account. - pub default: bool, -} - -impl Account { - pub fn new(name: &str, backend: &str, default: bool) -> Self { - Self { - name: name.into(), - backend: backend.into(), - default, - } - } - - pub fn to_row(&self, config: &ListAccountsTableConfig) -> Row { - let mut row = Row::new(); - row.max_height(1); - - row.add_cell(Cell::new(&self.name).fg(config.name_color())); - row.add_cell(Cell::new(&self.backend).fg(config.backends_color())); - row.add_cell(Cell::new(if self.default { "yes" } else { "" }).fg(config.default_color())); - - row - } -} - -impl fmt::Display for Account { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.name) - } -} - -/// Represents the list of printable accounts. -#[derive(Debug, Default, Serialize)] -pub struct Accounts(Vec); - -impl Deref for Accounts { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From> for Accounts { - fn from(map: Iter<'_, String, TomlAccountConfig>) -> Self { - let mut accounts: Vec<_> = map - .map(|(name, account)| { - #[allow(unused_mut)] - let mut backends = String::new(); - - #[cfg(feature = "imap")] - if account.imap.is_some() { - backends.push_str("imap"); - } - - #[cfg(feature = "maildir")] - if account.maildir.is_some() { - if !backends.is_empty() { - backends.push_str(", ") - } - backends.push_str("maildir"); - } - - #[cfg(feature = "notmuch")] - if account.notmuch.is_some() { - if !backends.is_empty() { - backends.push_str(", ") - } - backends.push_str("notmuch"); - } - - #[cfg(feature = "smtp")] - if account.smtp.is_some() { - if !backends.is_empty() { - backends.push_str(", ") - } - backends.push_str("smtp"); - } - - #[cfg(feature = "sendmail")] - if account.sendmail.is_some() { - if !backends.is_empty() { - backends.push_str(", ") - } - backends.push_str("sendmail"); - } - - Account::new(name, &backends, account.default.unwrap_or_default()) - }) - .collect(); - - // sort accounts by name - accounts.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap()); - - Self(accounts) - } -} - -pub struct AccountsTable { - accounts: Accounts, - width: Option, - config: ListAccountsTableConfig, -} - -impl AccountsTable { - pub fn with_some_width(mut self, width: Option) -> Self { - self.width = width; - self - } - - pub fn with_some_preset(mut self, preset: Option) -> Self { - self.config.preset = preset; - self - } - - pub fn with_some_name_color(mut self, color: Option) -> Self { - self.config.name_color = color; - self - } - - pub fn with_some_backends_color(mut self, color: Option) -> Self { - self.config.backends_color = color; - self - } - - pub fn with_some_default_color(mut self, color: Option) -> Self { - self.config.default_color = color; - self - } -} - -impl From for AccountsTable { - fn from(accounts: Accounts) -> Self { - Self { - accounts, - width: None, - config: Default::default(), - } - } -} - -impl fmt::Display for AccountsTable { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut table = Table::new(); - - table - .load_preset(self.config.preset()) - .set_content_arrangement(ContentArrangement::DynamicFullWidth) - .set_header(Row::from([ - Cell::new("NAME"), - Cell::new("BACKENDS"), - Cell::new("DEFAULT"), - ])) - .add_rows( - self.accounts - .iter() - .map(|account| account.to_row(&self.config)), - ); - - if let Some(width) = self.width { - table.set_width(width); - } - - writeln!(f)?; - write!(f, "{table}")?; - writeln!(f)?; - Ok(()) - } -} - -impl Serialize for AccountsTable { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.accounts.serialize(serializer) - } -} diff --git a/src/account/wizard.rs b/src/account/wizard.rs deleted file mode 100644 index d1b3112..0000000 --- a/src/account/wizard.rs +++ /dev/null @@ -1,95 +0,0 @@ -use color_eyre::Result; -use pimalaya_tui::{print, prompt}; - -use crate::{ - backend::{self, config::BackendConfig, BackendKind}, - message::config::{MessageConfig, MessageSendConfig}, -}; - -use super::TomlAccountConfig; - -pub async fn configure() -> Result<(String, TomlAccountConfig)> { - let email = prompt::email("Email address:", None)?; - - let mut config = TomlAccountConfig { - email: email.to_string(), - ..Default::default() - }; - - let autoconfig_email = config.email.to_owned(); - let autoconfig = - tokio::spawn(async move { email::autoconfig::from_addr(&autoconfig_email).await.ok() }); - - let default_account_name = email - .domain() - .split_once('.') - .map(|domain| domain.0) - .unwrap_or(email.domain()); - let account_name = prompt::text("Account name:", Some(default_account_name))?; - - config.display_name = Some(prompt::text( - "Full display name:", - Some(email.local_part()), - )?); - - config.downloads_dir = Some(prompt::path("Downloads directory:", Some("~/Downloads"))?); - - let autoconfig = autoconfig.await?; - let autoconfig = autoconfig.as_ref(); - - if let Some(config) = autoconfig { - if config.is_gmail() { - println!(); - print::warn("Warning: Google passwords cannot be used directly, see:"); - print::warn("https://github.com/pimalaya/himalaya?tab=readme-ov-file#configuration"); - println!(); - } - } - - match backend::wizard::configure(&account_name, &email, autoconfig).await? { - #[cfg(feature = "imap")] - BackendConfig::Imap(imap_config) => { - config.imap = Some(imap_config); - config.backend = Some(BackendKind::Imap); - } - #[cfg(feature = "maildir")] - BackendConfig::Maildir(mdir_config) => { - config.maildir = Some(mdir_config); - config.backend = Some(BackendKind::Maildir); - } - #[cfg(feature = "notmuch")] - BackendConfig::Notmuch(notmuch_config) => { - config.notmuch = Some(notmuch_config); - config.backend = Some(BackendKind::Notmuch); - } - _ => unreachable!(), - }; - - match backend::wizard::configure_sender(&account_name, &email, autoconfig).await? { - #[cfg(feature = "smtp")] - BackendConfig::Smtp(smtp_config) => { - config.smtp = Some(smtp_config); - config.message = Some(MessageConfig { - send: Some(MessageSendConfig { - backend: Some(BackendKind::Smtp), - ..Default::default() - }), - ..Default::default() - }); - } - #[cfg(feature = "sendmail")] - BackendConfig::Sendmail(sendmail_config) => { - config.sendmail = Some(sendmail_config); - config.message = Some(MessageConfig { - send: Some(MessageSendConfig { - backend: Some(BackendKind::Sendmail), - ..Default::default() - }), - ..Default::default() - }); - } - _ => unreachable!(), - }; - - Ok((account_name, config)) -} diff --git a/src/backend/config.rs b/src/backend/config.rs deleted file mode 100644 index dca3b14..0000000 --- a/src/backend/config.rs +++ /dev/null @@ -1,24 +0,0 @@ -#[cfg(feature = "imap")] -use email::imap::config::ImapConfig; -#[cfg(feature = "maildir")] -use email::maildir::config::MaildirConfig; -#[cfg(feature = "notmuch")] -use email::notmuch::config::NotmuchConfig; -#[cfg(feature = "sendmail")] -use email::sendmail::config::SendmailConfig; -#[cfg(feature = "smtp")] -use email::smtp::config::SmtpConfig; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum BackendConfig { - #[cfg(feature = "imap")] - Imap(ImapConfig), - #[cfg(feature = "maildir")] - Maildir(MaildirConfig), - #[cfg(feature = "notmuch")] - Notmuch(NotmuchConfig), - #[cfg(feature = "smtp")] - Smtp(SmtpConfig), - #[cfg(feature = "sendmail")] - Sendmail(SendmailConfig), -} diff --git a/src/backend/mod.rs b/src/backend/mod.rs deleted file mode 100644 index bd22ab7..0000000 --- a/src/backend/mod.rs +++ /dev/null @@ -1,735 +0,0 @@ -pub mod config; - -#[cfg(feature = "wizard")] -pub(crate) mod wizard; - -use async_trait::async_trait; -use color_eyre::Result; -use std::{fmt::Display, ops::Deref, sync::Arc}; -use tracing::instrument; - -#[cfg(feature = "imap")] -use email::imap::{ImapContext, ImapContextBuilder}; -#[cfg(any(feature = "account-sync", feature = "maildir"))] -use email::maildir::{MaildirContextBuilder, MaildirContextSync}; -#[cfg(feature = "notmuch")] -use email::notmuch::{NotmuchContextBuilder, NotmuchContextSync}; -#[cfg(feature = "sendmail")] -use email::sendmail::{SendmailContextBuilder, SendmailContextSync}; -#[cfg(feature = "smtp")] -use email::smtp::{SmtpContextBuilder, SmtpContextSync}; -use email::{ - account::config::AccountConfig, - backend::{ - feature::BackendFeature, macros::BackendContext, mapper::SomeBackendContextBuilderMapper, - }, - envelope::{ - get::GetEnvelope, - list::{ListEnvelopes, ListEnvelopesOptions}, - thread::ThreadEnvelopes, - Id, SingleId, - }, - flag::{add::AddFlags, remove::RemoveFlags, set::SetFlags, Flag, Flags}, - folder::{ - add::AddFolder, delete::DeleteFolder, expunge::ExpungeFolder, list::ListFolders, - purge::PurgeFolder, - }, - message::{ - add::AddMessage, - copy::CopyMessages, - delete::DeleteMessages, - get::GetMessages, - peek::PeekMessages, - r#move::MoveMessages, - send::{SendMessage, SendMessageThenSaveCopy}, - Messages, - }, - AnyResult, -}; -use serde::{Deserialize, Serialize}; - -use crate::{ - account::config::TomlAccountConfig, - cache::IdMapper, - envelope::{Envelopes, ThreadedEnvelopes}, -}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum BackendKind { - None, - - #[cfg(feature = "imap")] - Imap, - - #[cfg(feature = "maildir")] - Maildir, - - #[cfg(feature = "notmuch")] - Notmuch, - - #[cfg(feature = "smtp")] - Smtp, - - #[cfg(feature = "sendmail")] - Sendmail, -} - -impl Display for BackendKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::None => "None", - - #[cfg(feature = "imap")] - Self::Imap => "IMAP", - - #[cfg(feature = "maildir")] - Self::Maildir => "Maildir", - - #[cfg(feature = "notmuch")] - Self::Notmuch => "Notmuch", - - #[cfg(feature = "smtp")] - Self::Smtp => "SMTP", - - #[cfg(feature = "sendmail")] - Self::Sendmail => "Sendmail", - } - ) - } -} - -#[derive(Clone, Default)] -pub struct BackendContextBuilder { - pub toml_account_config: Arc, - pub account_config: Arc, - - #[cfg(feature = "imap")] - pub imap: Option, - - #[cfg(feature = "maildir")] - pub maildir: Option, - - #[cfg(feature = "notmuch")] - pub notmuch: Option, - - #[cfg(feature = "smtp")] - pub smtp: Option, - - #[cfg(feature = "sendmail")] - pub sendmail: Option, -} - -impl BackendContextBuilder { - pub async fn new( - toml_account_config: Arc, - account_config: Arc, - kinds: Vec<&BackendKind>, - ) -> Result { - Ok(Self { - toml_account_config: toml_account_config.clone(), - account_config: account_config.clone(), - - #[cfg(feature = "imap")] - imap: { - let builder = toml_account_config - .imap - .as_ref() - .filter(|_| kinds.contains(&&BackendKind::Imap)) - .map(Clone::clone) - .map(Arc::new) - .map(|imap_config| { - ImapContextBuilder::new(account_config.clone(), imap_config) - .with_prebuilt_credentials() - }); - match builder { - Some(builder) => Some(builder.await?), - None => None, - } - }, - - #[cfg(feature = "maildir")] - maildir: toml_account_config - .maildir - .as_ref() - .filter(|_| kinds.contains(&&BackendKind::Maildir)) - .map(Clone::clone) - .map(Arc::new) - .map(|mdir_config| MaildirContextBuilder::new(account_config.clone(), mdir_config)), - - #[cfg(feature = "notmuch")] - notmuch: toml_account_config - .notmuch - .as_ref() - .filter(|_| kinds.contains(&&BackendKind::Notmuch)) - .map(Clone::clone) - .map(Arc::new) - .map(|notmuch_config| { - NotmuchContextBuilder::new(account_config.clone(), notmuch_config) - }), - - #[cfg(feature = "smtp")] - smtp: toml_account_config - .smtp - .as_ref() - .filter(|_| kinds.contains(&&BackendKind::Smtp)) - .map(Clone::clone) - .map(Arc::new) - .map(|smtp_config| SmtpContextBuilder::new(account_config.clone(), smtp_config)), - - #[cfg(feature = "sendmail")] - sendmail: toml_account_config - .sendmail - .as_ref() - .filter(|_| kinds.contains(&&BackendKind::Sendmail)) - .map(Clone::clone) - .map(Arc::new) - .map(|sendmail_config| { - SendmailContextBuilder::new(account_config.clone(), sendmail_config) - }), - }) - } -} - -#[async_trait] -impl email::backend::context::BackendContextBuilder for BackendContextBuilder { - type Context = BackendContext; - - fn add_folder(&self) -> Option> { - match self.toml_account_config.add_folder_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.add_folder_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.add_folder_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.add_folder_with_some(&self.notmuch), - _ => None, - } - } - - fn list_folders(&self) -> Option> { - match self.toml_account_config.list_folders_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.list_folders_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.list_folders_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.list_folders_with_some(&self.notmuch), - _ => None, - } - } - - fn expunge_folder(&self) -> Option> { - match self.toml_account_config.expunge_folder_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.expunge_folder_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.expunge_folder_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.expunge_folder_with_some(&self.notmuch), - _ => None, - } - } - - fn purge_folder(&self) -> Option> { - match self.toml_account_config.purge_folder_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.purge_folder_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.purge_folder_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.purge_folder_with_some(&self.notmuch), - _ => None, - } - } - - fn delete_folder(&self) -> Option> { - match self.toml_account_config.delete_folder_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.delete_folder_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.delete_folder_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.delete_folder_with_some(&self.notmuch), - _ => None, - } - } - - fn get_envelope(&self) -> Option> { - match self.toml_account_config.get_envelope_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.get_envelope_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.get_envelope_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.get_envelope_with_some(&self.notmuch), - _ => None, - } - } - - fn list_envelopes(&self) -> Option> { - match self.toml_account_config.list_envelopes_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.list_envelopes_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.list_envelopes_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.list_envelopes_with_some(&self.notmuch), - _ => None, - } - } - - fn thread_envelopes(&self) -> Option> { - match self.toml_account_config.thread_envelopes_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.thread_envelopes_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.thread_envelopes_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.thread_envelopes_with_some(&self.notmuch), - _ => None, - } - } - - fn add_flags(&self) -> Option> { - match self.toml_account_config.add_flags_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.add_flags_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.add_flags_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.add_flags_with_some(&self.notmuch), - _ => None, - } - } - - fn set_flags(&self) -> Option> { - match self.toml_account_config.set_flags_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.set_flags_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.set_flags_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.set_flags_with_some(&self.notmuch), - _ => None, - } - } - - fn remove_flags(&self) -> Option> { - match self.toml_account_config.remove_flags_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.remove_flags_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.remove_flags_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.remove_flags_with_some(&self.notmuch), - _ => None, - } - } - - fn add_message(&self) -> Option> { - match self.toml_account_config.add_message_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.add_message_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.add_message_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.add_message_with_some(&self.notmuch), - _ => None, - } - } - - fn send_message(&self) -> Option> { - match self.toml_account_config.send_message_kind() { - #[cfg(feature = "smtp")] - Some(BackendKind::Smtp) => self.send_message_with_some(&self.smtp), - #[cfg(feature = "sendmail")] - Some(BackendKind::Sendmail) => self.send_message_with_some(&self.sendmail), - _ => None, - } - } - - fn peek_messages(&self) -> Option> { - match self.toml_account_config.peek_messages_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.peek_messages_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.peek_messages_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.peek_messages_with_some(&self.notmuch), - _ => None, - } - } - - fn get_messages(&self) -> Option> { - match self.toml_account_config.get_messages_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.get_messages_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.get_messages_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.get_messages_with_some(&self.notmuch), - _ => None, - } - } - - fn copy_messages(&self) -> Option> { - match self.toml_account_config.copy_messages_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.copy_messages_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.copy_messages_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.copy_messages_with_some(&self.notmuch), - _ => None, - } - } - - fn move_messages(&self) -> Option> { - match self.toml_account_config.move_messages_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.move_messages_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.move_messages_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.move_messages_with_some(&self.notmuch), - _ => None, - } - } - - fn delete_messages(&self) -> Option> { - match self.toml_account_config.delete_messages_kind() { - #[cfg(feature = "imap")] - Some(BackendKind::Imap) => self.delete_messages_with_some(&self.imap), - #[cfg(feature = "maildir")] - Some(BackendKind::Maildir) => self.delete_messages_with_some(&self.maildir), - #[cfg(feature = "notmuch")] - Some(BackendKind::Notmuch) => self.delete_messages_with_some(&self.notmuch), - _ => None, - } - } - - async fn build(self) -> AnyResult { - let mut ctx = BackendContext::default(); - - #[cfg(feature = "imap")] - if let Some(imap) = self.imap { - ctx.imap = Some(imap.build().await?); - } - - #[cfg(feature = "maildir")] - if let Some(maildir) = self.maildir { - ctx.maildir = Some(maildir.build().await?); - } - - #[cfg(feature = "notmuch")] - if let Some(notmuch) = self.notmuch { - ctx.notmuch = Some(notmuch.build().await?); - } - - #[cfg(feature = "smtp")] - if let Some(smtp) = self.smtp { - ctx.smtp = Some(smtp.build().await?); - } - - #[cfg(feature = "sendmail")] - if let Some(sendmail) = self.sendmail { - ctx.sendmail = Some(sendmail.build().await?); - } - - Ok(ctx) - } -} - -#[derive(BackendContext, Default)] -pub struct BackendContext { - #[cfg(feature = "imap")] - pub imap: Option, - - #[cfg(feature = "maildir")] - pub maildir: Option, - - #[cfg(feature = "notmuch")] - pub notmuch: Option, - - #[cfg(feature = "smtp")] - pub smtp: Option, - - #[cfg(feature = "sendmail")] - pub sendmail: Option, -} - -#[cfg(feature = "imap")] -impl AsRef> for BackendContext { - fn as_ref(&self) -> &Option { - &self.imap - } -} - -#[cfg(feature = "maildir")] -impl AsRef> for BackendContext { - fn as_ref(&self) -> &Option { - &self.maildir - } -} - -#[cfg(feature = "notmuch")] -impl AsRef> for BackendContext { - fn as_ref(&self) -> &Option { - &self.notmuch - } -} - -#[cfg(feature = "smtp")] -impl AsRef> for BackendContext { - fn as_ref(&self) -> &Option { - &self.smtp - } -} - -#[cfg(feature = "sendmail")] -impl AsRef> for BackendContext { - fn as_ref(&self) -> &Option { - &self.sendmail - } -} - -pub struct Backend { - pub toml_account_config: Arc, - pub backend: email::backend::Backend, -} - -impl Backend { - pub async fn new( - toml_account_config: Arc, - account_config: Arc, - backend_kinds: impl IntoIterator, - with_features: impl Fn(&mut email::backend::BackendBuilder), - ) -> Result { - let backend_kinds = backend_kinds.into_iter().collect(); - let backend_ctx_builder = BackendContextBuilder::new( - toml_account_config.clone(), - account_config.clone(), - backend_kinds, - ) - .await?; - let mut backend_builder = - email::backend::BackendBuilder::new(account_config.clone(), backend_ctx_builder) - .without_features(); - - with_features(&mut backend_builder); - - Ok(Self { - toml_account_config: toml_account_config.clone(), - backend: backend_builder.build().await?, - }) - } - - #[instrument(skip(self))] - fn build_id_mapper( - &self, - folder: &str, - backend_kind: Option<&BackendKind>, - ) -> Result { - #[allow(unused_mut)] - #[cfg(feature = "maildir")] - if let Some(BackendKind::Maildir) = backend_kind { - if let Some(_) = &self.toml_account_config.maildir { - return Ok(IdMapper::new(&self.backend.account_config, folder)?); - } - } - - #[cfg(feature = "notmuch")] - if let Some(BackendKind::Notmuch) = backend_kind { - if let Some(_) = &self.toml_account_config.notmuch { - return Ok(IdMapper::new(&self.backend.account_config, folder)?); - } - } - - Ok(IdMapper::Dummy) - } - - pub async fn list_envelopes( - &self, - folder: &str, - opts: ListEnvelopesOptions, - ) -> Result { - let backend_kind = self.toml_account_config.list_envelopes_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let envelopes = self.backend.list_envelopes(folder, opts).await?; - let envelopes = - Envelopes::try_from_backend(&self.backend.account_config, &id_mapper, envelopes)?; - Ok(envelopes) - } - - pub async fn thread_envelopes( - &self, - folder: &str, - opts: ListEnvelopesOptions, - ) -> Result { - let backend_kind = self.toml_account_config.thread_envelopes_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let envelopes = self.backend.thread_envelopes(folder, opts).await?; - let envelopes = ThreadedEnvelopes::try_from_backend(&id_mapper, envelopes)?; - Ok(envelopes) - } - - pub async fn thread_envelope( - &self, - folder: &str, - id: usize, - opts: ListEnvelopesOptions, - ) -> Result { - let backend_kind = self.toml_account_config.thread_envelopes_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let id = id_mapper.get_id(id)?; - let envelopes = self - .backend - .thread_envelope(folder, SingleId::from(id), opts) - .await?; - let envelopes = ThreadedEnvelopes::try_from_backend(&id_mapper, envelopes)?; - Ok(envelopes) - } - - pub async fn add_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> { - let backend_kind = self.toml_account_config.add_flags_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.add_flags(folder, &ids, flags).await?; - Ok(()) - } - - pub async fn add_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> { - let backend_kind = self.toml_account_config.add_flags_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.add_flag(folder, &ids, flag).await?; - Ok(()) - } - - pub async fn set_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> { - let backend_kind = self.toml_account_config.set_flags_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.set_flags(folder, &ids, flags).await?; - Ok(()) - } - - pub async fn set_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> { - let backend_kind = self.toml_account_config.set_flags_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.set_flag(folder, &ids, flag).await?; - Ok(()) - } - - pub async fn remove_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> { - let backend_kind = self.toml_account_config.remove_flags_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.remove_flags(folder, &ids, flags).await?; - Ok(()) - } - - pub async fn remove_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> { - let backend_kind = self.toml_account_config.remove_flags_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.remove_flag(folder, &ids, flag).await?; - Ok(()) - } - - pub async fn add_message(&self, folder: &str, email: &[u8]) -> Result { - let backend_kind = self.toml_account_config.add_message_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let id = self.backend.add_message(folder, email).await?; - id_mapper.create_alias(&*id)?; - Ok(id) - } - - pub async fn add_message_with_flags( - &self, - folder: &str, - email: &[u8], - flags: &Flags, - ) -> Result { - let backend_kind = self.toml_account_config.add_message_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let id = self - .backend - .add_message_with_flags(folder, email, flags) - .await?; - id_mapper.create_alias(&*id)?; - Ok(id) - } - - pub async fn peek_messages(&self, folder: &str, ids: &[usize]) -> Result { - let backend_kind = self.toml_account_config.get_messages_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - let msgs = self.backend.peek_messages(folder, &ids).await?; - Ok(msgs) - } - - pub async fn get_messages(&self, folder: &str, ids: &[usize]) -> Result { - let backend_kind = self.toml_account_config.get_messages_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - let msgs = self.backend.get_messages(folder, &ids).await?; - Ok(msgs) - } - - pub async fn copy_messages( - &self, - from_folder: &str, - to_folder: &str, - ids: &[usize], - ) -> Result<()> { - let backend_kind = self.toml_account_config.move_messages_kind(); - let id_mapper = self.build_id_mapper(from_folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend - .copy_messages(from_folder, to_folder, &ids) - .await?; - Ok(()) - } - - pub async fn move_messages( - &self, - from_folder: &str, - to_folder: &str, - ids: &[usize], - ) -> Result<()> { - let backend_kind = self.toml_account_config.move_messages_kind(); - let id_mapper = self.build_id_mapper(from_folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend - .move_messages(from_folder, to_folder, &ids) - .await?; - Ok(()) - } - - pub async fn delete_messages(&self, folder: &str, ids: &[usize]) -> Result<()> { - let backend_kind = self.toml_account_config.delete_messages_kind(); - let id_mapper = self.build_id_mapper(folder, backend_kind)?; - let ids = Id::multiple(id_mapper.get_ids(ids)?); - self.backend.delete_messages(folder, &ids).await?; - Ok(()) - } - - pub async fn send_message_then_save_copy(&self, msg: &[u8]) -> Result<()> { - self.backend.send_message_then_save_copy(msg).await?; - Ok(()) - } -} - -impl Deref for Backend { - type Target = email::backend::Backend; - - fn deref(&self) -> &Self::Target { - &self.backend - } -} diff --git a/src/backend/wizard.rs b/src/backend/wizard.rs deleted file mode 100644 index 035742c..0000000 --- a/src/backend/wizard.rs +++ /dev/null @@ -1,75 +0,0 @@ -use color_eyre::Result; -use email::autoconfig::config::AutoConfig; -use email_address::EmailAddress; -use pimalaya_tui::{prompt, wizard}; - -use super::{config::BackendConfig, BackendKind}; - -const DEFAULT_BACKEND_KINDS: &[BackendKind] = &[ - #[cfg(feature = "imap")] - BackendKind::Imap, - #[cfg(feature = "maildir")] - BackendKind::Maildir, - #[cfg(feature = "notmuch")] - BackendKind::Notmuch, -]; - -pub async fn configure( - account_name: &str, - email: &EmailAddress, - autoconfig: Option<&AutoConfig>, -) -> Result { - let backend = prompt::item("Default backend:", &*DEFAULT_BACKEND_KINDS, None)?; - - match backend { - #[cfg(feature = "imap")] - BackendKind::Imap => { - let config = wizard::imap::start(account_name, email, autoconfig).await?; - Ok(BackendConfig::Imap(config)) - } - #[cfg(feature = "maildir")] - BackendKind::Maildir => { - let config = wizard::maildir::start(account_name)?; - Ok(BackendConfig::Maildir(config)) - } - #[cfg(feature = "notmuch")] - BackendKind::Notmuch => { - let config = wizard::notmuch::start()?; - Ok(BackendConfig::Notmuch(config)) - } - _ => unreachable!(), - } -} - -const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[ - #[cfg(feature = "smtp")] - BackendKind::Smtp, - #[cfg(feature = "sendmail")] - BackendKind::Sendmail, -]; - -pub async fn configure_sender( - account_name: &str, - email: &EmailAddress, - autoconfig: Option<&AutoConfig>, -) -> Result { - let backend = prompt::item( - "Backend for sending messages:", - &*SEND_MESSAGE_BACKEND_KINDS, - None, - )?; - - match backend { - #[cfg(feature = "smtp")] - BackendKind::Smtp => { - let config = wizard::smtp::start(account_name, email, autoconfig).await?; - Ok(BackendConfig::Smtp(config)) - } - #[cfg(feature = "sendmail")] - BackendKind::Sendmail => { - let config = wizard::sendmail::start()?; - Ok(BackendConfig::Sendmail(config)) - } - _ => unreachable!(), - } -} diff --git a/src/cache/arg/disable.rs b/src/cache/arg/disable.rs deleted file mode 100644 index 04f32a1..0000000 --- a/src/cache/arg/disable.rs +++ /dev/null @@ -1,15 +0,0 @@ -use clap::Parser; - -/// The disable cache flag parser. -#[derive(Debug, Default, Parser)] -pub struct CacheDisableFlag { - /// Disable any sort of cache. - /// - /// The action depends on commands it apply on. For example, when - /// listing envelopes using the IMAP backend, this flag will - /// ensure that envelopes are fetched from the IMAP server rather - /// than the synchronized local Maildir. - #[arg(long = "disable-cache", alias = "no-cache", global = true)] - #[arg(name = "cache_disable")] - pub disable: bool, -} diff --git a/src/cache/arg/mod.rs b/src/cache/arg/mod.rs deleted file mode 100644 index a13d9f3..0000000 --- a/src/cache/arg/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod disable; diff --git a/src/cache/mod.rs b/src/cache/mod.rs deleted file mode 100644 index 5a111e6..0000000 --- a/src/cache/mod.rs +++ /dev/null @@ -1,144 +0,0 @@ -pub mod arg; - -use color_eyre::{eyre::eyre, eyre::Context, Result}; -use dirs::data_dir; -use email::account::config::AccountConfig; -use sled::{Config, Db}; -use std::collections::HashSet; -use tracing::debug; - -#[derive(Debug)] -pub enum IdMapper { - Dummy, - Mapper(Db), -} - -impl IdMapper { - pub fn new(account_config: &AccountConfig, folder: &str) -> Result { - let digest = md5::compute(account_config.name.clone() + folder); - let db_path = data_dir() - .ok_or(eyre!("cannot get XDG data directory"))? - .join("himalaya") - .join(".id-mappers") - .join(format!("{digest:x}")); - - let conn = Config::new() - .path(&db_path) - .idgen_persist_interval(1) - .open() - .with_context(|| format!("cannot open id mapper database at {db_path:?}"))?; - - Ok(Self::Mapper(conn)) - } - - pub fn create_alias(&self, id: I) -> Result - where - I: AsRef, - { - let id = id.as_ref(); - match self { - Self::Dummy => Ok(id.to_owned()), - Self::Mapper(conn) => { - debug!("creating alias for id {id}…"); - - let alias = conn - .generate_id() - .with_context(|| format!("cannot create alias for id {id}"))? - .to_string(); - debug!("created alias {alias} for id {id}"); - - conn.insert(&id, alias.as_bytes()) - .with_context(|| format!("cannot insert alias {alias} for id {id}"))?; - - Ok(alias) - } - } - } - - pub fn get_or_create_alias(&self, id: I) -> Result - where - I: AsRef, - { - let id = id.as_ref(); - match self { - Self::Dummy => Ok(id.to_owned()), - Self::Mapper(conn) => { - debug!("getting alias for id {id}…"); - - let alias = conn - .get(id) - .with_context(|| format!("cannot get alias for id {id}"))?; - - let alias = match alias { - Some(alias) => { - let alias = String::from_utf8_lossy(alias.as_ref()); - debug!("found alias {alias} for id {id}"); - alias.to_string() - } - None => { - debug!("alias not found, creating it…"); - self.create_alias(id)? - } - }; - - Ok(alias) - } - } - } - - pub fn get_id(&self, alias: A) -> Result - where - A: ToString, - { - let alias = alias.to_string(); - - match self { - Self::Dummy => Ok(alias.to_string()), - Self::Mapper(conn) => { - debug!("getting id from alias {alias}…"); - - let id = conn - .iter() - .flat_map(|entry| entry) - .find_map(|(entry_id, entry_alias)| { - if entry_alias.as_ref() == alias.as_bytes() { - let entry_id = String::from_utf8_lossy(entry_id.as_ref()); - Some(entry_id.to_string()) - } else { - None - } - }) - .ok_or_else(|| eyre!("cannot get id from alias {alias}"))?; - debug!("found id {id} from alias {alias}"); - - Ok(id) - } - } - } - - pub fn get_ids(&self, aliases: impl IntoIterator) -> Result> { - let aliases: Vec = aliases.into_iter().map(|alias| alias.to_string()).collect(); - - match self { - Self::Dummy => Ok(aliases), - Self::Mapper(conn) => { - let aliases: HashSet<&str> = aliases.iter().map(|alias| alias.as_str()).collect(); - let ids: Vec = conn - .iter() - .flat_map(|entry| entry) - .filter_map(|(entry_id, entry_alias)| { - let alias = String::from_utf8_lossy(entry_alias.as_ref()); - if aliases.contains(alias.as_ref()) { - let entry_id = String::from_utf8_lossy(entry_id.as_ref()); - Some(entry_id.to_string()) - } else { - None - } - }) - .collect(); - - Ok(ids) - } - } - } -} diff --git a/src/cli.rs b/src/cli.rs index 35156f7..52e7fec 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,11 +1,18 @@ use clap::{Parser, Subcommand}; use color_eyre::Result; +use pimalaya_tui::terminal::{ + cli::{ + arg::path_parser, + printer::{OutputFmt, Printer}, + }, + config::TomlConfig as _, +}; use std::path::PathBuf; use crate::{ account::command::AccountSubcommand, completion::command::CompletionGenerateCommand, - config::{self, Config}, + config::TomlConfig, envelope::command::EnvelopeSubcommand, flag::command::FlagSubcommand, folder::command::FolderSubcommand, @@ -14,8 +21,6 @@ use crate::{ attachment::command::AttachmentSubcommand, command::MessageSubcommand, template::command::TemplateSubcommand, }, - output::OutputFmt, - printer::Printer, }; #[derive(Parser, Debug)] @@ -34,7 +39,7 @@ pub struct Cli { /// which allows you to separate your public config from your /// private(s) one(s). #[arg(short, long = "config", global = true, env = "HIMALAYA_CONFIG")] - #[arg(value_name = "PATH", value_parser = config::path_parser)] + #[arg(value_name = "PATH", value_parser = path_parser)] pub config_paths: Vec, /// Customize the output format. @@ -111,31 +116,31 @@ impl HimalayaCommand { pub async fn execute(self, printer: &mut impl Printer, config_paths: &[PathBuf]) -> Result<()> { match self { Self::Account(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Folder(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Envelope(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Flag(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Message(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Attachment(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Template(cmd) => { - let config = Config::from_paths_or_default(config_paths).await?; + let config = TomlConfig::from_paths_or_default(config_paths).await?; cmd.execute(printer, &config).await } Self::Manual(cmd) => cmd.execute(printer).await, diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..3f6d68f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,3 @@ +use pimalaya_tui::himalaya::config::HimalayaTomlConfig; + +pub type TomlConfig = HimalayaTomlConfig; diff --git a/src/config/mod.rs b/src/config/mod.rs deleted file mode 100644 index 395ed2c..0000000 --- a/src/config/mod.rs +++ /dev/null @@ -1,230 +0,0 @@ -#[cfg(feature = "wizard")] -pub mod wizard; - -use std::{collections::HashMap, path::PathBuf, sync::Arc}; - -use color_eyre::{eyre::eyre, Result}; -use crossterm::style::Color; -use email::{ - account::config::AccountConfig, envelope::config::EnvelopeConfig, folder::config::FolderConfig, - message::config::MessageConfig, -}; -use pimalaya_tui::config::TomlConfig; -use serde::{Deserialize, Serialize}; -use shellexpand_utils::{canonicalize, expand}; - -use crate::account::config::{ListAccountsTableConfig, TomlAccountConfig}; - -/// Represents the user config file. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct Config { - #[serde(alias = "name")] - pub display_name: Option, - pub signature: Option, - pub signature_delim: Option, - pub downloads_dir: Option, - pub accounts: HashMap, - pub account: Option, -} - -impl TomlConfig for Config { - fn project_name() -> &'static str { - env!("CARGO_PKG_NAME") - } -} - -impl Config { - /// Create and save a TOML configuration using the wizard. - /// - /// If the user accepts the confirmation, the wizard starts and - /// help him to create his configuration file. Otherwise the - /// program stops. - /// - /// NOTE: the wizard can only be used with interactive shells. - #[cfg(feature = "wizard")] - async fn from_wizard(path: &PathBuf) -> Result { - Self::confirm_from_wizard(path)?; - wizard::configure(path).await - } - - /// Read and parse the TOML configuration from default paths. - pub async fn from_default_paths() -> Result { - match Self::first_valid_default_path() { - Some(path) => Self::from_paths(&[path]), - #[cfg(feature = "wizard")] - None => Self::from_wizard(&Self::default_path()?).await, - #[cfg(not(feature = "wizard"))] - None => color_eyre::eyre::bail!("cannot find config file from default paths"), - } - } - - /// Read and parse the TOML configuration at the optional given - /// path. - /// - /// If the given path exists, then read and parse the TOML - /// configuration from it. - /// - /// If the given path does not exist, then create it using the - /// wizard. - /// - /// If no path is given, then either read and parse the TOML - /// configuration at the first valid default path, otherwise - /// create it using the wizard. wizard. - pub async fn from_paths_or_default(paths: &[PathBuf]) -> Result { - match paths.len() { - 0 => Self::from_default_paths().await, - _ if paths[0].exists() => Self::from_paths(paths), - #[cfg(feature = "wizard")] - _ => Self::from_wizard(&paths[0]).await, - #[cfg(not(feature = "wizard"))] - _ => color_eyre::eyre::bail!("cannot find config file from default paths"), - } - } - - pub fn into_toml_account_config( - &self, - account_name: Option<&str>, - ) -> Result<(String, TomlAccountConfig)> { - #[allow(unused_mut)] - let (account_name, mut toml_account_config) = match account_name { - Some("default") | Some("") | None => self - .accounts - .iter() - .find_map(|(name, account)| { - account - .default - .filter(|default| *default) - .map(|_| (name.to_owned(), account.clone())) - }) - .ok_or_else(|| eyre!("cannot find default account")), - Some(name) => self - .accounts - .get(name) - .map(|account| (name.to_owned(), account.clone())) - .ok_or_else(|| eyre!("cannot find account {name}")), - }?; - - #[cfg(all(feature = "imap", feature = "keyring"))] - if let Some(imap_config) = toml_account_config.imap.as_mut() { - imap_config - .auth - .replace_undefined_keyring_entries(&account_name)?; - } - - #[cfg(all(feature = "smtp", feature = "keyring"))] - if let Some(smtp_config) = toml_account_config.smtp.as_mut() { - smtp_config - .auth - .replace_undefined_keyring_entries(&account_name)?; - } - - Ok((account_name, toml_account_config)) - } - - /// Build account configurations from a given account name. - pub fn into_account_configs( - self, - account_name: Option<&str>, - ) -> Result<(Arc, Arc)> { - let (account_name, toml_account_config) = self.into_toml_account_config(account_name)?; - - let config = email::config::Config { - display_name: self.display_name, - signature: self.signature, - signature_delim: self.signature_delim, - downloads_dir: self.downloads_dir, - - accounts: HashMap::from_iter(self.accounts.clone().into_iter().map( - |(name, config)| { - ( - name.clone(), - AccountConfig { - name, - email: config.email, - display_name: config.display_name, - signature: config.signature, - signature_delim: config.signature_delim, - downloads_dir: config.downloads_dir, - folder: config.folder.map(|c| FolderConfig { - aliases: c.alias, - list: c.list.map(|c| c.remote), - }), - envelope: config.envelope.map(|c| EnvelopeConfig { - list: c.list.map(|c| c.remote), - thread: c.thread.map(|c| c.remote), - }), - flag: None, - message: config.message.map(|c| MessageConfig { - read: c.read.map(|c| c.remote), - write: c.write.map(|c| c.remote), - send: c.send.map(|c| c.remote), - delete: c.delete.map(Into::into), - }), - template: config.template, - #[cfg(feature = "pgp")] - pgp: config.pgp, - }, - ) - }, - )), - }; - - let account_config = config.account(account_name)?; - - Ok((Arc::new(toml_account_config), Arc::new(account_config))) - } - - pub fn account_list_table_preset(&self) -> Option { - self.account - .as_ref() - .and_then(|account| account.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.preset.clone()) - } - - pub fn account_list_table_name_color(&self) -> Option { - self.account - .as_ref() - .and_then(|account| account.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.name_color) - } - - pub fn account_list_table_backends_color(&self) -> Option { - self.account - .as_ref() - .and_then(|account| account.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.backends_color) - } - - pub fn account_list_table_default_color(&self) -> Option { - self.account - .as_ref() - .and_then(|account| account.list.as_ref()) - .and_then(|list| list.table.as_ref()) - .and_then(|table| table.default_color) - } -} - -/// Parse a configuration file path as [`PathBuf`]. -/// -/// The path is shell-expanded then canonicalized (if applicable). -pub fn path_parser(path: &str) -> Result { - expand::try_path(path) - .map(canonicalize::path) - .map_err(|err| err.to_string()) -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct AccountsConfig { - pub list: Option, -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ListAccountsConfig { - pub table: Option, -} diff --git a/src/config/wizard.rs b/src/config/wizard.rs deleted file mode 100644 index ece28a7..0000000 --- a/src/config/wizard.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::{fs, path::PathBuf}; - -use color_eyre::Result; -use pimalaya_tui::{config::TomlConfig, print, prompt}; - -use crate::account; - -use super::Config; - -pub async fn configure(path: &PathBuf) -> Result { - print::section("Configuring your default account"); - - let mut config = Config::default(); - - let (account_name, account_config) = account::wizard::configure().await?; - config.accounts.insert(account_name, account_config); - - let path = prompt::path("Where to save the configuration?", Some(path))?; - println!("Writing the configuration to {}…", path.display()); - - let toml = config.pretty_serialize()?; - fs::create_dir_all(path.parent().unwrap_or(&path))?; - fs::write(path, toml)?; - - println!("Done! Exiting the wizard…"); - Ok(config) -} diff --git a/src/email/envelope/command/list.rs b/src/email/envelope/command/list.rs index 06f80ae..d8b5739 100644 --- a/src/email/envelope/command/list.rs +++ b/src/email/envelope/command/list.rs @@ -1,3 +1,5 @@ +use std::{process::exit, sync::Arc}; + use ariadne::{Color, Label, Report, ReportKind, Source}; use clap::Parser; use color_eyre::Result; @@ -5,12 +7,15 @@ use email::{ backend::feature::BackendFeatureSource, email::search_query, envelope::list::ListEnvelopesOptions, search_query::SearchEmailsQuery, }; -use std::process::exit; +use pimalaya_tui::{ + himalaya::{backend::BackendBuilder, config::EnvelopesTable}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - envelope::EnvelopesTable, folder::arg::name::FolderNameOptionalFlag, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, + folder::arg::name::FolderNameOptionalFlag, }; /// List all envelopes. @@ -132,27 +137,32 @@ impl Default for ListEnvelopesCommand { } impl ListEnvelopesCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing list envelopes command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; + let toml_account_config = Arc::new(toml_account_config); + let folder = &self.folder.name; let page = 1.max(self.page) - 1; let page_size = self .page_size .unwrap_or_else(|| account_config.get_envelope_list_page_size()); - let list_envelopes_kind = toml_account_config.list_envelopes_kind(); - - let backend = Backend::new( + let backend = BackendBuilder::new( toml_account_config.clone(), - account_config.clone(), - list_envelopes_kind, - |builder| builder.set_list_envelopes(BackendFeatureSource::Context), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_list_envelopes(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let query = self diff --git a/src/email/envelope/command/mod.rs b/src/email/envelope/command/mod.rs index d0a27de..d0b170c 100644 --- a/src/email/envelope/command/mod.rs +++ b/src/email/envelope/command/mod.rs @@ -3,8 +3,9 @@ pub mod thread; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::{list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand}; @@ -25,7 +26,7 @@ pub enum EnvelopeSubcommand { impl EnvelopeSubcommand { #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::List(cmd) => cmd.execute(printer, config).await, Self::Thread(cmd) => cmd.execute(printer, config).await, diff --git a/src/email/envelope/command/thread.rs b/src/email/envelope/command/thread.rs index 08b4495..8927c53 100644 --- a/src/email/envelope/command/thread.rs +++ b/src/email/envelope/command/thread.rs @@ -5,12 +5,16 @@ use email::{ backend::feature::BackendFeatureSource, email::search_query, envelope::list::ListEnvelopesOptions, search_query::SearchEmailsQuery, }; -use std::process::exit; +use pimalaya_tui::{ + himalaya::{backend::BackendBuilder, config::EnvelopesTree}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; +use std::{process::exit, sync::Arc}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, envelope::EnvelopesTree, - folder::arg::name::FolderNameOptionalFlag, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, + folder::arg::name::FolderNameOptionalFlag, }; /// Thread all envelopes. @@ -34,22 +38,27 @@ pub struct ThreadEnvelopesCommand { } impl ThreadEnvelopesCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing thread envelopes command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; + let account_config = Arc::new(account_config); let folder = &self.folder.name; - let thread_envelopes_kind = toml_account_config.thread_envelopes_kind(); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - thread_envelopes_kind, - |builder| builder.set_thread_envelopes(BackendFeatureSource::Context), + |builder| { + builder + .without_features() + .with_thread_envelopes(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let query = self diff --git a/src/email/envelope/config.rs b/src/email/envelope/config.rs deleted file mode 100644 index 30804ab..0000000 --- a/src/email/envelope/config.rs +++ /dev/null @@ -1,163 +0,0 @@ -use comfy_table::presets; -use crossterm::style::Color; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; - -use crate::{backend::BackendKind, ui::map_color}; - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct EnvelopeConfig { - pub list: Option, - pub thread: Option, - pub get: Option, -} - -impl EnvelopeConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(list) = &self.list { - kinds.extend(list.get_used_backends()); - } - - if let Some(get) = &self.get { - kinds.extend(get.get_used_backends()); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ListEnvelopesConfig { - pub backend: Option, - pub table: Option, - - #[serde(flatten)] - pub remote: email::envelope::list::config::EnvelopeListConfig, -} - -impl ListEnvelopesConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ListEnvelopesTableConfig { - pub preset: Option, - - pub unseen_char: Option, - pub replied_char: Option, - pub flagged_char: Option, - pub attachment_char: Option, - - pub id_color: Option, - pub flags_color: Option, - pub subject_color: Option, - pub sender_color: Option, - pub date_color: Option, -} - -impl ListEnvelopesTableConfig { - pub fn preset(&self) -> &str { - self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) - } - - pub fn replied_char(&self, replied: bool) -> char { - if replied { - self.replied_char.unwrap_or('R') - } else { - ' ' - } - } - - pub fn flagged_char(&self, flagged: bool) -> char { - if flagged { - self.flagged_char.unwrap_or('!') - } else { - ' ' - } - } - - pub fn attachment_char(&self, attachment: bool) -> char { - if attachment { - self.attachment_char.unwrap_or('@') - } else { - ' ' - } - } - - pub fn unseen_char(&self, unseen: bool) -> char { - if unseen { - self.unseen_char.unwrap_or('*') - } else { - ' ' - } - } - - pub fn id_color(&self) -> comfy_table::Color { - map_color(self.id_color.unwrap_or(Color::Red)) - } - - pub fn flags_color(&self) -> comfy_table::Color { - map_color(self.flags_color.unwrap_or(Color::Reset)) - } - - pub fn subject_color(&self) -> comfy_table::Color { - map_color(self.subject_color.unwrap_or(Color::Green)) - } - - pub fn sender_color(&self) -> comfy_table::Color { - map_color(self.sender_color.unwrap_or(Color::Blue)) - } - - pub fn date_color(&self) -> comfy_table::Color { - map_color(self.date_color.unwrap_or(Color::DarkYellow)) - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ThreadEnvelopesConfig { - pub backend: Option, - - #[serde(flatten)] - pub remote: email::envelope::thread::config::EnvelopeThreadConfig, -} - -impl ThreadEnvelopesConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct GetEnvelopeConfig { - pub backend: Option, -} - -impl GetEnvelopeConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} diff --git a/src/email/envelope/flag/command/add.rs b/src/email/envelope/flag/command/add.rs index bf9eb77..f90a1ae 100644 --- a/src/email/envelope/flag/command/add.rs +++ b/src/email/envelope/flag/command/add.rs @@ -1,15 +1,19 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, }; /// Add flag(s) to an envelope. @@ -29,7 +33,7 @@ pub struct FlagAddCommand { } impl FlagAddCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing add flag(s) command"); let folder = &self.folder.name; @@ -38,18 +42,21 @@ impl FlagAddCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let add_flags_kind = toml_account_config.add_flags_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - add_flags_kind, - |builder| builder.set_add_flags(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_add_flags(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.add_flags(folder, &ids, &flags).await?; - printer.out(format!("Flag(s) {flags} successfully added!")) + printer.out(format!("Flag(s) {flags} successfully added!\n")) } } diff --git a/src/email/envelope/flag/command/mod.rs b/src/email/envelope/flag/command/mod.rs index 15ba2ed..b1bbfc9 100644 --- a/src/email/envelope/flag/command/mod.rs +++ b/src/email/envelope/flag/command/mod.rs @@ -4,8 +4,9 @@ mod set; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::{add::FlagAddCommand, remove::FlagRemoveCommand, set::FlagSetCommand}; @@ -32,7 +33,7 @@ pub enum FlagSubcommand { impl FlagSubcommand { #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::Add(cmd) => cmd.execute(printer, config).await, Self::Set(cmd) => cmd.execute(printer, config).await, diff --git a/src/email/envelope/flag/command/remove.rs b/src/email/envelope/flag/command/remove.rs index cf401db..be651cf 100644 --- a/src/email/envelope/flag/command/remove.rs +++ b/src/email/envelope/flag/command/remove.rs @@ -1,15 +1,19 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, }; /// Remove flag(s) from an envelope. @@ -29,7 +33,7 @@ pub struct FlagRemoveCommand { } impl FlagRemoveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing remove flag(s) command"); let folder = &self.folder.name; @@ -38,18 +42,21 @@ impl FlagRemoveCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let remove_flags_kind = toml_account_config.remove_flags_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - remove_flags_kind, - |builder| builder.set_remove_flags(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_remove_flags(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.remove_flags(folder, &ids, &flags).await?; - printer.out(format!("Flag(s) {flags} successfully removed!")) + printer.out(format!("Flag(s) {flags} successfully removed!\n")) } } diff --git a/src/email/envelope/flag/command/set.rs b/src/email/envelope/flag/command/set.rs index 93607d6..7c0e387 100644 --- a/src/email/envelope/flag/command/set.rs +++ b/src/email/envelope/flag/command/set.rs @@ -1,15 +1,19 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, }; /// Replace flag(s) of an envelope. @@ -29,7 +33,7 @@ pub struct FlagSetCommand { } impl FlagSetCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing set flag(s) command"); let folder = &self.folder.name; @@ -38,18 +42,21 @@ impl FlagSetCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let set_flags_kind = toml_account_config.set_flags_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - set_flags_kind, - |builder| builder.set_set_flags(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_set_flags(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.set_flags(folder, &ids, &flags).await?; - printer.out(format!("Flag(s) {flags} successfully replaced!")) + printer.out(format!("Flag(s) {flags} successfully replaced!\n")) } } diff --git a/src/email/envelope/flag/config.rs b/src/email/envelope/flag/config.rs deleted file mode 100644 index eb75af4..0000000 --- a/src/email/envelope/flag/config.rs +++ /dev/null @@ -1,82 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; - -use crate::backend::BackendKind; - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FlagConfig { - pub add: Option, - pub set: Option, - pub remove: Option, -} - -impl FlagConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(add) = &self.add { - kinds.extend(add.get_used_backends()); - } - - if let Some(set) = &self.set { - kinds.extend(set.get_used_backends()); - } - - if let Some(remove) = &self.remove { - kinds.extend(remove.get_used_backends()); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FlagAddConfig { - pub backend: Option, -} - -impl FlagAddConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FlagSetConfig { - pub backend: Option, -} - -impl FlagSetConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FlagRemoveConfig { - pub backend: Option, -} - -impl FlagRemoveConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} diff --git a/src/email/envelope/flag/mod.rs b/src/email/envelope/flag/mod.rs index 75db6d3..1a3a73a 100644 --- a/src/email/envelope/flag/mod.rs +++ b/src/email/envelope/flag/mod.rs @@ -1,48 +1,2 @@ pub mod arg; pub mod command; -pub mod config; - -use serde::Serialize; -use std::{collections::HashSet, ops}; - -/// Represents the flag variants. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize)] -pub enum Flag { - Seen, - Answered, - Flagged, - Deleted, - Draft, - Custom(String), -} - -impl From<&email::flag::Flag> for Flag { - fn from(flag: &email::flag::Flag) -> Self { - use email::flag::Flag::*; - match flag { - Seen => Flag::Seen, - Answered => Flag::Answered, - Flagged => Flag::Flagged, - Deleted => Flag::Deleted, - Draft => Flag::Draft, - Custom(flag) => Flag::Custom(flag.clone()), - } - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] -pub struct Flags(pub HashSet); - -impl ops::Deref for Flags { - type Target = HashSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Flags { - fn from(flags: email::flag::Flags) -> Self { - Flags(flags.iter().map(Flag::from).collect()) - } -} diff --git a/src/email/envelope/mod.rs b/src/email/envelope/mod.rs index c9bfcc2..e84d06e 100644 --- a/src/email/envelope/mod.rs +++ b/src/email/envelope/mod.rs @@ -1,432 +1,3 @@ pub mod arg; pub mod command; -pub mod config; pub mod flag; - -use color_eyre::Result; -use comfy_table::{Attribute, Cell, ContentArrangement, Row, Table}; -use crossterm::{ - cursor, - style::{Color, Stylize}, - terminal, -}; -use email::{account::config::AccountConfig, envelope::ThreadedEnvelope}; -use petgraph::graphmap::DiGraphMap; -use serde::{Serialize, Serializer}; -use std::{collections::HashMap, fmt, ops::Deref, sync::Arc}; - -use crate::{ - cache::IdMapper, - flag::{Flag, Flags}, -}; - -use self::config::ListEnvelopesTableConfig; - -#[derive(Clone, Debug, Default, Serialize)] -pub struct Mailbox { - pub name: Option, - pub addr: String, -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct Envelope { - pub id: String, - pub flags: Flags, - pub subject: String, - pub from: Mailbox, - pub to: Mailbox, - pub date: String, - pub has_attachment: bool, -} - -impl Envelope { - fn to_row(&self, config: &ListEnvelopesTableConfig) -> Row { - let mut all_attributes = vec![]; - - let unseen = !self.flags.contains(&Flag::Seen); - if unseen { - all_attributes.push(Attribute::Bold) - } - - let flags = { - let mut flags = String::new(); - - flags.push(config.flagged_char(self.flags.contains(&Flag::Flagged))); - flags.push(config.unseen_char(unseen)); - flags.push(config.attachment_char(self.has_attachment)); - flags.push(config.replied_char(self.flags.contains(&Flag::Answered))); - - flags - }; - - let mut row = Row::new(); - row.max_height(1); - - row.add_cell( - Cell::new(&self.id) - .add_attributes(all_attributes.clone()) - .fg(config.id_color()), - ) - .add_cell( - Cell::new(flags) - .add_attributes(all_attributes.clone()) - .fg(config.flags_color()), - ) - .add_cell( - Cell::new(&self.subject) - .add_attributes(all_attributes.clone()) - .fg(config.subject_color()), - ) - .add_cell( - Cell::new(if let Some(name) = &self.from.name { - name - } else { - &self.from.addr - }) - .add_attributes(all_attributes.clone()) - .fg(config.sender_color()), - ) - .add_cell( - Cell::new(&self.date) - .add_attributes(all_attributes) - .fg(config.date_color()), - ); - - row - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct Envelopes(Vec); - -impl Envelopes { - pub fn try_from_backend( - config: &AccountConfig, - id_mapper: &IdMapper, - envelopes: email::envelope::Envelopes, - ) -> Result { - let envelopes = envelopes - .iter() - .map(|envelope| { - Ok(Envelope { - id: id_mapper.get_or_create_alias(&envelope.id)?, - flags: envelope.flags.clone().into(), - subject: envelope.subject.clone(), - from: Mailbox { - name: envelope.from.name.clone(), - addr: envelope.from.addr.clone(), - }, - to: Mailbox { - name: envelope.to.name.clone(), - addr: envelope.to.addr.clone(), - }, - date: envelope.format_date(config), - has_attachment: envelope.has_attachment, - }) - }) - .collect::>>()?; - - Ok(Envelopes(envelopes)) - } -} - -impl Deref for Envelopes { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub struct EnvelopesTable { - envelopes: Envelopes, - width: Option, - config: ListEnvelopesTableConfig, -} - -impl EnvelopesTable { - pub fn with_some_width(mut self, width: Option) -> Self { - self.width = width; - self - } - - pub fn with_some_preset(mut self, preset: Option) -> Self { - self.config.preset = preset; - self - } - - pub fn with_some_unseen_char(mut self, char: Option) -> Self { - self.config.unseen_char = char; - self - } - - pub fn with_some_replied_char(mut self, char: Option) -> Self { - self.config.replied_char = char; - self - } - - pub fn with_some_flagged_char(mut self, char: Option) -> Self { - self.config.flagged_char = char; - self - } - - pub fn with_some_attachment_char(mut self, char: Option) -> Self { - self.config.attachment_char = char; - self - } - - pub fn with_some_id_color(mut self, color: Option) -> Self { - self.config.id_color = color; - self - } - - pub fn with_some_flags_color(mut self, color: Option) -> Self { - self.config.flags_color = color; - self - } - - pub fn with_some_subject_color(mut self, color: Option) -> Self { - self.config.subject_color = color; - self - } - - pub fn with_some_sender_color(mut self, color: Option) -> Self { - self.config.sender_color = color; - self - } - - pub fn with_some_date_color(mut self, color: Option) -> Self { - self.config.date_color = color; - self - } -} - -impl From for EnvelopesTable { - fn from(envelopes: Envelopes) -> Self { - Self { - envelopes, - width: None, - config: Default::default(), - } - } -} - -impl fmt::Display for EnvelopesTable { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut table = Table::new(); - - table - .load_preset(self.config.preset()) - .set_content_arrangement(ContentArrangement::DynamicFullWidth) - .set_header(Row::from([ - Cell::new("ID"), - Cell::new("FLAGS"), - Cell::new("SUBJECT"), - Cell::new("FROM"), - Cell::new("DATE"), - ])) - .add_rows(self.envelopes.iter().map(|env| env.to_row(&self.config))); - - if let Some(width) = self.width { - table.set_width(width); - } - - writeln!(f)?; - write!(f, "{table}")?; - writeln!(f)?; - Ok(()) - } -} - -impl Serialize for EnvelopesTable { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.envelopes.serialize(serializer) - } -} - -pub struct ThreadedEnvelopes(email::envelope::ThreadedEnvelopes); - -impl ThreadedEnvelopes { - pub fn try_from_backend( - id_mapper: &IdMapper, - envelopes: email::envelope::ThreadedEnvelopes, - ) -> Result { - let prev_edges = envelopes - .graph() - .all_edges() - .map(|(a, b, w)| { - let a = id_mapper.get_or_create_alias(&a.id)?; - let b = id_mapper.get_or_create_alias(&b.id)?; - Ok((a, b, *w)) - }) - .collect::>>()?; - - let envelopes = envelopes - .map() - .iter() - .map(|(_, envelope)| { - let id = id_mapper.get_or_create_alias(&envelope.id)?; - let envelope = email::envelope::Envelope { - id: id.clone(), - message_id: envelope.message_id.clone(), - in_reply_to: envelope.in_reply_to.clone(), - flags: envelope.flags.clone(), - subject: envelope.subject.clone(), - from: envelope.from.clone(), - to: envelope.to.clone(), - date: envelope.date.clone(), - has_attachment: envelope.has_attachment, - }; - - Ok((id, envelope)) - }) - .collect::>>()?; - - let envelopes = email::envelope::ThreadedEnvelopes::build(envelopes, move |envelopes| { - let mut graph = DiGraphMap::::new(); - - for (a, b, w) in prev_edges.clone() { - let eb = envelopes.get(&b).unwrap(); - match envelopes.get(&a) { - Some(ea) => { - graph.add_edge(ea.as_threaded(), eb.as_threaded(), w); - } - None => { - let ea = ThreadedEnvelope { - id: "0", - message_id: "0", - subject: "", - from: "", - date: Default::default(), - }; - graph.add_edge(ea, eb.as_threaded(), w); - } - } - } - - graph - }); - - Ok(ThreadedEnvelopes(envelopes)) - } -} - -impl Deref for ThreadedEnvelopes { - type Target = email::envelope::ThreadedEnvelopes; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub struct EnvelopesTree { - config: Arc, - envelopes: ThreadedEnvelopes, -} - -impl EnvelopesTree { - pub fn new(config: Arc, envelopes: ThreadedEnvelopes) -> Self { - Self { config, envelopes } - } - - pub fn fmt( - f: &mut fmt::Formatter, - config: &AccountConfig, - graph: &DiGraphMap, u8>, - parent: ThreadedEnvelope<'_>, - pad: String, - weight: u8, - ) -> fmt::Result { - let edges = graph - .all_edges() - .filter_map(|(a, b, w)| { - if a == parent && *w == weight { - Some(b) - } else { - None - } - }) - .collect::>(); - - if parent.id == "0" { - f.write_str("root")?; - } else { - write!(f, "{}{}", parent.id.red(), ") ".dark_grey())?; - - if !parent.subject.is_empty() { - write!(f, "{} ", parent.subject.green())?; - } - - if !parent.from.is_empty() { - let left = "<".dark_grey(); - let right = ">".dark_grey(); - write!(f, "{left}{}{right}", parent.from.blue())?; - } - - let date = parent.format_date(config); - let cursor_date_begin_col = terminal::size().unwrap().0 - date.len() as u16; - - let dots = - "·".repeat((cursor_date_begin_col - cursor::position().unwrap().0 - 2) as usize); - write!(f, " {} {}", dots.dark_grey(), date.dark_yellow())?; - } - - writeln!(f)?; - - let edges_count = edges.len(); - for (i, b) in edges.into_iter().enumerate() { - let is_last = edges_count == i + 1; - let (x, y) = if is_last { - (' ', '└') - } else { - ('│', '├') - }; - - write!(f, "{pad}{y}─ ")?; - - let pad = format!("{pad}{x} "); - Self::fmt(f, config, graph, b, pad, weight + 1)?; - } - - Ok(()) - } -} - -impl fmt::Display for EnvelopesTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - EnvelopesTree::fmt( - f, - &self.config, - self.envelopes.0.graph(), - ThreadedEnvelope { - id: "0", - message_id: "0", - from: "", - subject: "", - date: Default::default(), - }, - String::new(), - 0, - ) - } -} - -impl Serialize for EnvelopesTree { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.envelopes.0.serialize(serializer) - } -} - -impl Deref for EnvelopesTree { - type Target = ThreadedEnvelopes; - - fn deref(&self) -> &Self::Target { - &self.envelopes - } -} diff --git a/src/email/message/attachment/command/download.rs b/src/email/message/attachment/command/download.rs index 419a143..55b3680 100644 --- a/src/email/message/attachment/command/download.rs +++ b/src/email/message/attachment/command/download.rs @@ -1,14 +1,17 @@ use clap::Parser; use color_eyre::{eyre::Context, Result}; use email::backend::feature::BackendFeatureSource; -use std::{fs, path::PathBuf}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; +use std::{fs, path::PathBuf, sync::Arc}; use tracing::info; use uuid::Uuid; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, + folder::arg::name::FolderNameOptionalFlag, }; /// Download all attachments for the given message. @@ -28,7 +31,7 @@ pub struct AttachmentDownloadCommand { } impl AttachmentDownloadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing download attachment(s) command"); let folder = &self.folder.name; @@ -38,14 +41,19 @@ impl AttachmentDownloadCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let get_messages_kind = toml_account_config.get_messages_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - get_messages_kind, - |builder| builder.set_get_messages(BackendFeatureSource::Context), + |builder| { + builder + .without_features() + .with_get_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let emails = backend.get_messages(folder, ids).await?; diff --git a/src/email/message/attachment/command/mod.rs b/src/email/message/attachment/command/mod.rs index 38e71f6..fe3577d 100644 --- a/src/email/message/attachment/command/mod.rs +++ b/src/email/message/attachment/command/mod.rs @@ -2,8 +2,9 @@ mod download; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::download::AttachmentDownloadCommand; @@ -19,7 +20,7 @@ pub enum AttachmentSubcommand { } impl AttachmentSubcommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::Download(cmd) => cmd.execute(printer, config).await, } diff --git a/src/email/message/command/copy.rs b/src/email/message/command/copy.rs index 12c0da3..64728fc 100644 --- a/src/email/message/command/copy.rs +++ b/src/email/message/command/copy.rs @@ -1,15 +1,19 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::{SourceFolderNameOptionalFlag, TargetFolderNameArg}, - printer::Printer, }; /// Copy a message from a source folder to a target folder. @@ -29,7 +33,7 @@ pub struct MessageCopyCommand { } impl MessageCopyCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing copy message(s) command"); let source = &self.source_folder.name; @@ -40,20 +44,23 @@ impl MessageCopyCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let copy_messages_kind = toml_account_config.copy_messages_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - copy_messages_kind, - |builder| builder.set_copy_messages(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_copy_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.copy_messages(source, target, ids).await?; printer.out(format!( - "Message(s) successfully copied from {source} to {target}!" + "Message(s) successfully copied from {source} to {target}!\n" )) } } diff --git a/src/email/message/command/delete.rs b/src/email/message/command/delete.rs index 9916202..e765878 100644 --- a/src/email/message/command/delete.rs +++ b/src/email/message/command/delete.rs @@ -1,12 +1,17 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, + folder::arg::name::FolderNameOptionalFlag, }; /// Mark as deleted a message from a folder. @@ -28,7 +33,7 @@ pub struct MessageDeleteCommand { } impl MessageDeleteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing delete message(s) command"); let folder = &self.folder.name; @@ -38,18 +43,21 @@ impl MessageDeleteCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let delete_messages_kind = toml_account_config.delete_messages_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - delete_messages_kind, - |builder| builder.set_delete_messages(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_delete_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.delete_messages(folder, ids).await?; - printer.out(format!("Message(s) successfully removed from {folder}!")) + printer.out(format!("Message(s) successfully removed from {folder}!\n")) } } diff --git a/src/email/message/command/forward.rs b/src/email/message/command/forward.rs index 1599927..d4287f6 100644 --- a/src/email/message/command/forward.rs +++ b/src/email/message/command/forward.rs @@ -1,17 +1,20 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::{eyre::eyre, Result}; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::{backend::BackendBuilder, editor}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, envelope::arg::ids::EnvelopeIdArg, folder::arg::name::FolderNameOptionalFlag, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, - printer::Printer, - ui::editor, }; /// Forward a message. @@ -39,7 +42,7 @@ pub struct MessageForwardCommand { } impl MessageForwardCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing forward message command"); let folder = &self.folder.name; @@ -48,18 +51,20 @@ impl MessageForwardCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let add_message_kind = toml_account_config.add_message_kind(); - let send_message_kind = toml_account_config.send_message_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - add_message_kind.into_iter().chain(send_message_kind), |builder| { - builder.set_add_message(BackendFeatureSource::Context); - builder.set_send_message(BackendFeatureSource::Context); + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + .with_send_message(BackendFeatureSource::Context) }, ) + .without_sending_backend() + .build() .await?; let id = self.envelope.id; diff --git a/src/email/message/command/mailto.rs b/src/email/message/command/mailto.rs index 0822d5c..034a425 100644 --- a/src/email/message/command/mailto.rs +++ b/src/email/message/command/mailto.rs @@ -1,14 +1,17 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; use mail_builder::MessageBuilder; +use pimalaya_tui::{ + himalaya::{backend::BackendBuilder, editor}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use url::Url; -use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, printer::Printer, - ui::editor, -}; +use crate::{account::arg::name::AccountNameFlag, config::TomlConfig}; /// Parse and edit a message from a mailto URL string. /// @@ -34,25 +37,27 @@ impl MessageMailtoCommand { }) } - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing mailto message command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let add_message_kind = toml_account_config.add_message_kind(); - let send_message_kind = toml_account_config.send_message_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - add_message_kind.into_iter().chain(send_message_kind), |builder| { - builder.set_add_message(BackendFeatureSource::Context); - builder.set_send_message(BackendFeatureSource::Context); + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + .with_send_message(BackendFeatureSource::Context) }, ) + .without_sending_backend() + .build() .await?; let mut builder = MessageBuilder::new().to(self.url.path()); diff --git a/src/email/message/command/mod.rs b/src/email/message/command/mod.rs index 9aedfc2..fa8cdff 100644 --- a/src/email/message/command/mod.rs +++ b/src/email/message/command/mod.rs @@ -12,8 +12,9 @@ pub mod write; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::{ copy::MessageCopyCommand, delete::MessageDeleteCommand, forward::MessageForwardCommand, @@ -67,7 +68,7 @@ pub enum MessageSubcommand { impl MessageSubcommand { #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::Read(cmd) => cmd.execute(printer, config).await, Self::Thread(cmd) => cmd.execute(printer, config).await, diff --git a/src/email/message/command/move.rs b/src/email/message/command/move.rs index ad7ff1b..0371d16 100644 --- a/src/email/message/command/move.rs +++ b/src/email/message/command/move.rs @@ -1,16 +1,20 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; #[allow(unused)] use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::{SourceFolderNameOptionalFlag, TargetFolderNameArg}, - printer::Printer, }; /// Move a message from a source folder to a target folder. @@ -30,7 +34,7 @@ pub struct MessageMoveCommand { } impl MessageMoveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing move message(s) command"); let source = &self.source_folder.name; @@ -41,20 +45,23 @@ impl MessageMoveCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let move_messages_kind = toml_account_config.move_messages_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - move_messages_kind, - |builder| builder.set_move_messages(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_move_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.move_messages(source, target, ids).await?; printer.out(format!( - "Message(s) successfully moved from {source} to {target}!" + "Message(s) successfully moved from {source} to {target}!\n" )) } } diff --git a/src/email/message/command/read.rs b/src/email/message/command/read.rs index 21896b5..f4614c3 100644 --- a/src/email/message/command/read.rs +++ b/src/email/message/command/read.rs @@ -1,14 +1,19 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; use mml::message::FilterParts; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; #[allow(unused)] use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, + folder::arg::name::FolderNameOptionalFlag, }; /// Read a message. @@ -73,7 +78,7 @@ pub struct MessageReadCommand { } impl MessageReadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing read message(s) command"); let folder = &self.folder.name; @@ -83,14 +88,19 @@ impl MessageReadCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let get_messages_kind = toml_account_config.get_messages_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - get_messages_kind, - |builder| builder.set_get_messages(BackendFeatureSource::Context), + |builder| { + builder + .without_features() + .with_get_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let emails = if self.preview { diff --git a/src/email/message/command/reply.rs b/src/email/message/command/reply.rs index 87f64d9..aba2f84 100644 --- a/src/email/message/command/reply.rs +++ b/src/email/message/command/reply.rs @@ -1,17 +1,20 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::{eyre::eyre, Result}; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::{backend::BackendBuilder, editor}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, envelope::arg::ids::EnvelopeIdArg, folder::arg::name::FolderNameOptionalFlag, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg}, - printer::Printer, - ui::editor, }; /// Reply to a message. @@ -42,7 +45,7 @@ pub struct MessageReplyCommand { } impl MessageReplyCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing reply message command"); let folder = &self.folder.name; @@ -50,18 +53,19 @@ impl MessageReplyCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let add_message_kind = toml_account_config.add_message_kind(); - let send_message_kind = toml_account_config.send_message_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - add_message_kind.into_iter().chain(send_message_kind), |builder| { - builder.set_add_message(BackendFeatureSource::Context); - builder.set_send_message(BackendFeatureSource::Context); + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + .with_send_message(BackendFeatureSource::Context) }, ) + .build() .await?; let id = self.envelope.id; diff --git a/src/email/message/command/save.rs b/src/email/message/command/save.rs index 2e997c7..d80bcb5 100644 --- a/src/email/message/command/save.rs +++ b/src/email/message/command/save.rs @@ -1,13 +1,19 @@ use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; -use std::io::{self, BufRead, IsTerminal}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; +use std::{ + io::{self, BufRead, IsTerminal}, + sync::Arc, +}; use tracing::info; -#[allow(unused)] use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - folder::arg::name::FolderNameOptionalFlag, message::arg::MessageRawArg, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, + folder::arg::name::FolderNameOptionalFlag, message::arg::MessageRawArg, }; /// Save a message to a folder. @@ -26,7 +32,7 @@ pub struct MessageSaveCommand { } impl MessageSaveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing save message command"); let folder = &self.folder.name; @@ -35,14 +41,17 @@ impl MessageSaveCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let add_message_kind = toml_account_config.add_message_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - add_message_kind, - |builder| builder.set_add_message(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let is_tty = io::stdin().is_terminal(); @@ -60,6 +69,6 @@ impl MessageSaveCommand { backend.add_message(folder, msg.as_bytes()).await?; - printer.out(format!("Message successfully saved to {folder}!")) + printer.out(format!("Message successfully saved to {folder}!\n")) } } diff --git a/src/email/message/command/send.rs b/src/email/message/command/send.rs index 029b1bc..0b48ff5 100644 --- a/src/email/message/command/send.rs +++ b/src/email/message/command/send.rs @@ -1,13 +1,17 @@ use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; -use std::io::{self, BufRead, IsTerminal}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; +use std::{ + io::{self, BufRead, IsTerminal}, + sync::Arc, +}; use tracing::info; -use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - message::arg::MessageRawArg, printer::Printer, -}; +use crate::{account::arg::name::AccountNameFlag, config::TomlConfig, message::arg::MessageRawArg}; /// Send a message. /// @@ -23,28 +27,24 @@ pub struct MessageSendCommand { } impl MessageSendCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing send message command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let send_message_kind = toml_account_config.send_message_kind().into_iter().chain( - toml_account_config - .add_message_kind() - .filter(|_| account_config.should_save_copy_sent_message()), - ); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - send_message_kind, + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), |builder| { - builder.set_send_message(BackendFeatureSource::Context); - builder.set_add_message(BackendFeatureSource::Context); + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + .with_send_message(BackendFeatureSource::Context) }, ) + .build() .await?; let msg = if io::stdin().is_terminal() { diff --git a/src/email/message/command/thread.rs b/src/email/message/command/thread.rs index 469dc2a..9a83091 100644 --- a/src/email/message/command/thread.rs +++ b/src/email/message/command/thread.rs @@ -1,15 +1,20 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; use mml::message::FilterParts; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::envelope::arg::ids::EnvelopeIdArg; #[allow(unused)] use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, + folder::arg::name::FolderNameOptionalFlag, }; /// Thread a message. @@ -74,7 +79,7 @@ pub struct MessageThreadCommand { } impl MessageThreadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing thread message(s) command"); let folder = &self.folder.name; @@ -84,17 +89,20 @@ impl MessageThreadCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let get_messages_kind = toml_account_config.get_messages_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - get_messages_kind, |builder| { - builder.set_thread_envelopes(BackendFeatureSource::Context); - builder.set_get_messages(BackendFeatureSource::Context); + builder + .without_features() + .with_get_messages(BackendFeatureSource::Context) + .with_thread_envelopes(BackendFeatureSource::Context) }, ) + .without_sending_backend() + .build() .await?; let envelopes = backend diff --git a/src/email/message/command/write.rs b/src/email/message/command/write.rs index 4e7b08b..7113fb1 100644 --- a/src/email/message/command/write.rs +++ b/src/email/message/command/write.rs @@ -1,15 +1,18 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::{backend::feature::BackendFeatureSource, message::Message}; +use pimalaya_tui::{ + himalaya::{backend::BackendBuilder, editor}, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, - printer::Printer, - ui::editor, }; /// Write a new message. @@ -31,25 +34,26 @@ pub struct MessageWriteCommand { } impl MessageWriteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing write message command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let add_message_kind = toml_account_config.add_message_kind(); - let send_message_kind = toml_account_config.send_message_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - add_message_kind.into_iter().chain(send_message_kind), |builder| { - builder.set_add_message(BackendFeatureSource::Context); - builder.set_send_message(BackendFeatureSource::Context); + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + .with_send_message(BackendFeatureSource::Context) }, ) + .build() .await?; let tpl = Message::new_tpl_builder(account_config.clone()) diff --git a/src/email/message/config.rs b/src/email/message/config.rs deleted file mode 100644 index 3ac987c..0000000 --- a/src/email/message/config.rs +++ /dev/null @@ -1,185 +0,0 @@ -use email::message::delete::config::DeleteMessageStyle; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; - -use crate::backend::BackendKind; - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageConfig { - pub write: Option, - pub send: Option, - pub peek: Option, - pub read: Option, - pub copy: Option, - pub r#move: Option, - pub delete: Option, -} - -impl MessageConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(add) = &self.write { - kinds.extend(add.get_used_backends()); - } - - if let Some(send) = &self.send { - kinds.extend(send.get_used_backends()); - } - - if let Some(peek) = &self.peek { - kinds.extend(peek.get_used_backends()); - } - - if let Some(get) = &self.read { - kinds.extend(get.get_used_backends()); - } - - if let Some(copy) = &self.copy { - kinds.extend(copy.get_used_backends()); - } - - if let Some(move_) = &self.r#move { - kinds.extend(move_.get_used_backends()); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageAddConfig { - pub backend: Option, - - #[serde(flatten)] - pub remote: email::message::add::config::MessageWriteConfig, -} - -impl MessageAddConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageSendConfig { - pub backend: Option, - - #[serde(flatten)] - pub remote: email::message::send::config::MessageSendConfig, -} - -impl MessageSendConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessagePeekConfig { - pub backend: Option, -} - -impl MessagePeekConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageGetConfig { - pub backend: Option, - - #[serde(flatten)] - pub remote: email::message::get::config::MessageReadConfig, -} - -impl MessageGetConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageCopyConfig { - pub backend: Option, -} - -impl MessageCopyConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct MessageMoveConfig { - pub backend: Option, -} - -impl MessageMoveConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct DeleteMessageConfig { - pub backend: Option, - pub style: Option, -} - -impl From for email::message::delete::config::DeleteMessageConfig { - fn from(config: DeleteMessageConfig) -> Self { - Self { - style: config.style, - } - } -} - -impl DeleteMessageConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} diff --git a/src/email/message/mod.rs b/src/email/message/mod.rs index b442bc0..57d3b73 100644 --- a/src/email/message/mod.rs +++ b/src/email/message/mod.rs @@ -1,5 +1,4 @@ pub mod arg; pub mod attachment; pub mod command; -pub mod config; pub mod template; diff --git a/src/email/message/template/command/forward.rs b/src/email/message/template/command/forward.rs index 2ce3aa2..5300d3b 100644 --- a/src/email/message/template/command/forward.rs +++ b/src/email/message/template/command/forward.rs @@ -1,16 +1,20 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::{eyre::eyre, Result}; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, envelope::arg::ids::EnvelopeIdArg, folder::arg::name::FolderNameOptionalFlag, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, - printer::Printer, }; /// Generate a template for forwarding a message. @@ -37,7 +41,7 @@ pub struct TemplateForwardCommand { } impl TemplateForwardCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing forward template command"); let folder = &self.folder.name; @@ -46,14 +50,19 @@ impl TemplateForwardCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let get_messages_kind = toml_account_config.get_messages_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - get_messages_kind, - |builder| builder.set_get_messages(BackendFeatureSource::Context), + |builder| { + builder + .without_features() + .with_get_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let id = self.envelope.id; diff --git a/src/email/message/template/command/mod.rs b/src/email/message/template/command/mod.rs index 3deb46f..e9c98a9 100644 --- a/src/email/message/template/command/mod.rs +++ b/src/email/message/template/command/mod.rs @@ -6,8 +6,9 @@ mod write; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::{ forward::TemplateForwardCommand, reply::TemplateReplyCommand, save::TemplateSaveCommand, @@ -43,7 +44,7 @@ pub enum TemplateSubcommand { } impl TemplateSubcommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::Write(cmd) => cmd.execute(printer, config).await, Self::Reply(cmd) => cmd.execute(printer, config).await, diff --git a/src/email/message/template/command/reply.rs b/src/email/message/template/command/reply.rs index a8e08b0..77cd00d 100644 --- a/src/email/message/template/command/reply.rs +++ b/src/email/message/template/command/reply.rs @@ -1,16 +1,20 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::{eyre::eyre, Result}; use email::backend::feature::BackendFeatureSource; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, + config::TomlConfig, envelope::arg::ids::EnvelopeIdArg, folder::arg::name::FolderNameOptionalFlag, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg}, - printer::Printer, }; /// Generate a template for replying to a message. @@ -41,7 +45,7 @@ pub struct TemplateReplyCommand { } impl TemplateReplyCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing reply template command"); let folder = &self.folder.name; @@ -51,14 +55,19 @@ impl TemplateReplyCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let get_messages_kind = toml_account_config.get_messages_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - get_messages_kind, - |builder| builder.set_get_messages(BackendFeatureSource::Context), + |builder| { + builder + .without_features() + .with_get_messages(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let tpl = backend diff --git a/src/email/message/template/command/save.rs b/src/email/message/template/command/save.rs index 566fabf..6be452a 100644 --- a/src/email/message/template/command/save.rs +++ b/src/email/message/template/command/save.rs @@ -2,13 +2,19 @@ use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; use mml::MmlCompilerBuilder; -use std::io::{self, BufRead, IsTerminal}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; +use std::{ + io::{self, BufRead, IsTerminal}, + sync::Arc, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - email::template::arg::TemplateRawArg, folder::arg::name::FolderNameOptionalFlag, - printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::TemplateRawArg, + folder::arg::name::FolderNameOptionalFlag, }; /// Save a template to a folder. @@ -30,7 +36,7 @@ pub struct TemplateSaveCommand { } impl TemplateSaveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing save template command"); let folder = &self.folder.name; @@ -39,14 +45,19 @@ impl TemplateSaveCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let add_message_kind = toml_account_config.add_message_kind(); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - add_message_kind, - |builder| builder.set_add_message(BackendFeatureSource::Context), + |builder| { + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let is_tty = io::stdin().is_terminal(); @@ -72,6 +83,6 @@ impl TemplateSaveCommand { backend.add_message(folder, &msg).await?; - printer.out(format!("Template successfully saved to {folder}!")) + printer.out(format!("Template successfully saved to {folder}!\n")) } } diff --git a/src/email/message/template/command/send.rs b/src/email/message/template/command/send.rs index eba4521..9cb2f2f 100644 --- a/src/email/message/template/command/send.rs +++ b/src/email/message/template/command/send.rs @@ -2,12 +2,18 @@ use clap::Parser; use color_eyre::Result; use email::backend::feature::BackendFeatureSource; use mml::MmlCompilerBuilder; -use std::io::{self, BufRead, IsTerminal}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; +use std::{ + io::{self, BufRead, IsTerminal}, + sync::Arc, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - email::template::arg::TemplateRawArg, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::TemplateRawArg, }; /// Send a template. @@ -26,28 +32,26 @@ pub struct TemplateSendCommand { } impl TemplateSendCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing send template command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let send_message_kind = toml_account_config.send_message_kind().into_iter().chain( - toml_account_config - .add_message_kind() - .filter(|_| account_config.should_save_copy_sent_message()), - ); + let account_config = Arc::new(account_config); - let backend = Backend::new( - toml_account_config.clone(), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), account_config.clone(), - send_message_kind, |builder| { - builder.set_send_message(BackendFeatureSource::Context); - builder.set_add_message(BackendFeatureSource::Context); + builder + .without_features() + .with_add_message(BackendFeatureSource::Context) + .with_send_message(BackendFeatureSource::Context) }, ) + .build() .await?; let tpl = if io::stdin().is_terminal() { diff --git a/src/email/message/template/command/write.rs b/src/email/message/template/command/write.rs index 9737352..e65aab5 100644 --- a/src/email/message/template/command/write.rs +++ b/src/email/message/template/command/write.rs @@ -1,12 +1,14 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::message::Message; +use pimalaya_tui::terminal::{cli::printer::Printer, config::TomlConfig as _}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, config::Config, + account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::body::TemplateRawBodyArg, message::arg::header::HeaderRawArgs, - printer::Printer, }; /// Generate a template for writing a new message from scratch. @@ -26,14 +28,14 @@ pub struct TemplateWriteCommand { } impl TemplateWriteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing write template command"); let (_, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let tpl = Message::new_tpl_builder(account_config) + let tpl = Message::new_tpl_builder(Arc::new(account_config)) .with_headers(self.headers.raw) .with_body(self.body.raw()) .build() diff --git a/src/folder/command/add.rs b/src/folder/command/add.rs index 0577214..a4a913c 100644 --- a/src/folder/command/add.rs +++ b/src/folder/command/add.rs @@ -1,11 +1,16 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::{backend::feature::BackendFeatureSource, folder::add::AddFolder}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - folder::arg::name::FolderNameArg, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg, }; /// Create a new folder. @@ -22,26 +27,30 @@ pub struct AddFolderCommand { } impl AddFolderCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing create folder command"); let folder = &self.folder.name; + let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let add_folder_kind = toml_account_config.add_folder_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - add_folder_kind, - |builder| builder.set_add_folder(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_add_folder(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.add_folder(folder).await?; - printer.log(format!("Folder {folder} successfully created!")) + printer.out(format!("Folder {folder} successfully created!\n")) } } diff --git a/src/folder/command/delete.rs b/src/folder/command/delete.rs index 528b4fa..de0addd 100644 --- a/src/folder/command/delete.rs +++ b/src/folder/command/delete.rs @@ -1,14 +1,16 @@ -use std::process; +use std::{process, sync::Arc}; use clap::Parser; use color_eyre::Result; use email::{backend::feature::BackendFeatureSource, folder::delete::DeleteFolder}; -use pimalaya_tui::prompt; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _, prompt}, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - folder::arg::name::FolderNameArg, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg, }; /// Delete a folder. @@ -25,12 +27,13 @@ pub struct FolderDeleteCommand { } impl FolderDeleteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing delete folder command"); let folder = &self.folder.name; - let confirm = format!("Do you really want to delete the folder {folder}? All emails will be definitely deleted."); + let confirm = format!("Do you really want to delete the folder {folder}"); + let confirm = format!("{confirm}? All emails will be definitely deleted."); if !prompt::bool(confirm, false)? { process::exit(0); @@ -40,18 +43,21 @@ impl FolderDeleteCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let delete_folder_kind = toml_account_config.delete_folder_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - delete_folder_kind, - |builder| builder.set_delete_folder(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_delete_folder(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.delete_folder(folder).await?; - printer.log(format!("Folder {folder} successfully deleted!")) + printer.out(format!("Folder {folder} successfully deleted!\n")) } } diff --git a/src/folder/command/expunge.rs b/src/folder/command/expunge.rs index bc4400b..8577295 100644 --- a/src/folder/command/expunge.rs +++ b/src/folder/command/expunge.rs @@ -1,11 +1,16 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::{backend::feature::BackendFeatureSource, folder::expunge::ExpungeFolder}; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - folder::arg::name::FolderNameArg, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg, }; /// Expunge a folder. @@ -23,7 +28,7 @@ pub struct FolderExpungeCommand { } impl FolderExpungeCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing expunge folder command"); let folder = &self.folder.name; @@ -31,18 +36,21 @@ impl FolderExpungeCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let expunge_folder_kind = toml_account_config.expunge_folder_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - expunge_folder_kind, - |builder| builder.set_expunge_folder(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_expunge_folder(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.expunge_folder(folder).await?; - printer.log(format!("Folder {folder} successfully expunged!")) + printer.out(format!("Folder {folder} successfully expunged!\n")) } } diff --git a/src/folder/command/list.rs b/src/folder/command/list.rs index 038a407..1eb3f66 100644 --- a/src/folder/command/list.rs +++ b/src/folder/command/list.rs @@ -1,15 +1,18 @@ +use std::sync::Arc; + use clap::Parser; use color_eyre::Result; use email::{backend::feature::BackendFeatureSource, folder::list::ListFolders}; +use pimalaya_tui::{ + himalaya::{ + backend::BackendBuilder, + config::{Folders, FoldersTable}, + }, + terminal::{cli::printer::Printer, config::TomlConfig as _}, +}; use tracing::info; -use crate::{ - account::arg::name::AccountNameFlag, - backend::Backend, - config::Config, - folder::{Folders, FoldersTable}, - printer::Printer, -}; +use crate::{account::arg::name::AccountNameFlag, config::TomlConfig}; /// List all folders. /// @@ -29,21 +32,26 @@ pub struct FolderListCommand { } impl FolderListCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing list folders command"); let (toml_account_config, account_config) = config .clone() .into_account_configs(self.account.name.as_deref())?; - let list_folders_kind = toml_account_config.list_folders_kind(); + let toml_account_config = Arc::new(toml_account_config); - let backend = Backend::new( + let backend = BackendBuilder::new( toml_account_config.clone(), - account_config.clone(), - list_folders_kind, - |builder| builder.set_list_folders(BackendFeatureSource::Context), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_list_folders(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; let folders = Folders::from(backend.list_folders().await?); diff --git a/src/folder/command/mod.rs b/src/folder/command/mod.rs index 61a5708..1eb92af 100644 --- a/src/folder/command/mod.rs +++ b/src/folder/command/mod.rs @@ -6,8 +6,9 @@ mod purge; use clap::Subcommand; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; -use crate::{config::Config, printer::Printer}; +use crate::config::TomlConfig; use self::{ add::AddFolderCommand, delete::FolderDeleteCommand, expunge::FolderExpungeCommand, @@ -38,7 +39,7 @@ pub enum FolderSubcommand { impl FolderSubcommand { #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::Add(cmd) => cmd.execute(printer, config).await, Self::List(cmd) => cmd.execute(printer, config).await, diff --git a/src/folder/command/purge.rs b/src/folder/command/purge.rs index 3cfb9e2..c1b9b50 100644 --- a/src/folder/command/purge.rs +++ b/src/folder/command/purge.rs @@ -1,14 +1,16 @@ -use std::process; +use std::{process, sync::Arc}; use clap::Parser; use color_eyre::Result; use email::{backend::feature::BackendFeatureSource, folder::purge::PurgeFolder}; -use pimalaya_tui::prompt; +use pimalaya_tui::{ + himalaya::backend::BackendBuilder, + terminal::{cli::printer::Printer, config::TomlConfig as _, prompt}, +}; use tracing::info; use crate::{ - account::arg::name::AccountNameFlag, backend::Backend, config::Config, - folder::arg::name::FolderNameArg, printer::Printer, + account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg, }; /// Purge a folder. @@ -25,12 +27,13 @@ pub struct FolderPurgeCommand { } impl FolderPurgeCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing purge folder command"); let folder = &self.folder.name; - let confirm = format!("Do you really want to purge the folder {folder}? All emails will be definitely deleted."); + let confirm = format!("Do you really want to purge the folder {folder}"); + let confirm = format!("{confirm}? All emails will be definitely deleted."); if !prompt::bool(confirm, false)? { process::exit(0); @@ -40,18 +43,21 @@ impl FolderPurgeCommand { .clone() .into_account_configs(self.account.name.as_deref())?; - let purge_folder_kind = toml_account_config.purge_folder_kind(); - - let backend = Backend::new( - toml_account_config.clone(), - account_config, - purge_folder_kind, - |builder| builder.set_purge_folder(BackendFeatureSource::Context), + let backend = BackendBuilder::new( + Arc::new(toml_account_config), + Arc::new(account_config), + |builder| { + builder + .without_features() + .with_purge_folder(BackendFeatureSource::Context) + }, ) + .without_sending_backend() + .build() .await?; backend.purge_folder(folder).await?; - printer.log(format!("Folder {folder} successfully purged!")) + printer.out(format!("Folder {folder} successfully purged!\n")) } } diff --git a/src/folder/config.rs b/src/folder/config.rs deleted file mode 100644 index 6ce8bf3..0000000 --- a/src/folder/config.rs +++ /dev/null @@ -1,157 +0,0 @@ -use comfy_table::presets; -use crossterm::style::Color; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; - -use crate::{backend::BackendKind, ui::map_color}; - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FolderConfig { - #[serde(alias = "aliases")] - pub alias: Option>, - pub add: Option, - pub list: Option, - pub expunge: Option, - pub purge: Option, - pub delete: Option, -} - -impl FolderConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(add) = &self.add { - kinds.extend(add.get_used_backends()); - } - - if let Some(list) = &self.list { - kinds.extend(list.get_used_backends()); - } - - if let Some(expunge) = &self.expunge { - kinds.extend(expunge.get_used_backends()); - } - - if let Some(purge) = &self.purge { - kinds.extend(purge.get_used_backends()); - } - - if let Some(delete) = &self.delete { - kinds.extend(delete.get_used_backends()); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FolderAddConfig { - pub backend: Option, -} - -impl FolderAddConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct FolderListConfig { - pub backend: Option, - pub table: Option, - - #[serde(flatten)] - pub remote: email::folder::list::config::FolderListConfig, -} - -impl FolderListConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ListFoldersTableConfig { - pub preset: Option, - pub name_color: Option, - pub desc_color: Option, -} - -impl ListFoldersTableConfig { - pub fn preset(&self) -> &str { - self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN) - } - - pub fn name_color(&self) -> comfy_table::Color { - map_color(self.name_color.unwrap_or(Color::Blue)) - } - - pub fn desc_color(&self) -> comfy_table::Color { - map_color(self.desc_color.unwrap_or(Color::Green)) - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FolderExpungeConfig { - pub backend: Option, -} - -impl FolderExpungeConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FolderPurgeConfig { - pub backend: Option, -} - -impl FolderPurgeConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct FolderDeleteConfig { - pub backend: Option, -} - -impl FolderDeleteConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} diff --git a/src/folder/mod.rs b/src/folder/mod.rs index b00d79f..1a3a73a 100644 --- a/src/folder/mod.rs +++ b/src/folder/mod.rs @@ -1,126 +1,2 @@ pub mod arg; pub mod command; -pub mod config; - -use comfy_table::{Cell, ContentArrangement, Row, Table}; -use crossterm::style::Color; -use serde::{Serialize, Serializer}; -use std::{fmt, ops::Deref}; - -use self::config::ListFoldersTableConfig; - -#[derive(Clone, Debug, Default, Serialize)] -pub struct Folder { - pub name: String, - pub desc: String, -} - -impl Folder { - pub fn to_row(&self, config: &ListFoldersTableConfig) -> Row { - let mut row = Row::new(); - row.max_height(1); - - row.add_cell(Cell::new(&self.name).fg(config.name_color())); - row.add_cell(Cell::new(&self.desc).fg(config.desc_color())); - - row - } -} - -impl From for Folder { - fn from(folder: email::folder::Folder) -> Self { - Folder { - name: folder.name, - desc: folder.desc, - } - } -} - -#[derive(Clone, Debug, Default, Serialize)] -pub struct Folders(Vec); - -impl Deref for Folders { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Folders { - fn from(folders: email::folder::Folders) -> Self { - Folders(folders.into_iter().map(Folder::from).collect()) - } -} - -pub struct FoldersTable { - folders: Folders, - width: Option, - config: ListFoldersTableConfig, -} - -impl FoldersTable { - pub fn with_some_width(mut self, width: Option) -> Self { - self.width = width; - self - } - - pub fn with_some_preset(mut self, preset: Option) -> Self { - self.config.preset = preset; - self - } - - pub fn with_some_name_color(mut self, color: Option) -> Self { - self.config.name_color = color; - self - } - - pub fn with_some_desc_color(mut self, color: Option) -> Self { - self.config.desc_color = color; - self - } -} - -impl From for FoldersTable { - fn from(folders: Folders) -> Self { - Self { - folders, - width: None, - config: Default::default(), - } - } -} - -impl fmt::Display for FoldersTable { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut table = Table::new(); - - table - .load_preset(self.config.preset()) - .set_content_arrangement(ContentArrangement::DynamicFullWidth) - .set_header(Row::from([Cell::new("NAME"), Cell::new("DESC")])) - .add_rows( - self.folders - .iter() - .map(|folder| folder.to_row(&self.config)), - ); - - if let Some(width) = self.width { - table.set_width(width); - } - - writeln!(f)?; - write!(f, "{table}")?; - writeln!(f)?; - Ok(()) - } -} - -impl Serialize for FoldersTable { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.folders.serialize(serializer) - } -} diff --git a/src/lib.rs b/src/lib.rs index 45ccab8..0f79671 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,10 @@ pub mod account; -pub mod backend; -pub mod cache; pub mod cli; pub mod completion; pub mod config; pub mod email; pub mod folder; pub mod manual; -pub mod output; -pub mod printer; -pub mod ui; #[doc(inline)] pub use crate::email::{envelope, flag, message}; diff --git a/src/main.rs b/src/main.rs index bc12106..db54231 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ use clap::Parser; use color_eyre::Result; use himalaya::{ - cli::Cli, config::Config, envelope::command::list::ListEnvelopesCommand, - message::command::mailto::MessageMailtoCommand, printer::StdoutPrinter, + cli::Cli, config::TomlConfig, envelope::command::list::ListEnvelopesCommand, + message::command::mailto::MessageMailtoCommand, +}; +use pimalaya_tui::terminal::{ + cli::{printer::StdoutPrinter, tracing}, + config::TomlConfig as _, }; -use pimalaya_tui::cli::tracing; #[tokio::main] async fn main() -> Result<()> { @@ -21,7 +24,7 @@ async fn main() -> Result<()> { if let Some(ref url) = mailto { let mut printer = StdoutPrinter::default(); - let config = Config::from_default_paths().await?; + let config = TomlConfig::from_default_paths().await?; return MessageMailtoCommand::new(url)? .execute(&mut printer, &config) @@ -33,7 +36,7 @@ async fn main() -> Result<()> { let res = match cli.command { Some(cmd) => cmd.execute(&mut printer, cli.config_paths.as_ref()).await, None => { - let config = Config::from_paths_or_default(cli.config_paths.as_ref()).await?; + let config = TomlConfig::from_paths_or_default(cli.config_paths.as_ref()).await?; ListEnvelopesCommand::default() .execute(&mut printer, &config) .await diff --git a/src/manual/command.rs b/src/manual/command.rs index ba69222..bf334b8 100644 --- a/src/manual/command.rs +++ b/src/manual/command.rs @@ -1,11 +1,12 @@ use clap::{CommandFactory, Parser}; use clap_mangen::Man; use color_eyre::Result; +use pimalaya_tui::terminal::cli::printer::Printer; use shellexpand_utils::{canonicalize, expand}; use std::{fs, path::PathBuf}; use tracing::info; -use crate::{cli::Cli, printer::Printer}; +use crate::cli::Cli; /// Generate manual pages to a directory. /// diff --git a/src/output/args.rs b/src/output/args.rs deleted file mode 100644 index 812052b..0000000 --- a/src/output/args.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Module related to output CLI. -//! -//! This module provides arguments related to output. - -use clap::Arg; - -pub(crate) const ARG_COLOR: &str = "color"; -pub(crate) const ARG_OUTPUT: &str = "output"; - -/// Output arguments. -pub fn global_args() -> impl IntoIterator { - [ - Arg::new(ARG_OUTPUT) - .help("Define the output format") - .long("output") - .short('o') - .global(true) - .value_name("format") - .value_parser(["plain", "json"]) - .default_value("plain"), - Arg::new(ARG_COLOR) - .help("Control when to use colors") - .long_help( - "Control when to use colors. - -The default setting is 'auto', which means himalaya will try to guess -when to use colors. For example, if himalaya is printing to a -terminal, then it will use colors, but if it is redirected to a file -or a pipe, then it will suppress color output. himalaya will suppress -color output in some other circumstances as well. For example, if the -TERM environment variable is not set or set to 'dumb', then himalaya -will not use colors. - -The possible values for this flag are: - -never Colors will never be used. -auto The default. himalaya tries to be smart. -always Colors will always be used regardless of where output is sent. -ansi Like 'always', but emits ANSI escapes (even in a Windows console).", - ) - .long("color") - .short('C') - .global(true) - .value_parser(["never", "auto", "always", "ansi"]) - .default_value("auto") - .value_name("mode"), - ] -} diff --git a/src/output/mod.rs b/src/output/mod.rs deleted file mode 100644 index a1ef5bc..0000000 --- a/src/output/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[allow(clippy::module_inception)] -pub mod output; - -pub use output::*; diff --git a/src/output/output.rs b/src/output/output.rs deleted file mode 100644 index 796cca7..0000000 --- a/src/output/output.rs +++ /dev/null @@ -1,49 +0,0 @@ -use clap::ValueEnum; -use color_eyre::{ - eyre::{bail, Error}, - Result, -}; -use serde::Serialize; -use std::{fmt, str::FromStr}; - -/// Represents the available output formats. -#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] -pub enum OutputFmt { - #[default] - Plain, - Json, -} - -impl FromStr for OutputFmt { - type Err = Error; - - fn from_str(fmt: &str) -> Result { - match fmt { - fmt if fmt.eq_ignore_ascii_case("json") => Ok(Self::Json), - fmt if fmt.eq_ignore_ascii_case("plain") => Ok(Self::Plain), - unknown => bail!("cannot parse output format {unknown}"), - } - } -} - -impl fmt::Display for OutputFmt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let fmt = match *self { - OutputFmt::Json => "JSON", - OutputFmt::Plain => "Plain", - }; - write!(f, "{}", fmt) - } -} - -/// Defines a struct-wrapper to provide a JSON output. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct OutputJson { - response: T, -} - -impl OutputJson { - pub fn new(response: T) -> Self { - Self { response } - } -} diff --git a/src/printer.rs b/src/printer.rs deleted file mode 100644 index 4cc4572..0000000 --- a/src/printer.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::{ - fmt, - io::{stderr, stdout, Stderr, Stdout, Write}, -}; - -use color_eyre::{eyre::Context, Result}; - -use crate::output::OutputFmt; - -pub trait PrintTable { - fn print(&self, writer: &mut dyn Write, table_max_width: Option) -> Result<()>; -} - -pub trait Printer { - fn out(&mut self, data: T) -> Result<()>; - - fn log(&mut self, data: T) -> Result<()> { - self.out(data) - } - - fn is_json(&self) -> bool { - false - } -} - -pub struct StdoutPrinter { - stdout: Stdout, - stderr: Stderr, - output: OutputFmt, -} - -impl StdoutPrinter { - pub fn new(output: OutputFmt) -> Self { - Self { - stdout: stdout(), - stderr: stderr(), - output, - } - } -} - -impl Default for StdoutPrinter { - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl Printer for StdoutPrinter { - fn out(&mut self, data: T) -> Result<()> { - match self.output { - OutputFmt::Plain => { - write!(self.stdout, "{data}")?; - } - OutputFmt::Json => { - serde_json::to_writer(&mut self.stdout, &data) - .context("cannot write json to writer")?; - } - }; - - Ok(()) - } - - fn log(&mut self, data: T) -> Result<()> { - if let OutputFmt::Plain = self.output { - write!(&mut self.stderr, "{data}")?; - } - - Ok(()) - } - - fn is_json(&self) -> bool { - self.output == OutputFmt::Json - } -} diff --git a/src/ui/choice.rs b/src/ui/choice.rs deleted file mode 100644 index 9b06db5..0000000 --- a/src/ui/choice.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::fmt; - -use color_eyre::Result; -use pimalaya_tui::prompt; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PreEditChoice { - Edit, - Discard, - Quit, -} - -impl fmt::Display for PreEditChoice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::Edit => "Edit it", - Self::Discard => "Discard it", - Self::Quit => "Quit", - } - ) - } -} - -static PRE_EDIT_CHOICES: [PreEditChoice; 3] = [ - PreEditChoice::Edit, - PreEditChoice::Discard, - PreEditChoice::Quit, -]; - -pub fn pre_edit() -> Result { - let user_choice = prompt::item( - "A draft was found, what would you like to do with it?", - &PRE_EDIT_CHOICES, - None, - )?; - - Ok(user_choice.clone()) -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PostEditChoice { - Send, - Edit, - LocalDraft, - RemoteDraft, - Discard, -} - -impl fmt::Display for PostEditChoice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Send => "Send it", - Self::Edit => "Edit it again", - Self::LocalDraft => "Save it as local draft", - Self::RemoteDraft => "Save it as remote draft", - Self::Discard => "Discard it", - } - ) - } -} - -static POST_EDIT_CHOICES: [PostEditChoice; 5] = [ - PostEditChoice::Send, - PostEditChoice::Edit, - PostEditChoice::LocalDraft, - PostEditChoice::RemoteDraft, - PostEditChoice::Discard, -]; - -pub fn post_edit() -> Result { - let user_choice = prompt::item( - "What would you like to do with this message?", - &POST_EDIT_CHOICES, - None, - )?; - - Ok(user_choice.clone()) -} diff --git a/src/ui/editor.rs b/src/ui/editor.rs deleted file mode 100644 index f9b28b8..0000000 --- a/src/ui/editor.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::{env, fs, sync::Arc}; - -use color_eyre::{eyre::Context, Result}; -use email::{ - account::config::AccountConfig, - email::utils::{local_draft_path, remove_local_draft}, - flag::{Flag, Flags}, - folder::DRAFTS, - template::Template, -}; -use mml::MmlCompilerBuilder; -use process::SingleCommand; -use tracing::debug; - -use crate::{ - backend::Backend, - printer::Printer, - ui::choice::{self, PostEditChoice, PreEditChoice}, -}; - -pub async fn open_with_tpl(tpl: Template) -> Result