From a0dea19cdf797ffabaf62310dc905488277fcea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 16 Oct 2024 11:46:12 +0200 Subject: [PATCH] wip: use shared stuff from pimalaya-tui --- Cargo.lock | 206 ++--- Cargo.toml | 24 +- src/account/command/check_up.rs | 109 ++- src/account/command/configure.rs | 19 +- src/account/command/list.rs | 12 +- src/account/command/mod.rs | 5 +- src/account/config.rs | 364 +-------- src/account/mod.rs | 195 ----- src/account/wizard.rs | 95 --- src/backend/config.rs | 24 - src/backend/mod.rs | 735 ------------------ src/backend/wizard.rs | 75 -- src/cache/arg/disable.rs | 15 - src/cache/arg/mod.rs | 1 - src/cache/mod.rs | 144 ---- src/cli.rs | 27 +- src/config.rs | 4 + src/config/mod.rs | 230 ------ src/config/wizard.rs | 27 - src/email/envelope/command/list.rs | 29 +- src/email/envelope/command/mod.rs | 5 +- src/email/envelope/command/thread.rs | 26 +- src/email/envelope/config.rs | 163 ---- src/email/envelope/flag/command/add.rs | 30 +- src/email/envelope/flag/command/mod.rs | 5 +- src/email/envelope/flag/command/remove.rs | 30 +- src/email/envelope/flag/command/set.rs | 30 +- src/email/envelope/flag/config.rs | 82 -- src/email/envelope/flag/mod.rs | 46 -- src/email/envelope/mod.rs | 429 ---------- .../message/attachment/command/download.rs | 27 +- src/email/message/attachment/command/mod.rs | 5 +- src/email/message/command/copy.rs | 30 +- src/email/message/command/delete.rs | 31 +- src/email/message/command/forward.rs | 28 +- src/email/message/command/mailto.rs | 28 +- src/email/message/command/mod.rs | 5 +- src/email/message/command/move.rs | 30 +- src/email/message/command/read.rs | 27 +- src/email/message/command/reply.rs | 28 +- src/email/message/command/save.rs | 34 +- src/email/message/command/send.rs | 36 +- src/email/message/command/thread.rs | 27 +- src/email/message/command/write.rs | 28 +- src/email/message/config.rs | 185 ----- src/email/message/mod.rs | 1 - src/email/message/template/command/forward.rs | 26 +- src/email/message/template/command/mod.rs | 5 +- src/email/message/template/command/reply.rs | 26 +- src/email/message/template/command/save.rs | 34 +- src/email/message/template/command/send.rs | 34 +- src/email/message/template/command/write.rs | 10 +- src/folder/command/add.rs | 30 +- src/folder/command/delete.rs | 33 +- src/folder/command/expunge.rs | 29 +- src/folder/command/list.rs | 33 +- src/folder/command/mod.rs | 5 +- src/folder/command/purge.rs | 33 +- src/folder/config.rs | 157 ---- src/folder/mod.rs | 124 --- src/lib.rs | 5 - src/main.rs | 13 +- src/manual/command.rs | 3 +- src/output/args.rs | 48 -- src/output/mod.rs | 4 - src/output/output.rs | 49 -- src/printer.rs | 74 -- src/ui/choice.rs | 84 -- src/ui/editor.rs | 140 ---- src/ui/mod.rs | 28 - 70 files changed, 708 insertions(+), 4055 deletions(-) delete mode 100644 src/account/wizard.rs delete mode 100644 src/backend/config.rs delete mode 100644 src/backend/mod.rs delete mode 100644 src/backend/wizard.rs delete mode 100644 src/cache/arg/disable.rs delete mode 100644 src/cache/arg/mod.rs delete mode 100644 src/cache/mod.rs create mode 100644 src/config.rs delete mode 100644 src/config/mod.rs delete mode 100644 src/config/wizard.rs delete mode 100644 src/email/envelope/config.rs delete mode 100644 src/email/envelope/flag/config.rs delete mode 100644 src/email/message/config.rs delete mode 100644 src/folder/config.rs delete mode 100644 src/output/args.rs delete mode 100644 src/output/mod.rs delete mode 100644 src/output/output.rs delete mode 100644 src/printer.rs delete mode 100644 src/ui/choice.rs delete mode 100644 src/ui/editor.rs delete mode 100644 src/ui/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 740f588a..8b850164 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,7 +443,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", ] @@ -510,9 +510,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.22" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "shlex", ] @@ -563,7 +563,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 +581,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 +591,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 +604,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.29" +version = "4.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8937760c3f4c60871870b8c3ee5f9b30771f792a7045c48bcbba999d7d6b3b8e" +checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" dependencies = [ "clap", ] @@ -631,9 +631,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", @@ -1145,7 +1145,6 @@ dependencies = [ [[package]] name = "email-lib" version = "0.25.0" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" dependencies = [ "async-trait", "chrono", @@ -1422,9 +1421,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 +1436,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 +1446,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,9 +1463,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" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1495,9 +1494,9 @@ dependencies = [ [[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", @@ -1506,21 +1505,21 @@ dependencies = [ [[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", @@ -1692,6 +1691,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" @@ -1793,7 +1798,6 @@ dependencies = [ "serde", "serde_json", "shellexpand-utils", - "sled", "tokio", "toml", "tracing", @@ -1897,9 +1901,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1978,7 +1982,7 @@ dependencies = [ "hyper 1.4.1", "hyper-util", "log", - "rustls 0.23.13", + "rustls 0.23.14", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -2094,7 +2098,7 @@ dependencies = [ [[package]] name = "imap-next" version = "0.2.0" -source = "git+https://github.com/duesee/imap-next#75671ca68e067e82a8846bef0e9396809ca93ffa" +source = "git+https://github.com/duesee/imap-next#7e120f40cb30cef0f761c0efd44a4846234e9e91" dependencies = [ "bytes", "imap-codec", @@ -2126,12 +2130,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]] @@ -2215,9 +2219,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" @@ -2233,9 +2237,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2263,7 +2267,7 @@ dependencies = [ [[package]] name = "keyring-lib" version = "0.4.3" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#17305a90a7ff1e69b9dd6e3e645e720e172d4ad6" dependencies = [ "keyring", "log", @@ -2333,7 +2337,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.6", + "redox_syscall 0.5.7", ] [[package]] @@ -2415,7 +2419,7 @@ checksum = "7a575d25cf00ed68e5790b473b29242a47e991c6187785d47b45e31fc5816554" dependencies = [ "base64 0.22.1", "gethostname", - "rustls 0.23.13", + "rustls 0.23.14", "rustls-pki-types", "smtp-proto", "tokio", @@ -2745,7 +2749,7 @@ dependencies = [ [[package]] name = "oauth-lib" version = "0.1.1" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#17305a90a7ff1e69b9dd6e3e645e720e172d4ad6" dependencies = [ "log", "oauth2", @@ -2786,9 +2790,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 +2933,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.6", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -3020,8 +3024,6 @@ dependencies = [ [[package]] name = "pgp-lib" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6802b1ef0dfc50217185a1eda6ddd546b65ffa8b80f942aa1feda6536adf165" dependencies = [ "async-recursion", "futures", @@ -3041,22 +3043,29 @@ dependencies = [ [[package]] name = "pimalaya-tui" version = "0.1.0" -source = "git+https://github.com/pimalaya/tui#b40ca9d9e161b91e9efcb762fa05f06e3fe46f63" 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", "tracing", @@ -3199,9 +3208,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -3209,7 +3218,7 @@ dependencies = [ [[package]] name = "process-lib" version = "0.4.2" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#17305a90a7ff1e69b9dd6e3e645e720e172d4ad6" dependencies = [ "log", "serde", @@ -3293,9 +3302,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 +3322,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 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 +3354,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,9 +3377,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" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -3539,9 +3548,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "log", "once_cell", @@ -3571,7 +3580,7 @@ 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", @@ -3588,11 +3597,10 @@ 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", ] @@ -3646,9 +3654,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", ] @@ -3686,7 +3694,7 @@ dependencies = [ [[package]] name = "secret-lib" version = "0.4.6" -source = "git+https://github.com/pimalaya/core#119975060c78b7632d1760a823428bf6ca07bb03" +source = "git+https://github.com/pimalaya/core#17305a90a7ff1e69b9dd6e3e645e720e172d4ad6" dependencies = [ "keyring-lib", "log", @@ -4139,9 +4147,9 @@ 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", @@ -4152,12 +4160,12 @@ dependencies = [ [[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", + "windows-sys 0.59.0", ] [[package]] @@ -4250,7 +4258,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.14", "rustls-pki-types", "tokio", ] @@ -4438,9 +4446,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" @@ -4570,9 +4578,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,9 +4589,9 @@ 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", @@ -4596,9 +4604,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -4608,9 +4616,9 @@ dependencies = [ [[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,9 +4626,9 @@ 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", @@ -4631,15 +4639,15 @@ dependencies = [ [[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" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index d24d772e..2b557d8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,17 @@ rustdoc-args = ["--cfg", "docsrs", "--document-private-items"] default = [ "imap", "maildir", - # "notmuch", + #"notmuch", "smtp", "sendmail", - # "keyring", - # "oauth2", + #"keyring", + #"oauth2", "wizard", - # "pgp-commands", - # "pgp-gpg", - # "pgp-native", + #"pgp-commands", + #"pgp-gpg", + #"pgp-native", ] imap = ["email-lib/imap", "pimalaya-tui/imap"] @@ -40,7 +40,7 @@ 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"] +wizard = ["dep:email_address", "dep:secret-lib", "email-lib/autoconfig", "pimalaya-tui/wizard"] pgp = [] pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pgp"] @@ -65,13 +65,12 @@ mml-lib = { version = "=1.0.14", default-features = false, features = ["derive"] oauth-lib = { version = "=0.1.1", optional = true } once_cell = "1.16" petgraph = "0.6" -pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path", "cli", "config", "tracing"] } +pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path", "cli", "himalaya", "tracing", "sled"] } process-lib = { version = "=0.4.2", features = ["derive"] } secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true } 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" @@ -84,11 +83,14 @@ 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" } +#email-lib = { git = "https://github.com/pimalaya/core" } +email-lib = { path = "/home/soywod/code/pimalaya/core/email" } +pgp-lib = { path = "/home/soywod/code/pimalaya/core/pgp" } 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" } +#pimalaya-tui = { git = "https://github.com/pimalaya/tui" } +pimalaya-tui = { path = "/home/soywod/code/pimalaya/tui" } diff --git a/src/account/command/check_up.rs b/src/account/command/check_up.rs index 803bd47d..283391bd 100644 --- a/src/account/command/check_up.rs +++ b/src/account/command/check_up.rs @@ -1,11 +1,22 @@ +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::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,7 +30,7 @@ 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); @@ -27,92 +38,60 @@ impl AccountCheckUpCommand { printer.log("Checking configuration integrity…")?; 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…")?; - 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")] - { + if let Some(mdir_config) = toml_account_config.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")] - { + if let Some(imap_config) = toml_account_config.imap { printer.log("Checking IMAP integrity…")?; - 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))); - - if let Some(imap) = imap.as_ref() { - imap.check_up().await?; - } + 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")] - { + if let Some(notmuch_config) = toml_account_config.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?; - } + let ctx = NotmuchContextBuilder::new(account_config.clone(), Arc::new(notmuch_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; } #[cfg(feature = "smtp")] - { + if let Some(smtp_config) = toml_account_config.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?; - } + let ctx = SmtpContextBuilder::new(account_config.clone(), Arc::new(smtp_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; } #[cfg(feature = "sendmail")] - { + if let Some(sendmail_config) = toml_account_config.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?; - } + let ctx = + SendmailContextBuilder::new(account_config.clone(), Arc::new(sendmail_config)); + BackendBuilder::new(account_config.clone(), ctx) + .check_up() + .await?; } printer.out("Checkup successfully completed!") diff --git a/src/account/command/configure.rs b/src/account/command/configure.rs index bb40e83e..7e15e580 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,19 +32,20 @@ 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 { + if let Some(config) = &toml_account_config.imap { let reset = match &config.auth { ImapAuthConfig::Passwd(config) => config.reset().await, #[cfg(feature = "oauth2")] ImapAuthConfig::OAuth2(config) => config.reset().await, + ImapAuthConfig::MissingOAuth2Feature => unreachable!(), }; if let Err(err) = reset { warn!("error while resetting imap secrets: {err}"); @@ -52,7 +54,7 @@ impl AccountConfigureCommand { } #[cfg(feature = "smtp")] - if let Some(ref config) = account_config.smtp { + if let Some(config) = &toml_account_config.smtp { let reset = match &config.auth { SmtpAuthConfig::Passwd(config) => config.reset().await, #[cfg(feature = "oauth2")] @@ -71,7 +73,7 @@ impl AccountConfigureCommand { } #[cfg(feature = "imap")] - if let Some(ref config) = account_config.imap { + if let Some(config) = &toml_account_config.imap { match &config.auth { ImapAuthConfig::Passwd(config) => { config @@ -84,11 +86,12 @@ impl AccountConfigureCommand { .configure(|| Ok(prompt::secret("IMAP OAuth 2.0 clientsecret")?)) .await } + ImapAuthConfig::MissingOAuth2Feature => unreachable!(), }?; } #[cfg(feature = "smtp")] - if let Some(ref config) = account_config.smtp { + if let Some(config) = &toml_account_config.smtp { match &config.auth { SmtpAuthConfig::Passwd(config) => { config diff --git a/src/account/command/list.rs b/src/account/command/list.rs index a454b342..a387729f 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 dca5739a..444a4563 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 65811304..daf6f389 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 d058a055..7b1d26bd 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 d1b31129..00000000 --- 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 dca3b143..00000000 --- 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 bd22ab72..00000000 --- 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 035742ca..00000000 --- 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 04f32a1a..00000000 --- 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 a13d9f32..00000000 --- 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 5a111e69..00000000 --- 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 35156f78..52e7fec3 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 00000000..becded16 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "wizard")] +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 395ed2c5..00000000 --- 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 ece28a7f..00000000 --- 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 06f80ae9..d61f731a 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,31 @@ 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) + }, ) + .build() .await?; let query = self diff --git a/src/email/envelope/command/mod.rs b/src/email/envelope/command/mod.rs index d0a27de5..d0b170ca 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 08b44952..7259baf9 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,26 @@ 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) + }, ) + .build() .await?; let query = self diff --git a/src/email/envelope/config.rs b/src/email/envelope/config.rs deleted file mode 100644 index 30804abd..00000000 --- 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 bf9eb778..449cb961 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,20 @@ 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) + }, ) + .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 15ba2ed7..b1bbfc9e 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 cf401dbd..2311faa7 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,20 @@ 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) + }, ) + .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 93607d6f..39276b8f 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,20 @@ 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) + }, ) + .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 eb75af4a..00000000 --- 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 75db6d34..1a3a73a9 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 c9bfcc2e..e84d06ee 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 419a1436..382b1cc1 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,18 @@ 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) + }, ) + .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 38e71f6e..fe3577de 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 12c0da32..8a934d27 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,22 @@ 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) + }, ) + .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 9916202b..99b35b22 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,20 @@ 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) + }, ) + .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 15999272..8e05bcce 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,19 @@ 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) }, ) + .build() .await?; let id = self.envelope.id; diff --git a/src/email/message/command/mailto.rs b/src/email/message/command/mailto.rs index 0822d5c0..37ddd87f 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,26 @@ 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) }, ) + .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 9aedfc26..fa8cdff3 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 ad7ff1bb..4c0ceedb 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,22 @@ 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) + }, ) + .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 21896b5c..4da99134 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,18 @@ 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) + }, ) + .build() .await?; let emails = if self.preview { diff --git a/src/email/message/command/reply.rs b/src/email/message/command/reply.rs index 87f64d9b..aba2f84c 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 2e997c72..f85f64e9 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,16 @@ 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) + }, ) + .build() .await?; let is_tty = io::stdin().is_terminal(); @@ -60,6 +68,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 029b1bc0..0b48ff5c 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 469dc2a8..fbac12a1 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,19 @@ 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) }, ) + .build() .await?; let envelopes = backend diff --git a/src/email/message/command/write.rs b/src/email/message/command/write.rs index 4e7b08b8..7113fb1b 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 3ac987c9..00000000 --- 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 b442bc0e..57d3b73c 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 2ce3aa29..5085c3b3 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,18 @@ 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) + }, ) + .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 3deb46ff..e9c98a92 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 a8e08b02..a899686f 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,18 @@ 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) + }, ) + .build() .await?; let tpl = backend diff --git a/src/email/message/template/command/save.rs b/src/email/message/template/command/save.rs index 566fabfb..9e3967c9 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,16 @@ impl TemplateSaveCommand { .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.clone(), - 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) + }, ) + .build() .await?; let is_tty = io::stdin().is_terminal(); @@ -72,6 +80,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 eba4521d..821323eb 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,24 @@ 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 backend = Backend::new( - toml_account_config.clone(), - account_config.clone(), - 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 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 9737352a..e65aab59 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 05772141..fcfbbf34 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,29 @@ 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) + }, ) + .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 528b4fac..ef8c7787 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,20 @@ 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) + }, ) + .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 bc4400b9..c680fc1a 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,20 @@ 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) + }, ) + .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 038a4074..61e94cde 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,25 @@ 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) + }, ) + .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 61a57080..1eb92af0 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 3cfb9e27..0df11e2c 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,20 @@ 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) + }, ) + .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 6ce8bf3a..00000000 --- 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 b00d79fd..1a3a73a9 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 45ccab8d..0f796718 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 bc121067..db542311 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 ba692223..bf334b8e 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 812052b8..00000000 --- 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 a1ef5bc8..00000000 --- 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 796cca7f..00000000 --- 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 4cc45727..00000000 --- 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 9b06db50..00000000 --- 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 f9b28b83..00000000 --- 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