mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-21 18:40:19 +00:00
wip: use shared stuff from pimalaya-tui
This commit is contained in:
parent
2386d0f517
commit
a0dea19cdf
70 changed files with 708 additions and 4055 deletions
206
Cargo.lock
generated
206
Cargo.lock
generated
|
@ -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",
|
||||
|
|
24
Cargo.toml
24
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" }
|
||||
|
|
|
@ -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!")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<bool>,
|
||||
pub email: String,
|
||||
pub display_name: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
pub signature_delim: Option<String>,
|
||||
pub downloads_dir: Option<PathBuf>,
|
||||
pub backend: Option<BackendKind>,
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
pub pgp: Option<PgpConfig>,
|
||||
|
||||
pub folder: Option<FolderConfig>,
|
||||
pub envelope: Option<EnvelopeConfig>,
|
||||
pub flag: Option<FlagConfig>,
|
||||
pub message: Option<MessageConfig>,
|
||||
pub template: Option<TemplateConfig>,
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
pub imap: Option<ImapConfig>,
|
||||
#[cfg(feature = "maildir")]
|
||||
pub maildir: Option<MaildirConfig>,
|
||||
#[cfg(feature = "notmuch")]
|
||||
pub notmuch: Option<NotmuchConfig>,
|
||||
#[cfg(feature = "smtp")]
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
#[cfg(feature = "sendmail")]
|
||||
pub sendmail: Option<SendmailConfig>,
|
||||
}
|
||||
|
||||
impl TomlAccountConfig {
|
||||
pub fn folder_list_table_preset(&self) -> Option<String> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<String> {
|
||||
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<char> {
|
||||
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<char> {
|
||||
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<char> {
|
||||
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<char> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<String>,
|
||||
pub name_color: Option<Color>,
|
||||
pub backends_color: Option<Color>,
|
||||
pub default_color: Option<Color>,
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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<Account>);
|
||||
|
||||
impl Deref for Accounts {
|
||||
type Target = Vec<Account>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Iter<'_, String, TomlAccountConfig>> 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<u16>,
|
||||
config: ListAccountsTableConfig,
|
||||
}
|
||||
|
||||
impl AccountsTable {
|
||||
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_preset(mut self, preset: Option<String>) -> Self {
|
||||
self.config.preset = preset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_name_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.name_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_backends_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.backends_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_default_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.default_color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Accounts> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.accounts.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -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<TomlAccountConfig>,
|
||||
pub account_config: Arc<AccountConfig>,
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
pub imap: Option<ImapContextBuilder>,
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
pub maildir: Option<MaildirContextBuilder>,
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
pub notmuch: Option<NotmuchContextBuilder>,
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
pub smtp: Option<SmtpContextBuilder>,
|
||||
|
||||
#[cfg(feature = "sendmail")]
|
||||
pub sendmail: Option<SendmailContextBuilder>,
|
||||
}
|
||||
|
||||
impl BackendContextBuilder {
|
||||
pub async fn new(
|
||||
toml_account_config: Arc<TomlAccountConfig>,
|
||||
account_config: Arc<AccountConfig>,
|
||||
kinds: Vec<&BackendKind>,
|
||||
) -> Result<Self> {
|
||||
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<BackendFeature<Self::Context, dyn AddFolder>> {
|
||||
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<BackendFeature<Self::Context, dyn ListFolders>> {
|
||||
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<BackendFeature<Self::Context, dyn ExpungeFolder>> {
|
||||
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<BackendFeature<Self::Context, dyn PurgeFolder>> {
|
||||
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<BackendFeature<Self::Context, dyn DeleteFolder>> {
|
||||
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<BackendFeature<Self::Context, dyn GetEnvelope>> {
|
||||
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<BackendFeature<Self::Context, dyn ListEnvelopes>> {
|
||||
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<BackendFeature<Self::Context, dyn ThreadEnvelopes>> {
|
||||
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<BackendFeature<Self::Context, dyn AddFlags>> {
|
||||
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<BackendFeature<Self::Context, dyn SetFlags>> {
|
||||
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<BackendFeature<Self::Context, dyn RemoveFlags>> {
|
||||
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<BackendFeature<Self::Context, dyn AddMessage>> {
|
||||
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<BackendFeature<Self::Context, dyn SendMessage>> {
|
||||
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<BackendFeature<Self::Context, dyn PeekMessages>> {
|
||||
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<BackendFeature<Self::Context, dyn GetMessages>> {
|
||||
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<BackendFeature<Self::Context, dyn CopyMessages>> {
|
||||
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<BackendFeature<Self::Context, dyn MoveMessages>> {
|
||||
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<BackendFeature<Self::Context, dyn DeleteMessages>> {
|
||||
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<Self::Context> {
|
||||
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<ImapContext>,
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
pub maildir: Option<MaildirContextSync>,
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
pub notmuch: Option<NotmuchContextSync>,
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
pub smtp: Option<SmtpContextSync>,
|
||||
|
||||
#[cfg(feature = "sendmail")]
|
||||
pub sendmail: Option<SendmailContextSync>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
impl AsRef<Option<ImapContext>> for BackendContext {
|
||||
fn as_ref(&self) -> &Option<ImapContext> {
|
||||
&self.imap
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
impl AsRef<Option<MaildirContextSync>> for BackendContext {
|
||||
fn as_ref(&self) -> &Option<MaildirContextSync> {
|
||||
&self.maildir
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
impl AsRef<Option<NotmuchContextSync>> for BackendContext {
|
||||
fn as_ref(&self) -> &Option<NotmuchContextSync> {
|
||||
&self.notmuch
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
impl AsRef<Option<SmtpContextSync>> for BackendContext {
|
||||
fn as_ref(&self) -> &Option<SmtpContextSync> {
|
||||
&self.smtp
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sendmail")]
|
||||
impl AsRef<Option<SendmailContextSync>> for BackendContext {
|
||||
fn as_ref(&self) -> &Option<SendmailContextSync> {
|
||||
&self.sendmail
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Backend {
|
||||
pub toml_account_config: Arc<TomlAccountConfig>,
|
||||
pub backend: email::backend::Backend<BackendContext>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub async fn new(
|
||||
toml_account_config: Arc<TomlAccountConfig>,
|
||||
account_config: Arc<AccountConfig>,
|
||||
backend_kinds: impl IntoIterator<Item = &BackendKind>,
|
||||
with_features: impl Fn(&mut email::backend::BackendBuilder<BackendContextBuilder>),
|
||||
) -> Result<Self> {
|
||||
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<IdMapper> {
|
||||
#[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<Envelopes> {
|
||||
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<ThreadedEnvelopes> {
|
||||
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<ThreadedEnvelopes> {
|
||||
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<SingleId> {
|
||||
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<SingleId> {
|
||||
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<Messages> {
|
||||
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<Messages> {
|
||||
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<BackendContext>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.backend
|
||||
}
|
||||
}
|
|
@ -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<BackendConfig> {
|
||||
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<BackendConfig> {
|
||||
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!(),
|
||||
}
|
||||
}
|
15
src/cache/arg/disable.rs
vendored
15
src/cache/arg/disable.rs
vendored
|
@ -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,
|
||||
}
|
1
src/cache/arg/mod.rs
vendored
1
src/cache/arg/mod.rs
vendored
|
@ -1 +0,0 @@
|
|||
pub mod disable;
|
144
src/cache/mod.rs
vendored
144
src/cache/mod.rs
vendored
|
@ -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<Self> {
|
||||
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<I>(&self, id: I) -> Result<String>
|
||||
where
|
||||
I: AsRef<str>,
|
||||
{
|
||||
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<I>(&self, id: I) -> Result<String>
|
||||
where
|
||||
I: AsRef<str>,
|
||||
{
|
||||
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<A>(&self, alias: A) -> Result<String>
|
||||
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<Item = impl ToString>) -> Result<Vec<String>> {
|
||||
let aliases: Vec<String> = 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<String> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
src/cli.rs
27
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<PathBuf>,
|
||||
|
||||
/// 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,
|
||||
|
|
4
src/config.rs
Normal file
4
src/config.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
#[cfg(feature = "wizard")]
|
||||
use pimalaya_tui::himalaya::config::HimalayaTomlConfig;
|
||||
|
||||
pub type TomlConfig = HimalayaTomlConfig;
|
|
@ -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<String>,
|
||||
pub signature: Option<String>,
|
||||
pub signature_delim: Option<String>,
|
||||
pub downloads_dir: Option<PathBuf>,
|
||||
pub accounts: HashMap<String, TomlAccountConfig>,
|
||||
pub account: Option<AccountsConfig>,
|
||||
}
|
||||
|
||||
impl TomlConfig<AccountConfig> 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> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<TomlAccountConfig>, Arc<AccountConfig>)> {
|
||||
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<String> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<Color> {
|
||||
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<PathBuf, String> {
|
||||
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<ListAccountsConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ListAccountsConfig {
|
||||
pub table: Option<ListAccountsTableConfig>,
|
||||
}
|
|
@ -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<Config> {
|
||||
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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ListEnvelopesConfig>,
|
||||
pub thread: Option<ThreadEnvelopesConfig>,
|
||||
pub get: Option<GetEnvelopeConfig>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
pub table: Option<ListEnvelopesTableConfig>,
|
||||
|
||||
#[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<String>,
|
||||
|
||||
pub unseen_char: Option<char>,
|
||||
pub replied_char: Option<char>,
|
||||
pub flagged_char: Option<char>,
|
||||
pub attachment_char: Option<char>,
|
||||
|
||||
pub id_color: Option<Color>,
|
||||
pub flags_color: Option<Color>,
|
||||
pub subject_color: Option<Color>,
|
||||
pub sender_color: Option<Color>,
|
||||
pub date_color: Option<Color>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
|
||||
#[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<BackendKind>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FlagAddConfig>,
|
||||
pub set: Option<FlagSetConfig>,
|
||||
pub remove: Option<FlagRemoveConfig>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<Flag>);
|
||||
|
||||
impl ops::Deref for Flags {
|
||||
type Target = HashSet<Flag>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<email::flag::Flags> for Flags {
|
||||
fn from(flags: email::flag::Flags) -> Self {
|
||||
Flags(flags.iter().map(Flag::from).collect())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
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<Envelope>);
|
||||
|
||||
impl Envelopes {
|
||||
pub fn try_from_backend(
|
||||
config: &AccountConfig,
|
||||
id_mapper: &IdMapper,
|
||||
envelopes: email::envelope::Envelopes,
|
||||
) -> Result<Envelopes> {
|
||||
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::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(Envelopes(envelopes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Envelopes {
|
||||
type Target = Vec<Envelope>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnvelopesTable {
|
||||
envelopes: Envelopes,
|
||||
width: Option<u16>,
|
||||
config: ListEnvelopesTableConfig,
|
||||
}
|
||||
|
||||
impl EnvelopesTable {
|
||||
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_preset(mut self, preset: Option<String>) -> Self {
|
||||
self.config.preset = preset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_unseen_char(mut self, char: Option<char>) -> Self {
|
||||
self.config.unseen_char = char;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_replied_char(mut self, char: Option<char>) -> Self {
|
||||
self.config.replied_char = char;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_flagged_char(mut self, char: Option<char>) -> Self {
|
||||
self.config.flagged_char = char;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_attachment_char(mut self, char: Option<char>) -> Self {
|
||||
self.config.attachment_char = char;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_id_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.id_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_flags_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.flags_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_subject_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.subject_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_sender_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.sender_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_date_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.date_color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Envelopes> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<ThreadedEnvelopes> {
|
||||
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::<Result<Vec<_>>>()?;
|
||||
|
||||
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::<Result<HashMap<_, _>>>()?;
|
||||
|
||||
let envelopes = email::envelope::ThreadedEnvelopes::build(envelopes, move |envelopes| {
|
||||
let mut graph = DiGraphMap::<ThreadedEnvelope, u8>::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<AccountConfig>,
|
||||
envelopes: ThreadedEnvelopes,
|
||||
}
|
||||
|
||||
impl EnvelopesTree {
|
||||
pub fn new(config: Arc<AccountConfig>, envelopes: ThreadedEnvelopes) -> Self {
|
||||
Self { config, envelopes }
|
||||
}
|
||||
|
||||
pub fn fmt(
|
||||
f: &mut fmt::Formatter,
|
||||
config: &AccountConfig,
|
||||
graph: &DiGraphMap<ThreadedEnvelope<'_>, 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::<Vec<_>>();
|
||||
|
||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.envelopes.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for EnvelopesTree {
|
||||
type Target = ThreadedEnvelopes;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.envelopes
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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<MessageAddConfig>,
|
||||
pub send: Option<MessageSendConfig>,
|
||||
pub peek: Option<MessagePeekConfig>,
|
||||
pub read: Option<MessageGetConfig>,
|
||||
pub copy: Option<MessageCopyConfig>,
|
||||
pub r#move: Option<MessageMoveConfig>,
|
||||
pub delete: Option<DeleteMessageConfig>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
|
||||
#[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<BackendKind>,
|
||||
|
||||
#[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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
|
||||
#[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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
pub style: Option<DeleteMessageStyle>,
|
||||
}
|
||||
|
||||
impl From<DeleteMessageConfig> 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
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
pub mod arg;
|
||||
pub mod attachment;
|
||||
pub mod command;
|
||||
pub mod config;
|
||||
pub mod template;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HashMap<String, String>>,
|
||||
pub add: Option<FolderAddConfig>,
|
||||
pub list: Option<FolderListConfig>,
|
||||
pub expunge: Option<FolderExpungeConfig>,
|
||||
pub purge: Option<FolderPurgeConfig>,
|
||||
pub delete: Option<FolderDeleteConfig>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
pub table: Option<ListFoldersTableConfig>,
|
||||
|
||||
#[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<String>,
|
||||
pub name_color: Option<Color>,
|
||||
pub desc_color: Option<Color>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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<BackendKind>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<email::folder::Folder> 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<Folder>);
|
||||
|
||||
impl Deref for Folders {
|
||||
type Target = Vec<Folder>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<email::folder::Folders> for Folders {
|
||||
fn from(folders: email::folder::Folders) -> Self {
|
||||
Folders(folders.into_iter().map(Folder::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FoldersTable {
|
||||
folders: Folders,
|
||||
width: Option<u16>,
|
||||
config: ListFoldersTableConfig,
|
||||
}
|
||||
|
||||
impl FoldersTable {
|
||||
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_preset(mut self, preset: Option<String>) -> Self {
|
||||
self.config.preset = preset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_name_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.name_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_some_desc_color(mut self, color: Option<Color>) -> Self {
|
||||
self.config.desc_color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Folders> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.folders.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
13
src/main.rs
13
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
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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<Item = Arg> {
|
||||
[
|
||||
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"),
|
||||
]
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
#[allow(clippy::module_inception)]
|
||||
pub mod output;
|
||||
|
||||
pub use output::*;
|
|
@ -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<Self, Self::Err> {
|
||||
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<T: Serialize> {
|
||||
response: T,
|
||||
}
|
||||
|
||||
impl<T: Serialize> OutputJson<T> {
|
||||
pub fn new(response: T) -> Self {
|
||||
Self { response }
|
||||
}
|
||||
}
|
|
@ -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<u16>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait Printer {
|
||||
fn out<T: fmt::Display + serde::Serialize>(&mut self, data: T) -> Result<()>;
|
||||
|
||||
fn log<T: fmt::Display + serde::Serialize>(&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<T: fmt::Display + serde::Serialize>(&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<T: fmt::Display + serde::Serialize>(&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
|
||||
}
|
||||
}
|
|
@ -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<PreEditChoice> {
|
||||
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<PostEditChoice> {
|
||||
let user_choice = prompt::item(
|
||||
"What would you like to do with this message?",
|
||||
&POST_EDIT_CHOICES,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(user_choice.clone())
|
||||
}
|
140
src/ui/editor.rs
140
src/ui/editor.rs
|
@ -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<Template> {
|
||||
let path = local_draft_path();
|
||||
|
||||
debug!("create draft");
|
||||
fs::write(&path, tpl.as_bytes()).context(format!("cannot write local draft at {:?}", path))?;
|
||||
|
||||
debug!("open editor");
|
||||
let editor = env::var("EDITOR").context("cannot get editor from env var")?;
|
||||
SingleCommand::from(format!("{editor} {}", &path.to_string_lossy()))
|
||||
.with_output_piped(false)
|
||||
.run()
|
||||
.await
|
||||
.context("cannot launch editor")?;
|
||||
|
||||
debug!("read draft");
|
||||
let content =
|
||||
fs::read_to_string(&path).context(format!("cannot read local draft at {:?}", path))?;
|
||||
|
||||
Ok(content.into())
|
||||
}
|
||||
|
||||
pub async fn open_with_local_draft() -> Result<Template> {
|
||||
let path = local_draft_path();
|
||||
let content =
|
||||
fs::read_to_string(&path).context(format!("cannot read local draft at {:?}", path))?;
|
||||
open_with_tpl(content.into()).await
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn edit_tpl_with_editor<P: Printer>(
|
||||
config: Arc<AccountConfig>,
|
||||
printer: &mut P,
|
||||
backend: &Backend,
|
||||
mut tpl: Template,
|
||||
) -> Result<()> {
|
||||
let draft = local_draft_path();
|
||||
if draft.exists() {
|
||||
loop {
|
||||
match choice::pre_edit() {
|
||||
Ok(choice) => match choice {
|
||||
PreEditChoice::Edit => {
|
||||
tpl = open_with_local_draft().await?;
|
||||
break;
|
||||
}
|
||||
PreEditChoice::Discard => {
|
||||
tpl = open_with_tpl(tpl).await?;
|
||||
break;
|
||||
}
|
||||
PreEditChoice::Quit => return Ok(()),
|
||||
},
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tpl = open_with_tpl(tpl).await?;
|
||||
}
|
||||
|
||||
loop {
|
||||
match choice::post_edit() {
|
||||
Ok(PostEditChoice::Send) => {
|
||||
printer.log("Sending email…")?;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut compiler = MmlCompilerBuilder::new();
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
compiler.set_some_pgp(config.pgp.clone());
|
||||
|
||||
let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?;
|
||||
|
||||
backend.send_message_then_save_copy(&email).await?;
|
||||
|
||||
remove_local_draft()?;
|
||||
printer.log("Done!")?;
|
||||
break;
|
||||
}
|
||||
Ok(PostEditChoice::Edit) => {
|
||||
tpl = open_with_tpl(tpl).await?;
|
||||
continue;
|
||||
}
|
||||
Ok(PostEditChoice::LocalDraft) => {
|
||||
printer.log("Email successfully saved locally")?;
|
||||
break;
|
||||
}
|
||||
Ok(PostEditChoice::RemoteDraft) => {
|
||||
#[allow(unused_mut)]
|
||||
let mut compiler = MmlCompilerBuilder::new();
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
compiler.set_some_pgp(config.pgp.clone());
|
||||
|
||||
let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?;
|
||||
|
||||
backend
|
||||
.add_message_with_flags(
|
||||
DRAFTS,
|
||||
&email,
|
||||
&Flags::from_iter([Flag::Seen, Flag::Draft]),
|
||||
)
|
||||
.await?;
|
||||
remove_local_draft()?;
|
||||
printer.log("Email successfully saved to drafts")?;
|
||||
break;
|
||||
}
|
||||
Ok(PostEditChoice::Discard) => {
|
||||
remove_local_draft()?;
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use crossterm::style::Color;
|
||||
|
||||
pub mod choice;
|
||||
pub mod editor;
|
||||
|
||||
pub(crate) fn map_color(color: Color) -> comfy_table::Color {
|
||||
match color {
|
||||
Color::Reset => comfy_table::Color::Reset,
|
||||
Color::Black => comfy_table::Color::Black,
|
||||
Color::DarkGrey => comfy_table::Color::DarkGrey,
|
||||
Color::Red => comfy_table::Color::Red,
|
||||
Color::DarkRed => comfy_table::Color::DarkRed,
|
||||
Color::Green => comfy_table::Color::Green,
|
||||
Color::DarkGreen => comfy_table::Color::DarkGreen,
|
||||
Color::Yellow => comfy_table::Color::Yellow,
|
||||
Color::DarkYellow => comfy_table::Color::DarkYellow,
|
||||
Color::Blue => comfy_table::Color::Blue,
|
||||
Color::DarkBlue => comfy_table::Color::DarkBlue,
|
||||
Color::Magenta => comfy_table::Color::Magenta,
|
||||
Color::DarkMagenta => comfy_table::Color::DarkMagenta,
|
||||
Color::Cyan => comfy_table::Color::Cyan,
|
||||
Color::DarkCyan => comfy_table::Color::DarkCyan,
|
||||
Color::White => comfy_table::Color::White,
|
||||
Color::Grey => comfy_table::Color::Grey,
|
||||
Color::Rgb { r, g, b } => comfy_table::Color::Rgb { r, g, b },
|
||||
Color::AnsiValue(n) => comfy_table::Color::AnsiValue(n),
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue