mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 04:20:22 +00:00
Merge branch 'pimalaya-tui-refactor'
This commit is contained in:
commit
6ff3771135
73 changed files with 1403 additions and 4930 deletions
66
CHANGELOG.md
66
CHANGELOG.md
|
@ -30,9 +30,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
|
||||
- Refactored IMAP and SMTP auth config API
|
||||
- Improved error messages when missing cargo features. For example, if a TOML configuration uses the IMAP backend without the `imap` cargo features, the error `missing "imap" feature` is displayed. [#20](https://github.com/pimalaya/core/issues/20)
|
||||
- Normalized enum-based configurations, using the [internally tagged representation](https://serde.rs/enum-representations.html#internally-tagged) `type =`. It should reduce issues due to misconfiguration, and improve othe error messages. Yet it is not perfect, see [#802](https://github.com/toml-rs/toml/issues/802):
|
||||
|
||||
The IMAP and SMTP auth config option is now explicit, in order to improve error messages:
|
||||
- `imap.*`, `maildir.*` and `notmuch.*` moved to `backend.*`:
|
||||
|
||||
```toml
|
||||
# before
|
||||
imap.host = "localhost"
|
||||
imap.port = 143
|
||||
|
||||
# after
|
||||
backend.type = "imap"
|
||||
backend.host = "localhost"
|
||||
backend.port = 143
|
||||
```
|
||||
|
||||
- `smtp.*` and `sendmail.*` moved to `message.send.backend.*`:
|
||||
|
||||
```toml
|
||||
# before
|
||||
smtp.host = "localhost"
|
||||
smtp.port = 25
|
||||
|
||||
# after
|
||||
message.send.backend.type = "smtp"
|
||||
message.send.backend.host = "localhost"
|
||||
message.send.backend.port = 25
|
||||
```
|
||||
|
||||
- `pgp.backend` renamed `pgp.type`:
|
||||
|
||||
```toml
|
||||
# before
|
||||
pgp.backend = "commands"
|
||||
pgp.encrypt-cmd = "gpg --encrypt --quiet --armor <recipients>"
|
||||
|
||||
# after
|
||||
pgp.type = "commands"
|
||||
pgp.encrypt-cmd = "gpg --encrypt --quiet --armor <recipients>"
|
||||
```
|
||||
|
||||
- `{imap,smtp}.auth` moved as well:
|
||||
|
||||
```toml
|
||||
# before
|
||||
|
@ -40,10 +79,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
smtp.oauth2.method = "xoauth2"
|
||||
|
||||
# after
|
||||
imap.auth.type = "password"
|
||||
imap.auth.cmd = "pass show example"
|
||||
smtp.auth.type = "oauth2"
|
||||
smtp.auth.method = "xoauth2"
|
||||
backend.auth.type = "password"
|
||||
backend.auth.cmd = "pass show example"
|
||||
message.send.backend.auth.type = "oauth2"
|
||||
message.send.backend.auth.method = "xoauth2"
|
||||
```
|
||||
|
||||
## [1.0.0-beta.4] - 2024-04-16
|
||||
|
@ -849,18 +888,3 @@ Few major concepts changed:
|
|||
[0.2.1]: https://github.com/soywod/himalaya/compare/v0.2.0...v0.2.1
|
||||
[0.2.0]: https://github.com/soywod/himalaya/compare/v0.1.0...v0.2.0
|
||||
[0.1.0]: https://github.com/soywod/himalaya/releases/tag/v0.1.0
|
||||
|
||||
[#39]: https://todo.sr.ht/~soywod/pimalaya/39
|
||||
[#41]: https://todo.sr.ht/~soywod/pimalaya/41
|
||||
[#43]: https://todo.sr.ht/~soywod/pimalaya/43
|
||||
[#54]: https://todo.sr.ht/~soywod/pimalaya/54
|
||||
[#58]: https://todo.sr.ht/~soywod/pimalaya/58
|
||||
[#59]: https://todo.sr.ht/~soywod/pimalaya/59
|
||||
[#60]: https://todo.sr.ht/~soywod/pimalaya/60
|
||||
[#95]: https://todo.sr.ht/~soywod/pimalaya/95
|
||||
[#172]: https://todo.sr.ht/~soywod/pimalaya/172
|
||||
[#173]: https://todo.sr.ht/~soywod/pimalaya/173
|
||||
[#184]: https://todo.sr.ht/~soywod/pimalaya/184
|
||||
[#188]: https://todo.sr.ht/~soywod/pimalaya/188
|
||||
[#194]: https://todo.sr.ht/~soywod/pimalaya/194
|
||||
[#195]: https://todo.sr.ht/~soywod/pimalaya/195
|
||||
|
|
1268
Cargo.lock
generated
1268
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
65
Cargo.toml
65
Cargo.toml
|
@ -12,24 +12,22 @@ documentation = "https://github.com/pimalaya/himalaya/"
|
|||
repository = "https://github.com/pimalaya/himalaya/"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs", "--document-private-items"]
|
||||
features = ["imap", "maildir", "smtp", "sendmail", "oauth2", "wizard", "pgp-commands", "pgp-native"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"imap",
|
||||
"maildir",
|
||||
# "notmuch",
|
||||
#"notmuch",
|
||||
"smtp",
|
||||
"sendmail",
|
||||
|
||||
# "keyring",
|
||||
# "oauth2",
|
||||
#"sendmail",
|
||||
#"keyring",
|
||||
#"oauth2",
|
||||
"wizard",
|
||||
|
||||
# "pgp-commands",
|
||||
# "pgp-gpg",
|
||||
# "pgp-native",
|
||||
#"pgp-commands",
|
||||
#"pgp-gpg",
|
||||
#"pgp-native",
|
||||
]
|
||||
|
||||
imap = ["email-lib/imap", "pimalaya-tui/imap"]
|
||||
|
@ -38,57 +36,32 @@ notmuch = ["email-lib/notmuch", "pimalaya-tui/notmuch"]
|
|||
smtp = ["email-lib/smtp", "pimalaya-tui/smtp"]
|
||||
sendmail = ["email-lib/sendmail", "pimalaya-tui/sendmail"]
|
||||
|
||||
keyring = ["email-lib/keyring", "pimalaya-tui/keyring", "secret-lib?/keyring-tokio"]
|
||||
oauth2 = ["dep:oauth-lib", "email-lib/oauth2", "pimalaya-tui/oauth2", "keyring"]
|
||||
wizard = ["dep:email_address", "dep:secret-lib", "email-lib/autoconfig"]
|
||||
keyring = ["email-lib/keyring", "pimalaya-tui/keyring", "secret-lib/keyring"]
|
||||
oauth2 = ["email-lib/oauth2", "pimalaya-tui/oauth2", "keyring"]
|
||||
wizard = ["email-lib/autoconfig", "pimalaya-tui/wizard"]
|
||||
|
||||
pgp = []
|
||||
pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pgp"]
|
||||
pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pgp"]
|
||||
pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pgp"]
|
||||
pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pimalaya-tui/pgp-commands", "pgp"]
|
||||
pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pimalaya-tui/pgp-gpg", "pgp"]
|
||||
pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pimalaya-tui/pgp-native", "pgp"]
|
||||
|
||||
[dependencies]
|
||||
ariadne = "0.2"
|
||||
async-trait = "0.1"
|
||||
clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete = "4.4"
|
||||
clap_mangen = "0.2"
|
||||
color-eyre = "0.6"
|
||||
comfy-table = "7.1"
|
||||
crossterm = { version = "0.27", features = ["serde"] }
|
||||
dirs = "4"
|
||||
email-lib = { version = "=0.25.0", default-features = false, features = ["derive", "thread", "tracing"] }
|
||||
email_address = { version = "0.2", optional = true }
|
||||
email-lib = { version = "=0.26", default-features = false, features = ["tokio-rustls", "derive", "thread"] }
|
||||
mail-builder = "0.3"
|
||||
md5 = "0.7"
|
||||
mml-lib = { version = "=1.0.14", default-features = false, features = ["derive"] }
|
||||
oauth-lib = { version = "=0.1.1", optional = true }
|
||||
mml-lib = { version = "1", default-features = false, features = ["compiler", "interpreter", "derive"] }
|
||||
once_cell = "1.16"
|
||||
petgraph = "0.6"
|
||||
pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path", "cli", "config", "tracing"] }
|
||||
process-lib = { version = "=0.4.2", features = ["derive"] }
|
||||
secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true }
|
||||
pimalaya-tui = { version = "=0.1", default-features = false, features = ["email", "path", "cli", "himalaya", "tracing", "sled"] }
|
||||
secret-lib = { version = "1", default-features = false, features = ["tokio", "rustls", "command", "derive"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
shellexpand-utils = "=0.2.1"
|
||||
sled = "=0.34.7"
|
||||
tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
toml = "0.8"
|
||||
tracing = "0.1"
|
||||
url = "2.2"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
|
||||
[patch.crates-io]
|
||||
# IMAP
|
||||
imap-next = { git = "https://github.com/duesee/imap-next" }
|
||||
imap-client = { git = "https://github.com/pimalaya/imap-client" }
|
||||
|
||||
# Pimalaya core
|
||||
email-lib = { git = "https://github.com/pimalaya/core" }
|
||||
keyring-lib = { git = "https://github.com/pimalaya/core" }
|
||||
oauth-lib = { git = "https://github.com/pimalaya/core" }
|
||||
process-lib = { git = "https://github.com/pimalaya/core" }
|
||||
secret-lib = { git = "https://github.com/pimalaya/core" }
|
||||
|
||||
# Pimalaya TUI
|
||||
pimalaya-tui = { git = "https://github.com/pimalaya/tui" }
|
||||
|
|
|
@ -621,12 +621,13 @@ You can also manually edit your own configuration, from scratch:
|
|||
|
||||
## Sponsoring
|
||||
|
||||
[![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/project/Himalaya/index.html)
|
||||
[![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/)
|
||||
|
||||
Special thanks to the [NLnet foundation](https://nlnet.nl/project/Himalaya/index.html) and the [European Commission](https://www.ngi.eu/) that helped the project to receive financial support from:
|
||||
Special thanks to the [NLnet foundation](https://nlnet.nl/) and the [European Commission](https://www.ngi.eu/) that helped the project to receive financial support from various programs:
|
||||
|
||||
- [NGI Assure](https://nlnet.nl/assure/) in 2022
|
||||
- [NGI Zero Entrust](https://nlnet.nl/entrust/) in 2023
|
||||
- [NGI Assure](https://nlnet.nl/project/Himalaya/) in 2022
|
||||
- [NGI Zero Entrust](https://nlnet.nl/project/Pimalaya/) in 2023
|
||||
- [NGI Zero Core](https://nlnet.nl/project/Pimalaya-PIM/) in 2024 *(still ongoing)*
|
||||
|
||||
If you appreciate the project, feel free to donate using one of the following providers:
|
||||
|
||||
|
|
|
@ -288,13 +288,80 @@ template.forward.signature-style = "inlined"
|
|||
template.forward.quote-headline = "-------- Forwarded Message --------\n"
|
||||
|
||||
########################################
|
||||
#### PGP configuration #################
|
||||
#### GPG-based PGP configuration #######
|
||||
########################################
|
||||
|
||||
# TODO
|
||||
#pgp.backend = "commands"
|
||||
#pgp.backend = "gpg"
|
||||
#pgp.backend = "native"
|
||||
# Enables PGP using GPG bindings. It requires the GPG lib to be
|
||||
# installed on the system, and the `pgp-gpg` cargo feature on.
|
||||
#
|
||||
#pgp.type = "gpg"
|
||||
|
||||
########################################
|
||||
#### Command-based PGP configuration ###
|
||||
########################################
|
||||
|
||||
# Enables PGP using shell commands. A PGP client needs to be installed
|
||||
# on the system, like gpg. It also requires the `pgp-commands` cargo
|
||||
# feature.
|
||||
#
|
||||
#pgp.type = "commands"
|
||||
|
||||
# Defines the encrypt command. The special placeholder `<recipients>`
|
||||
# represents the list of recipients, formatted by
|
||||
# `pgp.encrypt-recipient-fmt`.
|
||||
#
|
||||
#pgp.encrypt-cmd = "gpg --encrypt --quiet --armor <recipients>"
|
||||
|
||||
# Formats recipients for `pgp.encrypt-cmd`. The special placeholder
|
||||
# `<recipient>` is replaced by an actual recipient at runtime.
|
||||
#
|
||||
#pgp.encrypt-recipient-fmt = "--recipient <recipient>"
|
||||
|
||||
# Defines the separator used between formatted recipients
|
||||
# `pgp.encrypt-recipient-fmt`.
|
||||
#
|
||||
#pgp.encrypt-recipients-sep = " "
|
||||
|
||||
# Defines the decrypt command.
|
||||
#
|
||||
#pgp.decrypt-cmd = "gpg --decrypt --quiet"
|
||||
|
||||
# Defines the sign command.
|
||||
#
|
||||
#pgp.sign-cmd = "gpg --sign --quiet --armor"
|
||||
|
||||
# Defines the verify command.
|
||||
#
|
||||
#pgp.verify-cmd = "gpg --verify --quiet"
|
||||
|
||||
########################################
|
||||
#### Native PGP configuration ##########
|
||||
########################################
|
||||
|
||||
# Enables the native Rust implementation of PGP. It requires the
|
||||
# `pgp-native` cargo feature.
|
||||
#
|
||||
#pgp.type = "native"
|
||||
|
||||
# Defines where to find the PGP secret key.
|
||||
#
|
||||
#pgp.secret-key.path = "/path/to/secret.key"
|
||||
#pgp.secret-key.keyring = "my-pgp-secret-key"
|
||||
|
||||
# Defines how to retrieve the PGP secret key passphrase.
|
||||
#
|
||||
#pgp.secret-key-passphrase.raw = "p@assw0rd"
|
||||
#pgp.secret-key-passphrase.keyring = "my-pgp-passphrase"
|
||||
#pgp.secret-key-passphrase.cmd = "pass show pgp-passphrase"
|
||||
|
||||
# Enables the Web Key Discovery protocol to discover recipients'
|
||||
# public key based on their email address.
|
||||
#
|
||||
#pgp.wkd = true
|
||||
|
||||
# Enables public key servers discovery.
|
||||
#
|
||||
#pgp.key-servers = ["hkps://keys.openpgp.org", "hkps://keys.mailvelope.com"]
|
||||
|
||||
########################################
|
||||
#### IMAP configuration ################
|
||||
|
@ -302,54 +369,54 @@ template.forward.quote-headline = "-------- Forwarded Message --------\n"
|
|||
|
||||
# Defines the IMAP backend as the default one for all features.
|
||||
#
|
||||
backend = "imap"
|
||||
backend.type = "imap"
|
||||
|
||||
# IMAP server host name.
|
||||
#
|
||||
imap.host = "localhost"
|
||||
backend.host = "localhost"
|
||||
|
||||
# IMAP server port.
|
||||
#
|
||||
#imap.port = 143
|
||||
imap.port = 993
|
||||
#backend.port = 143
|
||||
backend.port = 993
|
||||
|
||||
# IMAP server encryption.
|
||||
#
|
||||
#imap.encryption = "none" # or false
|
||||
#imap.encryption = "start-tls"
|
||||
imap.encryption = "tls" # or true
|
||||
#backend.encryption = "none" # or false
|
||||
#backend.encryption = "start-tls"
|
||||
backend.encryption = "tls" # or true
|
||||
|
||||
# IMAP server login.
|
||||
#
|
||||
imap.login = "example@localhost"
|
||||
backend.login = "example@localhost"
|
||||
|
||||
# IMAP server password authentication configuration.
|
||||
#
|
||||
imap.auth.type = "password"
|
||||
backend.auth.type = "password"
|
||||
#
|
||||
# Password can be inlined (not recommended).
|
||||
#
|
||||
#imap.auth.raw = "p@assw0rd"
|
||||
#backend.auth.raw = "p@assw0rd"
|
||||
#
|
||||
# Password can be stored inside your system global keyring (requires
|
||||
# the keyring cargo feature). You must run at least once `himalaya
|
||||
# account configure` to set up the password.
|
||||
#
|
||||
#imap.auth.keyring = "example-imap"
|
||||
#backend.auth.keyring = "example-imap"
|
||||
#
|
||||
# Password can be retrieved from a shell command.
|
||||
#
|
||||
imap.auth.cmd = "pass show example-imap"
|
||||
backend.auth.cmd = "pass show example-imap"
|
||||
|
||||
# IMAP server OAuth 2.0 authorization configuration.
|
||||
#
|
||||
#imap.auth.type = "oauth2"
|
||||
#backend.auth.type = "oauth2"
|
||||
#
|
||||
# Client identifier issued to the client during the registration
|
||||
# process described in RFC6749.
|
||||
# See <https://datatracker.ietf.org/doc/html/rfc6749#section-2.2>.
|
||||
#
|
||||
#imap.auth.client-id = "client-id"
|
||||
#backend.auth.client-id = "client-id"
|
||||
#
|
||||
# Client password issued to the client during the registration process
|
||||
# described in RFC6749.
|
||||
|
@ -357,23 +424,23 @@ imap.auth.cmd = "pass show example-imap"
|
|||
# Defaults to keyring "<account-name>-imap-client-secret".
|
||||
# See <https://datatracker.ietf.org/doc/html/rfc6749#section-2.2>.
|
||||
#
|
||||
#imap.auth.client-secret.raw = "<raw-client-secret>"
|
||||
#imap.auth.client-secret.keyring = "example-imap-client-secret"
|
||||
#imap.auth.client-secret.cmd = "pass show example-imap-client-secret"
|
||||
#backend.auth.client-secret.raw = "<raw-client-secret>"
|
||||
#backend.auth.client-secret.keyring = "example-imap-client-secret"
|
||||
#backend.auth.client-secret.cmd = "pass show example-imap-client-secret"
|
||||
#
|
||||
# Method for presenting an OAuth 2.0 bearer token to a service for
|
||||
# authentication.
|
||||
#
|
||||
#imap.auth.method = "oauthbearer"
|
||||
#imap.auth.method = "xoauth2"
|
||||
#backend.auth.method = "oauthbearer"
|
||||
#backend.auth.method = "xoauth2"
|
||||
#
|
||||
# URL of the authorization server's authorization endpoint.
|
||||
#
|
||||
#imap.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
#backend.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
#
|
||||
# URL of the authorization server's token endpoint.
|
||||
#
|
||||
#imap.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
|
||||
#backend.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
|
||||
#
|
||||
# Access token returned by the token endpoint and used to access
|
||||
# protected resources. It is recommended to use the keyring variant,
|
||||
|
@ -381,9 +448,9 @@ imap.auth.cmd = "pass show example-imap"
|
|||
#
|
||||
# Defaults to keyring "<account-name>-imap-access-token".
|
||||
#
|
||||
#imap.auth.access-token.raw = "<raw-access-token>"
|
||||
#imap.auth.access-token.keyring = "example-imap-access-token"
|
||||
#imap.auth.access-token.cmd = "pass show example-imap-access-token"
|
||||
#backend.auth.access-token.raw = "<raw-access-token>"
|
||||
#backend.auth.access-token.keyring = "example-imap-access-token"
|
||||
#backend.auth.access-token.cmd = "pass show example-imap-access-token"
|
||||
#
|
||||
# Refresh token used to obtain a new access token (if supported by the
|
||||
# authorization server). It is recommended to use the keyring variant,
|
||||
|
@ -391,30 +458,35 @@ imap.auth.cmd = "pass show example-imap"
|
|||
#
|
||||
# Defaults to keyring "<account-name>-imap-refresh-token".
|
||||
#
|
||||
#imap.auth.refresh-token.raw = "<raw-refresh-token>"
|
||||
#imap.auth.refresh-token.keyring = "example-imap-refresh-token"
|
||||
#imap.auth.refresh-token.cmd = "pass show example-imap-refresh-token"
|
||||
#backend.auth.refresh-token.raw = "<raw-refresh-token>"
|
||||
#backend.auth.refresh-token.keyring = "example-imap-refresh-token"
|
||||
#backend.auth.refresh-token.cmd = "pass show example-imap-refresh-token"
|
||||
#
|
||||
# Enable the protection, as defined in RFC7636.
|
||||
#
|
||||
# See <https://datatracker.ietf.org/doc/html/rfc7636>.
|
||||
#
|
||||
#imap.auth.pkce = true
|
||||
#backend.auth.pkce = true
|
||||
#
|
||||
# Access token scope(s), as defined by the authorization server.
|
||||
#
|
||||
#imap.auth.scope = "unique scope"
|
||||
#imap.auth.scopes = ["multiple", "scopes"]
|
||||
#backend.auth.scope = "unique scope"
|
||||
#backend.auth.scopes = ["multiple", "scopes"]
|
||||
#
|
||||
# URL scheme of the redirect server.
|
||||
# Defaults to http.
|
||||
#
|
||||
#backend.auth.redirect-scheme = "http"
|
||||
#
|
||||
# Host name of the redirect server.
|
||||
# Defaults to localhost.
|
||||
#
|
||||
#imap.auth.redirect-host = "localhost"
|
||||
#backend.auth.redirect-host = "localhost"
|
||||
#
|
||||
# Port of the redirect server.
|
||||
# Defaults to the first available one.
|
||||
#
|
||||
#imap.auth.redirect-port = 9999
|
||||
#backend.auth.redirect-port = 9999
|
||||
|
||||
########################################
|
||||
#### Maildir configuration #############
|
||||
|
@ -422,18 +494,18 @@ imap.auth.cmd = "pass show example-imap"
|
|||
|
||||
# Defines the Maildir backend as the default one for all features.
|
||||
#
|
||||
#backend = "maildir"
|
||||
#backend.type = "maildir"
|
||||
|
||||
# The Maildir root directory. The path should point to the root level
|
||||
# of the Maildir directory.
|
||||
#
|
||||
#maildir.root-dir = "~/.Mail/example"
|
||||
#backend.root-dir = "~/.Mail/example"
|
||||
|
||||
# Does the Maildir folder follows the Maildir++ standard?
|
||||
#
|
||||
# See <https://en.wikipedia.org/wiki/Maildir#Maildir++>.
|
||||
#
|
||||
#maildir.maildirpp = false
|
||||
#backend.maildirpp = false
|
||||
|
||||
########################################
|
||||
#### Notmuch configuration #############
|
||||
|
@ -441,25 +513,25 @@ imap.auth.cmd = "pass show example-imap"
|
|||
|
||||
# Defines the Notmuch backend as the default one for all features.
|
||||
#
|
||||
#backend = "notmuch"
|
||||
#backend.type = "notmuch"
|
||||
|
||||
# The path to the Notmuch database. The path should point to the root
|
||||
# directory containing the Notmuch database (usually the root Maildir
|
||||
# directory).
|
||||
#
|
||||
#notmuch.db-path = "~/.Mail/example"
|
||||
#backend.db-path = "~/.Mail/example"
|
||||
|
||||
# Overrides the default path to the Maildir folder.
|
||||
#
|
||||
#notmuch.maildir-path = "~/.Mail/example"
|
||||
#backend.maildir-path = "~/.Mail/example"
|
||||
|
||||
# Overrides the default Notmuch configuration file path.
|
||||
#
|
||||
#notmuch.config-path = "~/.notmuchrc"
|
||||
#backend.config-path = "~/.notmuchrc"
|
||||
|
||||
# Override the default Notmuch profile name.
|
||||
#
|
||||
#notmuch.profile = "example"
|
||||
#backend.profile = "example"
|
||||
|
||||
########################################
|
||||
#### SMTP configuration ################
|
||||
|
@ -467,55 +539,55 @@ imap.auth.cmd = "pass show example-imap"
|
|||
|
||||
# Defines the SMTP backend for the message sending feature.
|
||||
#
|
||||
message.send.backend = "smtp"
|
||||
message.send.backend.type = "smtp"
|
||||
|
||||
# SMTP server host name.
|
||||
#
|
||||
smtp.host = "localhost"
|
||||
message.send.backend.host = "localhost"
|
||||
|
||||
# SMTP server port.
|
||||
#
|
||||
#smtp.port = 25
|
||||
#smtp.port = 465
|
||||
smtp.port = 587
|
||||
#message.send.backend.port = 25
|
||||
#message.send.backend.port = 465
|
||||
message.send.backend.port = 587
|
||||
|
||||
# SMTP server encryption.
|
||||
#
|
||||
#smtp.encryption = "none" # or false
|
||||
#smtp.encryption = "start-tls"
|
||||
smtp.encryption = "tls" # or true
|
||||
#message.send.backend.encryption = "none" # or false
|
||||
#message.send.backend.encryption = "start-tls"
|
||||
message.send.backend.encryption = "tls" # or true
|
||||
|
||||
# SMTP server login.
|
||||
#
|
||||
smtp.login = "example@localhost"
|
||||
message.send.backend.login = "example@localhost"
|
||||
|
||||
# SMTP server password authentication configuration.
|
||||
#
|
||||
smtp.auth.type = "password"
|
||||
message.send.backend.auth.type = "password"
|
||||
#
|
||||
# Password can be inlined (not recommended).
|
||||
#
|
||||
#smtp.auth.raw = "p@assw0rd"
|
||||
#message.send.backend.auth.raw = "p@assw0rd"
|
||||
#
|
||||
# Password can be stored inside your system global keyring (requires
|
||||
# the keyring cargo feature). You must run at least once `himalaya
|
||||
# account configure` to set up the password.
|
||||
#
|
||||
#smtp.auth.keyring = "example-smtp"
|
||||
#message.send.backend.auth.keyring = "example-smtp"
|
||||
#
|
||||
# Password can be retrieved from a shell command.
|
||||
#
|
||||
smtp.auth.cmd = "pass show example-smtp"
|
||||
message.send.backend.auth.cmd = "pass show example-smtp"
|
||||
|
||||
# SMTP server OAuth 2.0 authorization configuration.
|
||||
#
|
||||
#smtp.auth.type = "oauth2"
|
||||
#message.send.backend.auth.type = "oauth2"
|
||||
#
|
||||
# Client identifier issued to the client during the registration
|
||||
# process described in RFC6749.
|
||||
# See <https://datatracker.ietf.org/doc/html/rfc6749#section-2.2>.
|
||||
#
|
||||
#smtp.auth.client-id = "client-id"
|
||||
#message.send.backend.auth.client-id = "client-id"
|
||||
#
|
||||
# Client password issued to the client during the registration process
|
||||
# described in RFC6749.
|
||||
|
@ -523,23 +595,23 @@ smtp.auth.cmd = "pass show example-smtp"
|
|||
# Defaults to keyring "<account-name>-smtp-client-secret".
|
||||
# See <https://datatracker.ietf.org/doc/html/rfc6749#section-2.2>.
|
||||
#
|
||||
#smtp.auth.client-secret.raw = "<raw-client-secret>"
|
||||
#smtp.auth.client-secret.keyring = "example-smtp-client-secret"
|
||||
#smtp.auth.client-secret.cmd = "pass show example-smtp-client-secret"
|
||||
#message.send.backend.auth.client-secret.raw = "<raw-client-secret>"
|
||||
#message.send.backend.auth.client-secret.keyring = "example-smtp-client-secret"
|
||||
#message.send.backend.auth.client-secret.cmd = "pass show example-smtp-client-secret"
|
||||
#
|
||||
# Method for presenting an OAuth 2.0 bearer token to a service for
|
||||
# authentication.
|
||||
#
|
||||
#smtp.auth.method = "oauthbearer"
|
||||
#smtp.auth.method = "xoauth2"
|
||||
#message.send.backend.auth.method = "oauthbearer"
|
||||
#message.send.backend.auth.method = "xoauth2"
|
||||
#
|
||||
# URL of the authorization server's authorization endpoint.
|
||||
#
|
||||
#smtp.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
#message.send.backend.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
#
|
||||
# URL of the authorization server's token endpoint.
|
||||
#
|
||||
#smtp.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
|
||||
#message.send.backend.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
|
||||
#
|
||||
# Access token returned by the token endpoint and used to access
|
||||
# protected resources. It is recommended to use the keyring variant,
|
||||
|
@ -547,9 +619,9 @@ smtp.auth.cmd = "pass show example-smtp"
|
|||
#
|
||||
# Defaults to keyring "<account-name>-smtp-access-token".
|
||||
#
|
||||
#smtp.auth.access-token.raw = "<raw-access-token>"
|
||||
#smtp.auth.access-token.keyring = "example-smtp-access-token"
|
||||
#smtp.auth.access-token.cmd = "pass show example-smtp-access-token"
|
||||
#message.send.backend.auth.access-token.raw = "<raw-access-token>"
|
||||
#message.send.backend.auth.access-token.keyring = "example-smtp-access-token"
|
||||
#message.send.backend.auth.access-token.cmd = "pass show example-smtp-access-token"
|
||||
#
|
||||
# Refresh token used to obtain a new access token (if supported by the
|
||||
# authorization server). It is recommended to use the keyring variant,
|
||||
|
@ -557,30 +629,35 @@ smtp.auth.cmd = "pass show example-smtp"
|
|||
#
|
||||
# Defaults to keyring "<account-name>-smtp-refresh-token".
|
||||
#
|
||||
#smtp.auth.refresh-token.raw = "<raw-refresh-token>"
|
||||
#smtp.auth.refresh-token.keyring = "example-smtp-refresh-token"
|
||||
#smtp.auth.refresh-token.cmd = "pass show example-smtp-refresh-token"
|
||||
#message.send.backend.auth.refresh-token.raw = "<raw-refresh-token>"
|
||||
#message.send.backend.auth.refresh-token.keyring = "example-smtp-refresh-token"
|
||||
#message.send.backend.auth.refresh-token.cmd = "pass show example-smtp-refresh-token"
|
||||
#
|
||||
# Enable the protection, as defined in RFC7636.
|
||||
#
|
||||
# See <https://datatracker.ietf.org/doc/html/rfc7636>.
|
||||
#
|
||||
#smtp.auth.pkce = true
|
||||
#message.send.backend.auth.pkce = true
|
||||
#
|
||||
# Access token scope(s), as defined by the authorization server.
|
||||
#
|
||||
#smtp.auth.scope = "unique scope"
|
||||
#smtp.auth.scopes = ["multiple", "scopes"]
|
||||
#message.send.backend.auth.scope = "unique scope"
|
||||
#message.send.backend.auth.scopes = ["multiple", "scopes"]
|
||||
#
|
||||
# URL scheme of the redirect server.
|
||||
# Defaults to http.
|
||||
#
|
||||
#message.send.backend.auth.redirect-scheme = "http"
|
||||
#
|
||||
# Host name of the redirect server.
|
||||
# Defaults to localhost.
|
||||
#
|
||||
#smtp.auth.redirect-host = "localhost"
|
||||
#message.send.backend.auth.redirect-host = "localhost"
|
||||
#
|
||||
# Port of the redirect server.
|
||||
# Defaults to the first available one.
|
||||
#
|
||||
#smtp.auth.redirect-port = 9999
|
||||
#message.send.backend.auth.redirect-port = 9999
|
||||
|
||||
########################################
|
||||
#### Sendmail configuration ############
|
||||
|
@ -588,8 +665,8 @@ smtp.auth.cmd = "pass show example-smtp"
|
|||
|
||||
# Defines the Sendmail backend for the message sending feature.
|
||||
#
|
||||
#message.send.backend = "sendmail"
|
||||
#message.send.backend.type = "sendmail"
|
||||
|
||||
# Customizes the sendmail shell command.
|
||||
#
|
||||
#sendmail.cmd = "/usr/bin/sendmail"
|
||||
#message.send.backend.cmd = "/usr/bin/sendmail"
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::backend::context::BackendContextBuilder;
|
||||
use email::backend::BackendBuilder;
|
||||
#[cfg(feature = "imap")]
|
||||
use email::imap::ImapContextBuilder;
|
||||
#[cfg(feature = "maildir")]
|
||||
use email::maildir::MaildirContextBuilder;
|
||||
#[cfg(feature = "notmuch")]
|
||||
use email::notmuch::NotmuchContextBuilder;
|
||||
#[cfg(feature = "sendmail")]
|
||||
use email::sendmail::SendmailContextBuilder;
|
||||
#[cfg(feature = "smtp")]
|
||||
use email::smtp::SmtpContextBuilder;
|
||||
use pimalaya_tui::{
|
||||
himalaya::config::{Backend, SendingBackend},
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::OptionalAccountNameArg, backend, config::Config, printer::Printer,
|
||||
};
|
||||
use crate::{account::arg::name::OptionalAccountNameArg, config::TomlConfig};
|
||||
|
||||
/// Check up the given account.
|
||||
///
|
||||
|
@ -19,102 +33,77 @@ pub struct AccountCheckUpCommand {
|
|||
}
|
||||
|
||||
impl AccountCheckUpCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing check up account command");
|
||||
|
||||
let account = self.account.name.as_ref().map(String::as_str);
|
||||
|
||||
printer.log("Checking configuration integrity…")?;
|
||||
printer.log("Checking configuration integrity…\n")?;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(account)?;
|
||||
let used_backends = toml_account_config.get_used_backends();
|
||||
|
||||
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?;
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
match toml_account_config.backend {
|
||||
#[cfg(feature = "maildir")]
|
||||
{
|
||||
printer.log("Checking Maildir integrity…")?;
|
||||
Some(Backend::Maildir(mdir_config)) => {
|
||||
printer.log("Checking Maildir integrity…\n")?;
|
||||
|
||||
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")]
|
||||
{
|
||||
printer.log("Checking IMAP integrity…")?;
|
||||
Some(Backend::Imap(imap_config)) => {
|
||||
printer.log("Checking IMAP integrity…\n")?;
|
||||
|
||||
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")]
|
||||
{
|
||||
printer.log("Checking Notmuch integrity…")?;
|
||||
Some(Backend::Notmuch(notmuch_config)) => {
|
||||
printer.log("Checking Notmuch integrity…\n")?;
|
||||
|
||||
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?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let sending_backend = toml_account_config
|
||||
.message
|
||||
.and_then(|msg| msg.send)
|
||||
.and_then(|send| send.backend);
|
||||
|
||||
match sending_backend {
|
||||
#[cfg(feature = "smtp")]
|
||||
{
|
||||
printer.log("Checking SMTP integrity…")?;
|
||||
Some(SendingBackend::Smtp(smtp_config)) => {
|
||||
printer.log("Checking SMTP integrity…\n")?;
|
||||
|
||||
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")]
|
||||
{
|
||||
printer.log("Checking Sendmail integrity…")?;
|
||||
Some(SendingBackend::Sendmail(sendmail_config)) => {
|
||||
printer.log("Checking Sendmail integrity…\n")?;
|
||||
|
||||
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!")
|
||||
printer.out("Checkup successfully completed!\n")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@ use email::imap::config::ImapAuthConfig;
|
|||
#[cfg(feature = "smtp")]
|
||||
use email::smtp::config::SmtpAuthConfig;
|
||||
#[cfg(any(feature = "imap", feature = "smtp", feature = "pgp"))]
|
||||
use pimalaya_tui::prompt;
|
||||
use pimalaya_tui::terminal::prompt;
|
||||
use pimalaya_tui::terminal::{cli::printer::Printer, config::TomlConfig as _};
|
||||
use tracing::info;
|
||||
#[cfg(any(feature = "imap", feature = "smtp"))]
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::{account::arg::name::AccountNameArg, config::Config, printer::Printer};
|
||||
use crate::{account::arg::name::AccountNameArg, config::TomlConfig};
|
||||
|
||||
/// Configure an account.
|
||||
///
|
||||
|
@ -31,20 +32,22 @@ pub struct AccountConfigureCommand {
|
|||
}
|
||||
|
||||
impl AccountConfigureCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing configure account command");
|
||||
|
||||
let account = &self.account.name;
|
||||
let (_, account_config) = config.into_toml_account_config(Some(account))?;
|
||||
let (_, toml_account_config) = config.to_toml_account_config(Some(account))?;
|
||||
|
||||
if self.reset {
|
||||
#[cfg(feature = "imap")]
|
||||
if let Some(ref config) = account_config.imap {
|
||||
let reset = match &config.auth {
|
||||
ImapAuthConfig::Passwd(config) => config.reset().await,
|
||||
{
|
||||
let reset = match toml_account_config.imap_auth_config() {
|
||||
Some(ImapAuthConfig::Password(config)) => config.reset().await,
|
||||
#[cfg(feature = "oauth2")]
|
||||
ImapAuthConfig::OAuth2(config) => config.reset().await,
|
||||
Some(ImapAuthConfig::OAuth2(config)) => config.reset().await,
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(err) = reset {
|
||||
warn!("error while resetting imap secrets: {err}");
|
||||
debug!("error while resetting imap secrets: {err:?}");
|
||||
|
@ -52,12 +55,14 @@ impl AccountConfigureCommand {
|
|||
}
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
if let Some(ref config) = account_config.smtp {
|
||||
let reset = match &config.auth {
|
||||
SmtpAuthConfig::Passwd(config) => config.reset().await,
|
||||
{
|
||||
let reset = match toml_account_config.smtp_auth_config() {
|
||||
Some(SmtpAuthConfig::Password(config)) => config.reset().await,
|
||||
#[cfg(feature = "oauth2")]
|
||||
SmtpAuthConfig::OAuth2(config) => config.reset().await,
|
||||
Some(SmtpAuthConfig::OAuth2(config)) => config.reset().await,
|
||||
_ => Ok(()),
|
||||
};
|
||||
|
||||
if let Err(err) = reset {
|
||||
warn!("error while resetting smtp secrets: {err}");
|
||||
debug!("error while resetting smtp secrets: {err:?}");
|
||||
|
@ -65,56 +70,54 @@ impl AccountConfigureCommand {
|
|||
}
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
if let Some(ref config) = account_config.pgp {
|
||||
if let Some(config) = &toml_account_config.pgp {
|
||||
config.reset().await?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
if let Some(ref config) = account_config.imap {
|
||||
match &config.auth {
|
||||
ImapAuthConfig::Passwd(config) => {
|
||||
match toml_account_config.imap_auth_config() {
|
||||
Some(ImapAuthConfig::Password(config)) => {
|
||||
config
|
||||
.configure(|| Ok(prompt::password("IMAP password")?))
|
||||
.await
|
||||
}
|
||||
#[cfg(feature = "oauth2")]
|
||||
ImapAuthConfig::OAuth2(config) => {
|
||||
Some(ImapAuthConfig::OAuth2(config)) => {
|
||||
config
|
||||
.configure(|| Ok(prompt::secret("IMAP OAuth 2.0 clientsecret")?))
|
||||
.configure(|| Ok(prompt::secret("IMAP OAuth 2.0 client secret")?))
|
||||
.await
|
||||
}
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
if let Some(ref config) = account_config.smtp {
|
||||
match &config.auth {
|
||||
SmtpAuthConfig::Passwd(config) => {
|
||||
match toml_account_config.smtp_auth_config() {
|
||||
Some(SmtpAuthConfig::Password(config)) => {
|
||||
config
|
||||
.configure(|| Ok(prompt::password("SMTP password")?))
|
||||
.await
|
||||
}
|
||||
#[cfg(feature = "oauth2")]
|
||||
SmtpAuthConfig::OAuth2(config) => {
|
||||
Some(SmtpAuthConfig::OAuth2(config)) => {
|
||||
config
|
||||
.configure(|| Ok(prompt::secret("SMTP OAuth 2.0 client secret")?))
|
||||
.await
|
||||
}
|
||||
_ => Ok(()),
|
||||
}?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
if let Some(ref config) = account_config.pgp {
|
||||
if let Some(config) = &toml_account_config.pgp {
|
||||
config
|
||||
.configure(&account_config.email, || {
|
||||
.configure(&toml_account_config.email, || {
|
||||
Ok(prompt::password("PGP secret key password")?)
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
printer.out(format!(
|
||||
"Account {account} successfully {}configured!",
|
||||
"Account {account} successfully {}configured!\n",
|
||||
if self.reset { "re" } else { "" }
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
3
src/config.rs
Normal file
3
src/config.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
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,32 @@ impl Default for ListEnvelopesCommand {
|
|||
}
|
||||
|
||||
impl ListEnvelopesCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing list envelopes command");
|
||||
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let toml_account_config = Arc::new(toml_account_config);
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let page = 1.max(self.page) - 1;
|
||||
let page_size = self
|
||||
.page_size
|
||||
.unwrap_or_else(|| account_config.get_envelope_list_page_size());
|
||||
|
||||
let list_envelopes_kind = toml_account_config.list_envelopes_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
let backend = BackendBuilder::new(
|
||||
toml_account_config.clone(),
|
||||
account_config.clone(),
|
||||
list_envelopes_kind,
|
||||
|builder| builder.set_list_envelopes(BackendFeatureSource::Context),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_list_envelopes(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let query = self
|
||||
|
|
|
@ -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,27 @@ pub struct ThreadEnvelopesCommand {
|
|||
}
|
||||
|
||||
impl ThreadEnvelopesCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing thread envelopes command");
|
||||
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let account_config = Arc::new(account_config);
|
||||
let folder = &self.folder.name;
|
||||
let thread_envelopes_kind = toml_account_config.thread_envelopes_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
thread_envelopes_kind,
|
||||
|builder| builder.set_thread_envelopes(BackendFeatureSource::Context),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_thread_envelopes(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let query = self
|
||||
|
|
|
@ -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,21 @@ impl FlagAddCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_flags_kind = toml_account_config.add_flags_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
add_flags_kind,
|
||||
|builder| builder.set_add_flags(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_flags(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.add_flags(folder, &ids, &flags).await?;
|
||||
|
||||
printer.out(format!("Flag(s) {flags} successfully added!"))
|
||||
printer.out(format!("Flag(s) {flags} successfully added!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,21 @@ impl FlagRemoveCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let remove_flags_kind = toml_account_config.remove_flags_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
remove_flags_kind,
|
||||
|builder| builder.set_remove_flags(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_remove_flags(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.remove_flags(folder, &ids, &flags).await?;
|
||||
|
||||
printer.out(format!("Flag(s) {flags} successfully removed!"))
|
||||
printer.out(format!("Flag(s) {flags} successfully removed!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
config::Config,
|
||||
config::TomlConfig,
|
||||
flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs},
|
||||
folder::arg::name::FolderNameOptionalFlag,
|
||||
printer::Printer,
|
||||
};
|
||||
|
||||
/// Replace flag(s) of an envelope.
|
||||
|
@ -29,7 +33,7 @@ pub struct FlagSetCommand {
|
|||
}
|
||||
|
||||
impl FlagSetCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing set flag(s) command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
@ -38,18 +42,21 @@ impl FlagSetCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let set_flags_kind = toml_account_config.set_flags_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
set_flags_kind,
|
||||
|builder| builder.set_set_flags(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_set_flags(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.set_flags(folder, &ids, &flags).await?;
|
||||
|
||||
printer.out(format!("Flag(s) {flags} successfully replaced!"))
|
||||
printer.out(format!("Flag(s) {flags} successfully replaced!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,19 @@ impl AttachmentDownloadCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
get_messages_kind,
|
||||
|builder| builder.set_get_messages(BackendFeatureSource::Context),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_get_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let emails = backend.get_messages(folder, ids).await?;
|
||||
|
|
|
@ -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,23 @@ impl MessageCopyCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let copy_messages_kind = toml_account_config.copy_messages_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
copy_messages_kind,
|
||||
|builder| builder.set_copy_messages(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_copy_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.copy_messages(source, target, ids).await?;
|
||||
|
||||
printer.out(format!(
|
||||
"Message(s) successfully copied from {source} to {target}!"
|
||||
"Message(s) successfully copied from {source} to {target}!\n"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config,
|
||||
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
||||
printer::Printer,
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs,
|
||||
folder::arg::name::FolderNameOptionalFlag,
|
||||
};
|
||||
|
||||
/// Mark as deleted a message from a folder.
|
||||
|
@ -28,7 +33,7 @@ pub struct MessageDeleteCommand {
|
|||
}
|
||||
|
||||
impl MessageDeleteCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing delete message(s) command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
@ -38,18 +43,21 @@ impl MessageDeleteCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let delete_messages_kind = toml_account_config.delete_messages_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
delete_messages_kind,
|
||||
|builder| builder.set_delete_messages(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_delete_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.delete_messages(folder, ids).await?;
|
||||
|
||||
printer.out(format!("Message(s) successfully removed from {folder}!"))
|
||||
printer.out(format!("Message(s) successfully removed from {folder}!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::{eyre::eyre, Result};
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use pimalaya_tui::{
|
||||
himalaya::{backend::BackendBuilder, editor},
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
config::Config,
|
||||
config::TomlConfig,
|
||||
envelope::arg::ids::EnvelopeIdArg,
|
||||
folder::arg::name::FolderNameOptionalFlag,
|
||||
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs},
|
||||
printer::Printer,
|
||||
ui::editor,
|
||||
};
|
||||
|
||||
/// Forward a message.
|
||||
|
@ -39,7 +42,7 @@ pub struct MessageForwardCommand {
|
|||
}
|
||||
|
||||
impl MessageForwardCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing forward message command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
@ -48,18 +51,20 @@ impl MessageForwardCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let send_message_kind = toml_account_config.send_message_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
add_message_kind.into_iter().chain(send_message_kind),
|
||||
|builder| {
|
||||
builder.set_add_message(BackendFeatureSource::Context);
|
||||
builder.set_send_message(BackendFeatureSource::Context);
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_message(BackendFeatureSource::Context)
|
||||
.with_send_message(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let id = self.envelope.id;
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use mail_builder::MessageBuilder;
|
||||
use pimalaya_tui::{
|
||||
himalaya::{backend::BackendBuilder, editor},
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config, printer::Printer,
|
||||
ui::editor,
|
||||
};
|
||||
use crate::{account::arg::name::AccountNameFlag, config::TomlConfig};
|
||||
|
||||
/// Parse and edit a message from a mailto URL string.
|
||||
///
|
||||
|
@ -34,25 +37,27 @@ impl MessageMailtoCommand {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing mailto message command");
|
||||
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let send_message_kind = toml_account_config.send_message_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
add_message_kind.into_iter().chain(send_message_kind),
|
||||
|builder| {
|
||||
builder.set_add_message(BackendFeatureSource::Context);
|
||||
builder.set_send_message(BackendFeatureSource::Context);
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_message(BackendFeatureSource::Context)
|
||||
.with_send_message(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let mut builder = MessageBuilder::new().to(self.url.path());
|
||||
|
|
|
@ -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,23 @@ impl MessageMoveCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let move_messages_kind = toml_account_config.move_messages_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
move_messages_kind,
|
||||
|builder| builder.set_move_messages(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_move_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.move_messages(source, target, ids).await?;
|
||||
|
||||
printer.out(format!(
|
||||
"Message(s) successfully moved from {source} to {target}!"
|
||||
"Message(s) successfully moved from {source} to {target}!\n"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use mml::message::FilterParts;
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
#[allow(unused)]
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config,
|
||||
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
||||
printer::Printer,
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs,
|
||||
folder::arg::name::FolderNameOptionalFlag,
|
||||
};
|
||||
|
||||
/// Read a message.
|
||||
|
@ -73,7 +78,7 @@ pub struct MessageReadCommand {
|
|||
}
|
||||
|
||||
impl MessageReadCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing read message(s) command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
@ -83,14 +88,19 @@ impl MessageReadCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
get_messages_kind,
|
||||
|builder| builder.set_get_messages(BackendFeatureSource::Context),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_get_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let emails = if self.preview {
|
||||
|
|
|
@ -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,17 @@ impl MessageSaveCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
add_message_kind,
|
||||
|builder| builder.set_add_message(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_message(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let is_tty = io::stdin().is_terminal();
|
||||
|
@ -60,6 +69,6 @@ impl MessageSaveCommand {
|
|||
|
||||
backend.add_message(folder, msg.as_bytes()).await?;
|
||||
|
||||
printer.out(format!("Message successfully saved to {folder}!"))
|
||||
printer.out(format!("Message successfully saved to {folder}!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,20 @@ impl MessageThreadCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
get_messages_kind,
|
||||
|builder| {
|
||||
builder.set_thread_envelopes(BackendFeatureSource::Context);
|
||||
builder.set_get_messages(BackendFeatureSource::Context);
|
||||
builder
|
||||
.without_features()
|
||||
.with_get_messages(BackendFeatureSource::Context)
|
||||
.with_thread_envelopes(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let envelopes = backend
|
||||
|
|
|
@ -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,19 @@ impl TemplateForwardCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
get_messages_kind,
|
||||
|builder| builder.set_get_messages(BackendFeatureSource::Context),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_get_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let id = self.envelope.id;
|
||||
|
|
|
@ -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,19 @@ impl TemplateReplyCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
get_messages_kind,
|
||||
|builder| builder.set_get_messages(BackendFeatureSource::Context),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_get_messages(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let tpl = backend
|
||||
|
|
|
@ -2,13 +2,19 @@ use clap::Parser;
|
|||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use mml::MmlCompilerBuilder;
|
||||
use std::io::{self, BufRead, IsTerminal};
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use std::{
|
||||
io::{self, BufRead, IsTerminal},
|
||||
sync::Arc,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config,
|
||||
email::template::arg::TemplateRawArg, folder::arg::name::FolderNameOptionalFlag,
|
||||
printer::Printer,
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::TemplateRawArg,
|
||||
folder::arg::name::FolderNameOptionalFlag,
|
||||
};
|
||||
|
||||
/// Save a template to a folder.
|
||||
|
@ -30,7 +36,7 @@ pub struct TemplateSaveCommand {
|
|||
}
|
||||
|
||||
impl TemplateSaveCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing save template command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
@ -39,14 +45,19 @@ impl TemplateSaveCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
add_message_kind,
|
||||
|builder| builder.set_add_message(BackendFeatureSource::Context),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_message(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let is_tty = io::stdin().is_terminal();
|
||||
|
@ -72,6 +83,6 @@ impl TemplateSaveCommand {
|
|||
|
||||
backend.add_message(folder, &msg).await?;
|
||||
|
||||
printer.out(format!("Template successfully saved to {folder}!"))
|
||||
printer.out(format!("Template successfully saved to {folder}!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,18 @@ use clap::Parser;
|
|||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use mml::MmlCompilerBuilder;
|
||||
use std::io::{self, BufRead, IsTerminal};
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use std::{
|
||||
io::{self, BufRead, IsTerminal},
|
||||
sync::Arc,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config,
|
||||
email::template::arg::TemplateRawArg, printer::Printer,
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::TemplateRawArg,
|
||||
};
|
||||
|
||||
/// Send a template.
|
||||
|
@ -26,28 +32,26 @@ pub struct TemplateSendCommand {
|
|||
}
|
||||
|
||||
impl TemplateSendCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing send template command");
|
||||
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
||||
toml_account_config
|
||||
.add_message_kind()
|
||||
.filter(|_| account_config.should_save_copy_sent_message()),
|
||||
);
|
||||
let account_config = Arc::new(account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
account_config.clone(),
|
||||
send_message_kind,
|
||||
|builder| {
|
||||
builder.set_send_message(BackendFeatureSource::Context);
|
||||
builder.set_add_message(BackendFeatureSource::Context);
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_message(BackendFeatureSource::Context)
|
||||
.with_send_message(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let tpl = if io::stdin().is_terminal() {
|
||||
|
|
|
@ -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,30 @@ pub struct AddFolderCommand {
|
|||
}
|
||||
|
||||
impl AddFolderCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing create folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_folder_kind = toml_account_config.add_folder_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
add_folder_kind,
|
||||
|builder| builder.set_add_folder(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_add_folder(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.add_folder(folder).await?;
|
||||
|
||||
printer.log(format!("Folder {folder} successfully created!"))
|
||||
printer.out(format!("Folder {folder} successfully created!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use std::process;
|
||||
use std::{process, sync::Arc};
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::{backend::feature::BackendFeatureSource, folder::delete::DeleteFolder};
|
||||
use pimalaya_tui::prompt;
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _, prompt},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config,
|
||||
folder::arg::name::FolderNameArg, printer::Printer,
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
|
||||
};
|
||||
|
||||
/// Delete a folder.
|
||||
|
@ -25,12 +27,13 @@ pub struct FolderDeleteCommand {
|
|||
}
|
||||
|
||||
impl FolderDeleteCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing delete folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
let confirm = format!("Do you really want to delete the folder {folder}? All emails will be definitely deleted.");
|
||||
let confirm = format!("Do you really want to delete the folder {folder}");
|
||||
let confirm = format!("{confirm}? All emails will be definitely deleted.");
|
||||
|
||||
if !prompt::bool(confirm, false)? {
|
||||
process::exit(0);
|
||||
|
@ -40,18 +43,21 @@ impl FolderDeleteCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let delete_folder_kind = toml_account_config.delete_folder_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
delete_folder_kind,
|
||||
|builder| builder.set_delete_folder(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_delete_folder(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.delete_folder(folder).await?;
|
||||
|
||||
printer.log(format!("Folder {folder} successfully deleted!"))
|
||||
printer.out(format!("Folder {folder} successfully deleted!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::{backend::feature::BackendFeatureSource, folder::expunge::ExpungeFolder};
|
||||
use pimalaya_tui::{
|
||||
himalaya::backend::BackendBuilder,
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::Config,
|
||||
folder::arg::name::FolderNameArg, printer::Printer,
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
|
||||
};
|
||||
|
||||
/// Expunge a folder.
|
||||
|
@ -23,7 +28,7 @@ pub struct FolderExpungeCommand {
|
|||
}
|
||||
|
||||
impl FolderExpungeCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing expunge folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
@ -31,18 +36,21 @@ impl FolderExpungeCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let expunge_folder_kind = toml_account_config.expunge_folder_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
expunge_folder_kind,
|
||||
|builder| builder.set_expunge_folder(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_expunge_folder(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.expunge_folder(folder).await?;
|
||||
|
||||
printer.log(format!("Folder {folder} successfully expunged!"))
|
||||
printer.out(format!("Folder {folder} successfully expunged!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::{backend::feature::BackendFeatureSource, folder::list::ListFolders};
|
||||
use pimalaya_tui::{
|
||||
himalaya::{
|
||||
backend::BackendBuilder,
|
||||
config::{Folders, FoldersTable},
|
||||
},
|
||||
terminal::{cli::printer::Printer, config::TomlConfig as _},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
config::Config,
|
||||
folder::{Folders, FoldersTable},
|
||||
printer::Printer,
|
||||
};
|
||||
use crate::{account::arg::name::AccountNameFlag, config::TomlConfig};
|
||||
|
||||
/// List all folders.
|
||||
///
|
||||
|
@ -29,21 +32,26 @@ pub struct FolderListCommand {
|
|||
}
|
||||
|
||||
impl FolderListCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &Config) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing list folders command");
|
||||
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let list_folders_kind = toml_account_config.list_folders_kind();
|
||||
let toml_account_config = Arc::new(toml_account_config);
|
||||
|
||||
let backend = Backend::new(
|
||||
let backend = BackendBuilder::new(
|
||||
toml_account_config.clone(),
|
||||
account_config.clone(),
|
||||
list_folders_kind,
|
||||
|builder| builder.set_list_folders(BackendFeatureSource::Context),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_list_folders(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let folders = Folders::from(backend.list_folders().await?);
|
||||
|
|
|
@ -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,21 @@ impl FolderPurgeCommand {
|
|||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let purge_folder_kind = toml_account_config.purge_folder_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
purge_folder_kind,
|
||||
|builder| builder.set_purge_folder(BackendFeatureSource::Context),
|
||||
let backend = BackendBuilder::new(
|
||||
Arc::new(toml_account_config),
|
||||
Arc::new(account_config),
|
||||
|builder| {
|
||||
builder
|
||||
.without_features()
|
||||
.with_purge_folder(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.purge_folder(folder).await?;
|
||||
|
||||
printer.log(format!("Folder {folder} successfully purged!"))
|
||||
printer.out(format!("Folder {folder} successfully purged!\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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