From 662bd26eb15fb5a0882b0079b8024a219946540e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Mon, 1 Jun 2026 15:19:45 +0200 Subject: [PATCH] refactor: remove composer and reader Composers and readers did not work as expected. It is just not possible for himalaya to spawn a command that spawns $EDITOR, piping and redirection cannot satisfy all the needs. Either the $EDITOR does not spawn (hangs over), either himalaya does not collect any output from edition. The simplest way is to use an intermediate temp file, or use process substitution. For eg., using mml: mml compose >(himalaya message send) You can also write into a file then feed himalaya with it. --- CHANGELOG.md | 8 + CONTRIBUTING.md | 2 +- MIGRATION.md | 17 +- README.md | 38 +---- config.sample.toml | 26 --- src/account/context.rs | 63 +------ src/cli.rs | 40 ++--- src/config.rs | 81 +-------- src/imap/cli.rs | 18 +- src/imap/client.rs | 27 +-- src/imap/envelope/cli.rs | 16 +- src/imap/envelope/get.rs | 10 +- src/imap/envelope/list.rs | 20 ++- src/imap/envelope/search.rs | 14 +- src/imap/envelope/sort.rs | 10 +- src/imap/envelope/thread.rs | 4 +- src/imap/flag/add.rs | 2 +- src/imap/flag/cli.rs | 10 +- src/imap/flag/list.rs | 12 +- src/imap/flag/remove.rs | 2 +- src/imap/flag/set.rs | 2 +- src/imap/id.rs | 10 +- src/imap/mailbox/cli.rs | 12 +- src/imap/mailbox/close.rs | 2 +- src/imap/mailbox/create.rs | 2 +- src/imap/mailbox/delete.rs | 2 +- src/imap/mailbox/expunge.rs | 2 +- src/imap/mailbox/list.rs | 12 +- src/imap/mailbox/purge.rs | 2 +- src/imap/mailbox/rename.rs | 2 +- src/imap/mailbox/select.rs | 2 +- src/imap/mailbox/status.rs | 10 +- src/imap/mailbox/subscribe.rs | 2 +- src/imap/mailbox/unselect.rs | 2 +- src/imap/mailbox/unsubscribe.rs | 2 +- src/imap/message/cli.rs | 10 +- src/imap/message/copy.rs | 2 +- src/imap/message/export.rs | 12 +- src/imap/message/get.rs | 2 +- src/imap/message/move.rs | 2 +- src/imap/message/read.rs | 2 +- src/imap/message/save.rs | 2 +- src/jmap/cli.rs | 20 ++- src/jmap/client.rs | 21 +-- src/jmap/email/cli.rs | 12 +- src/jmap/email/copy.rs | 2 +- src/jmap/email/delete.rs | 2 +- src/jmap/email/export.rs | 2 +- src/jmap/email/get.rs | 28 ++-- src/jmap/email/import.rs | 2 +- src/jmap/email/parse.rs | 2 +- src/jmap/email/query.rs | 28 ++-- src/jmap/email/read.rs | 2 +- src/jmap/email/update.rs | 2 +- src/jmap/identity/cli.rs | 10 +- src/jmap/identity/create.rs | 2 +- src/jmap/identity/delete.rs | 2 +- src/jmap/identity/get.rs | 10 +- src/jmap/identity/update.rs | 2 +- src/jmap/mailbox/cli.rs | 12 +- src/jmap/mailbox/create.rs | 2 +- src/jmap/mailbox/destroy.rs | 2 +- src/jmap/mailbox/get.rs | 18 +- src/jmap/mailbox/query.rs | 18 +- src/jmap/mailbox/update.rs | 2 +- src/jmap/query.rs | 2 +- src/jmap/submission/cancel.rs | 2 +- src/jmap/submission/cli.rs | 14 +- src/jmap/submission/create.rs | 10 +- src/jmap/submission/get.rs | 10 +- src/jmap/submission/query.rs | 10 +- src/jmap/thread/cli.rs | 10 +- src/jmap/thread/get.rs | 10 +- src/jmap/vacation/cli.rs | 10 +- src/jmap/vacation/get.rs | 10 +- src/jmap/vacation/set.rs | 2 +- src/m2dir/cli.rs | 14 +- src/m2dir/client.rs | 16 +- src/m2dir/create.rs | 2 +- src/m2dir/delete.rs | 2 +- src/m2dir/envelope/cli.rs | 12 +- src/m2dir/envelope/get.rs | 10 +- src/m2dir/envelope/list.rs | 20 ++- src/m2dir/flag/add.rs | 2 +- src/m2dir/flag/cli.rs | 10 +- src/m2dir/flag/list.rs | 12 +- src/m2dir/flag/remove.rs | 2 +- src/m2dir/flag/set.rs | 2 +- src/m2dir/list.rs | 12 +- src/m2dir/message/cli.rs | 2 +- src/m2dir/message/export.rs | 2 +- src/m2dir/message/get.rs | 2 +- src/m2dir/message/read.rs | 2 +- src/m2dir/message/save.rs | 2 +- src/maildir/cli.rs | 14 +- src/maildir/client.rs | 23 ++- src/maildir/create.rs | 2 +- src/maildir/delete.rs | 2 +- src/maildir/envelope/cli.rs | 12 +- src/maildir/envelope/get.rs | 10 +- src/maildir/envelope/list.rs | 20 ++- src/maildir/flag/add.rs | 2 +- src/maildir/flag/cli.rs | 10 +- src/maildir/flag/list.rs | 8 +- src/maildir/flag/remove.rs | 2 +- src/maildir/flag/set.rs | 2 +- src/maildir/list.rs | 12 +- src/maildir/message/cli.rs | 2 +- src/maildir/message/copy.rs | 2 +- src/maildir/message/export.rs | 2 +- src/maildir/message/get.rs | 2 +- src/maildir/message/move.rs | 2 +- src/maildir/message/read.rs | 2 +- src/maildir/message/save.rs | 2 +- src/maildir/rename.rs | 2 +- src/shared/attachments/cli.rs | 12 +- src/shared/attachments/download.rs | 31 ++-- src/shared/attachments/list.rs | 26 +-- src/shared/client.rs | 13 +- src/shared/envelopes/cli.rs | 12 +- src/shared/envelopes/list.rs | 44 ++--- src/shared/envelopes/search.rs | 44 ++--- src/shared/flags/add.rs | 10 +- src/shared/flags/cli.rs | 14 +- src/shared/flags/remove.rs | 10 +- src/shared/flags/set.rs | 10 +- src/shared/mailboxes/cli.rs | 10 +- src/shared/mailboxes/list.rs | 20 ++- src/shared/messages/add.rs | 2 +- src/shared/messages/arg.rs | 2 +- src/shared/messages/cli.rs | 42 ++--- src/shared/messages/compose.rs | 23 ++- src/shared/messages/compose_with.rs | 76 --------- src/shared/messages/copy.rs | 2 +- src/shared/messages/forward.rs | 21 ++- src/shared/messages/forward_with.rs | 78 --------- src/shared/messages/mailto.rs | 246 ---------------------------- src/shared/messages/mod.rs | 6 - src/shared/messages/mv.rs | 2 +- src/shared/messages/output.rs | 43 +++-- src/shared/messages/read.rs | 6 +- src/shared/messages/read_with.rs | 76 --------- src/shared/messages/reply.rs | 21 ++- src/shared/messages/reply_with.rs | 80 --------- src/shared/messages/runner.rs | 69 -------- src/shared/messages/send.rs | 2 +- src/smtp/cli.rs | 2 +- src/smtp/client.rs | 23 +-- src/smtp/message/cli.rs | 2 +- src/smtp/message/send.rs | 2 +- src/wizard/discover.rs | 1 - 151 files changed, 864 insertions(+), 1291 deletions(-) delete mode 100644 src/shared/messages/compose_with.rs delete mode 100644 src/shared/messages/forward_with.rs delete mode 100644 src/shared/messages/mailto.rs delete mode 100644 src/shared/messages/read_with.rs delete mode 100644 src/shared/messages/reply_with.rs delete mode 100644 src/shared/messages/runner.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 068141e9..58777fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Unified raw-message input across `messages add`, `messages send`, `imap message save`, `maildir message save`, `jmap email import` and `smtp message send` behind a single `MessageArg` (ported from `mml::cli::args::MessageArg`). Every command now accepts the same three forms: a positional file path, a positional inline raw message (with `\r` / `\n` literals normalized to `\r\n`), or stdin when piped. The legacy `--file ` flag on `messages add` is gone (positional path replaces it). +- Split the merged `Account` out of every client wrapper (`EmailClient`, `ImapClient`, `JmapClient`, `MaildirClient`, `M2dirClient`, `SmtpClient`). Subcommands now receive `account: &mut Account` and `client: &mut Client` as sibling arguments rather than reaching through `client.account`, which keeps account access borrow-disjoint from `&mut client` calls. + ### Fixed - Fixed compilation error when `wizard` feature was disabled ([#634]). +- Fixed `--save ` on `messages compose` / `reply` / `forward` to resolve the mailbox name through the account's alias map (`account.resolve_mailbox`) before calling the backend, so `--save Sent` honours e.g. `mailbox.alias.sent = "[Gmail]/Sent Mail"`. + +### Removed + +- Removed the `[message.composer.*]` and `[message.reader.*]` config tables together with the `messages compose-with`, `reply-with`, `forward-with`, `mailto` and `read-with` subcommands. The "stdout = MIME draft" contract was structurally incompatible with composers that spawn an interactive editor: the editor inherited the parent's piped stdout, breaking its UI. Richer composition is now wired through standalone tools chained into `messages send` / `messages add` via a tempfile or shell process substitution; see the README and [mml](https://github.com/pimalaya/mml). + ## [1.2.0] - 2026-02-19 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93032ead..990a4338 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Himalaya CLI is the command-line front-end of the [Pimalaya](https://github.com/ - [pimalaya/stream](https://github.com/pimalaya/stream): TCP / TLS / SASL plumbing shared by all std clients. - [pimalaya/cli](https://github.com/pimalaya/cli): cross-binary CLI helpers (printer, prompt, wizard primitives, clap args, build-time env, spinner). - [pimalaya/config](https://github.com/pimalaya/config): TOML configuration loader and shell-expanded secrets. -- [pimalaya/mml](https://github.com/pimalaya/mml): MIME Meta Language composer / reader, plugged in via `[message.composer.*]` / `[message.reader.*]`. +- [pimalaya/mml](https://github.com/pimalaya/mml): MIME Meta Language composer / interpreter, chained into `messages send` / `messages add` via a tempfile or shell process substitution. - [pimalaya/sirup](https://github.com/pimalaya/sirup): session re-use over a Unix socket (pair with `imap.server` / `smtp.server` to amortize TLS handshakes). - [pimalaya/ortie](https://github.com/pimalaya/ortie): standalone OAuth 2.0 token broker (replaces v1's bundled `oauth-lib`). - [pimalaya/mimosa](https://github.com/pimalaya/mimosa): standalone secret manager (replaces v1's bundled `keyring-lib`). diff --git a/MIGRATION.md b/MIGRATION.md index 256cb70b..91852f3b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -69,13 +69,13 @@ New in v2: `-b`, `--backend` (force a specific backend for shared commands) and - `save --folder` (optional) becomes `add --mailbox` (mandatory). - `save ` split into the explicit `--file ` and positional ``. - Added `add --flag` to attach flags at insertion time. -- `write`, `reply`, `forward` are no longer interactive. They build the message from CLI flags through the built-in flag composer. The interactive variants live under `compose-with`, `reply-with`, `forward-with`, which delegate to a user-defined composer declared in `[message.composer.*]`. -- `read` no longer renders human-readable text; that responsibility moved to the reader. The v2 `read` prints message-level info; the reader pipeline lives under `read-with`, backed by `[message.reader.*]`. -- `mailto ` now pipes the parsed RFC 6068 URI through a user-defined composer (same routing options as `compose-with`) instead of opening the v1 interactive editor. -- `messages send` gains `--file ` as a parity with `messages add` for reading the raw message from a file instead of stdin or the positional argument. +- `write`, `reply`, `forward` are no longer interactive. They build the message from CLI flags through the built-in flag composer. Interactive composition is delegated to standalone tools chained into `messages send` / `messages add` via a tempfile or shell process substitution; no `*-with` subcommands or `[message.composer.*]` table on the himalaya side. +- `read` no longer renders human-readable text; the v2 `read` prints message-level info. For custom rendering, pipe `read --raw` into a standalone interpreter. +- `mailto:` URI handling is no longer a himalaya subcommand. Register a small shell wrapper (e.g. `mml mailto "$1" /tmp/draft.eml && himalaya messages send /tmp/draft.eml`) as your desktop mailto handler. +- `messages send` and `messages add` read the raw message from a positional path, an inline raw value, or stdin (the unified `MessageArg`). - `export` and `edit` are removed. -See [pimalaya/mml](https://github.com/pimalaya/mml) for a ready-to-use composer and reader. +See [pimalaya/mml](https://github.com/pimalaya/mml) for a ready-to-use composer / interpreter. #### Attachments @@ -86,7 +86,7 @@ See [pimalaya/mml](https://github.com/pimalaya/mml) for a ready-to-use composer #### Template -Fully removed. The template pipeline (compose / reply / forward drafts, MML compile, MIME interpret) lives in [pimalaya/mml](https://github.com/pimalaya/mml) as both a library and a CLI; plug it into himalaya as a composer/reader. +Fully removed. The template pipeline (compose / reply / forward drafts, MML compile, MIME interpret) lives in [pimalaya/mml](https://github.com/pimalaya/mml) as both a library and a CLI; chain its CLI into `messages send` / `messages add` (see the README). ### Configuration changes @@ -96,8 +96,7 @@ The full configuration schema is documented in [config.sample.toml](./config.sam - Removed `display-name`, `signature`, `signature-delim`: composition left the CLI. - Only `downloads-dir` remains for the `attachments download` command. -- Composition / reading hooks live under `[message.composer.]` and `[message.reader.]`, each optionally flagged `default = true`. A composer entry sets one shell command per operation (`compose`, `reply`, `forward`); a reader entry sets a single `command`. -- The `message`, `template` and `pgp` top-level entries are removed. +- The `message`, `template` and `pgp` top-level entries are removed. Composition and rendering happen outside himalaya now (see the README for the recommended shell-pipeline shapes). #### Table customization @@ -221,4 +220,4 @@ Both backends are removed. Notmuch may come back in a future release. 2. Run `himalaya -c ~/.config/himalaya/config.v2.toml account check` to validate the connection for each declared backend. 3. Once the new file passes the check, replace the v1 `config.toml` with it. 4. If you relied on keyring / OAuth, install [pimalaya/mimosa](https://github.com/pimalaya/mimosa) and/or [pimalaya/ortie](https://github.com/pimalaya/ortie) and wire them as `command = …` secrets. -5. If you relied on `write` / `reply` / `forward`, install [pimalaya/mml](https://github.com/pimalaya/mml) and declare it under `[message.composer.*]` / `[message.reader.*]`. +5. If you relied on the interactive `write` / `reply` / `forward`, install [pimalaya/mml](https://github.com/pimalaya/mml) and chain it into `himalaya messages send` / `messages add` via a tempfile or `>(...)` process substitution (see the README for ready-made `bash`/`zsh` snippets). diff --git a/README.md b/README.md index eb0b1056..0c6d662b 100644 --- a/README.md +++ b/README.md @@ -235,38 +235,18 @@ himalaya messages compose --from me@example.org --to you@example.org \ --subject "Hello" --body "Hi!" --send ``` -For richer composition (multipart MIME, MML directives, signing/encryption, editor-driven workflows…), wire a user-defined composer in `[message.composer.*]` and invoke it with the `-with` variants. Each entry declares one shell command per operation (`compose` for blank drafts and `mailto`, `reply` for replies, `forward` for forwards); the source message is piped on stdin for `reply` / `forward`. For example, with [mml](https://github.com/pimalaya/mml): +For richer composition (multipart MIME, MML directives, signing/encryption, editor-driven workflows), chain a standalone composer such as [mml](https://github.com/pimalaya/mml) into `messages send` / `messages add` through a tempfile or bash/zsh process substitution: -```toml -[message.composer.mml] -compose = "mml compose" -reply = "mml reply" -forward = "mml forward" -default = true +```sh +# Explicit tempfile, works in plain POSIX sh +mml compose /tmp/draft.eml && himalaya messages send /tmp/draft.eml + +# Bash / zsh process substitution, single command, no tempfile +mml compose >(himalaya messages send) +himalaya messages read 42 | mml reply >(himalaya messages send) ``` -``` -himalaya messages compose-with -himalaya messages reply-with -m INBOX 42 --send -himalaya messages forward-with -m INBOX 42 --send -himalaya messages mailto 'mailto:bob@example.org?subject=Hi&body=Hello' -``` - -`messages mailto ` parses an RFC 6068 `mailto:` URI (recipient list in the path, `to` / `cc` / `bcc` / `subject` / `body` query parameters), builds a draft RFC 5322 skeleton with those headers pre-filled, then pipes it on stdin to the named (or default) composer's `compose` command. The composer's output is routed through `--save` / `--send` like the other `-with` variants. Useful as a desktop `mailto:` handler. - -### Reading messages - -The built-in `messages read` command renders a message with himalaya's default formatter. For custom rendering, declare a reader in `[message.reader.*]` and call `read-with`: - -```toml -[message.reader.mml] -command = "mml read" -default = true -``` - -``` -himalaya messages read-with -m INBOX 42 -``` +The path-arg or process-substitution forms keep the composer's stdout connected to the terminal, so any `$EDITOR` it spawns sees a real tty. The bare-pipe form (`mml compose | himalaya messages send`) hangs because the editor inherits a pipe on its stdout. ### Re-using sessions diff --git a/config.sample.toml b/config.sample.toml index bc814053..aa58ddfe 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -120,32 +120,6 @@ #mailbox.alias.drafts = "[Gmail]/Drafts" #mailbox.alias.trash = "[Gmail]/Trash" -# -------------------------------------------------------------------------------- -# User-defined composers and readers -# -------------------------------------------------------------------------------- - -# Composers produce a MIME draft on stdout. They are invoked by the -# `messages compose-with` / `reply-with` / `forward-with` subcommands. -# Each entry declares one shell command per operation: `compose` (for blank -# drafts and `mailto`), `reply`, and `forward`. Stdin carries the source MIME -# bytes for `reply` / `forward` and is empty for `compose`; stderr is inherited -# so the composer can prompt the user. -# -# Pick one with `compose-with `, or let himalaya use the entry flagged -# `default = true` when no name is passed. -# -# Example using https://github.com/pimalaya/mml: -#message.composer.mml.compose = ["mml", "compose"] -#message.composer.mml.reply = ["mml", "reply"] -#message.composer.mml.forward = ["mml", "forward"] -#message.composer.mml.default = true - -# Readers consume a MIME message on stdin and emit human-readable bytes on -# stdout. They are invoked by `messages read-with`. -# -#message.reader.mml.command = ["mml", "read"] -#message.reader.mml.default = true - # -------------------------------------------------------------------------------- # Account config # -------------------------------------------------------------------------------- diff --git a/src/account/context.rs b/src/account/context.rs index 04c31fa8..b57f6644 100644 --- a/src/account/context.rs +++ b/src/account/context.rs @@ -30,14 +30,13 @@ use std::{collections::HashMap, env::temp_dir, path::PathBuf}; -use anyhow::{Result, anyhow}; use comfy_table::{Color as TableColor, ContentArrangement, presets}; use crossterm::style::Color; use dirs::download_dir; use crate::config::{ - AccountConfig, AttachmentListTableConfig, ComposerConfig, Config, EnvelopeListTableConfig, - MailboxListTableConfig, ReaderConfig, TableArrangementConfig, + AccountConfig, AttachmentListTableConfig, Config, EnvelopeListTableConfig, + MailboxListTableConfig, TableArrangementConfig, }; const DEFAULT_DATETIME_FMT: &str = "%F %R%:z"; @@ -70,29 +69,16 @@ pub struct Account { /// `mailbox.alias` at the global and account levels; account /// entries overwrite same-named global entries. pub mailbox_alias: HashMap, - - /// User-defined composers. Only sourced from the global - /// [`Config`]; account-level configs do not override these. - pub composer: HashMap, - /// User-defined readers. See [`Account::composer`]. - pub reader: HashMap, } impl Account { /// Folds `other`'s set fields on top of `self`. Each `Option` /// field is taken from `other` when `Some`, otherwise from - /// `self`. The composer/reader maps are extended (entries from - /// `other` overwrite same-named entries from `self`). + /// `self`. pub fn merge(self, other: Self) -> Self { let mut mailbox_alias = self.mailbox_alias; mailbox_alias.extend(other.mailbox_alias); - let mut composer = self.composer; - composer.extend(other.composer); - - let mut reader = self.reader; - reader.extend(other.reader); - Self { downloads_dir: other.downloads_dir.or(self.downloads_dir), table_preset: other.table_preset.or(self.table_preset), @@ -118,9 +104,6 @@ impl Account { ), mailbox_alias, - - composer, - reader, } } @@ -198,40 +181,6 @@ impl Account { .map(String::as_str) } - /// Gets a composer configuration. When `name` is given, looks up - /// the corresponding entry and bails if missing. - /// When `name` is `None`, returns the entry with `default = true`, - /// or bails with a hint if no default is set. - pub fn get_composer_mut(&mut self, name: Option<&str>) -> Result<&mut ComposerConfig> { - match name { - Some(name) => self - .composer - .get_mut(name) - .ok_or(anyhow!("no composer named `{name}` in [message.composer]")), - None => self - .composer - .values_mut() - .find(|c| c.default) - .ok_or(anyhow!( - "no composer specified and no default in [message.composer.*]; \ - pass a or set `default = true` on one entry" - )), - } - } - - pub fn get_reader_mut(&mut self, name: Option<&str>) -> Result<&mut ReaderConfig> { - match name { - Some(name) => self - .reader - .get_mut(name) - .ok_or(anyhow!("no reader named `{name}` in [message.reader]")), - None => self.reader.values_mut().find(|c| c.default).ok_or(anyhow!( - "no reader specified and no default in [message.reader.*]; \ - pass a or set `default = true` on one entry" - )), - } - } - // ── envelopes list — flag glyphs ───────────────────────────────────── pub fn envelopes_list_table_unseen_char(&self) -> char { @@ -429,9 +378,6 @@ impl From for Account { attachments_list_table: config.attachment.list.table, mailbox_alias: lowercase_alias_keys(config.mailbox.alias), - - composer: config.message.composer, - reader: config.message.reader, } } } @@ -452,9 +398,6 @@ impl From for Account { attachments_list_table: config.attachment.list.table, mailbox_alias: lowercase_alias_keys(config.mailbox.alias), - - composer: HashMap::new(), - reader: HashMap::new(), } } } diff --git a/src/cli.rs b/src/cli.rs index 265d391b..f5a2216d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -170,56 +170,56 @@ impl Command { // Self::Mailbox(cmd) => { let (config, account_config) = configs()?; - let client = EmailClient::new(config, account_config, backend)?; - cmd.execute(printer, client) + let (mut account, mut client) = EmailClient::new(config, account_config, backend)?; + cmd.execute(printer, &mut account, &mut client) } Self::Envelope(cmd) => { let (config, account_config) = configs()?; - let client = EmailClient::new(config, account_config, backend)?; - cmd.execute(printer, client) + let (mut account, mut client) = EmailClient::new(config, account_config, backend)?; + cmd.execute(printer, &mut account, &mut client) } Self::Flag(cmd) => { let (config, account_config) = configs()?; - let client = EmailClient::new(config, account_config, backend)?; - cmd.execute(printer, client) + let (mut account, mut client) = EmailClient::new(config, account_config, backend)?; + cmd.execute(printer, &mut account, &mut client) } Self::Message(cmd) => { let (config, account_config) = configs()?; - let client = EmailClient::new(config, account_config, backend)?; - cmd.execute(printer, client) + let (mut account, mut client) = EmailClient::new(config, account_config, backend)?; + cmd.execute(printer, &mut account, &mut client) } Self::Attachment(cmd) => { let (config, account_config) = configs()?; - let client = EmailClient::new(config, account_config, backend)?; - cmd.execute(printer, client) + let (mut account, mut client) = EmailClient::new(config, account_config, backend)?; + cmd.execute(printer, &mut account, &mut client) } // --- Protocol-specific APIs // #[cfg(feature = "imap")] Self::Imap(cmd) => { - let client = build_imap_client(config_paths, account_name)?; - cmd.execute(printer, client) + let (mut account, mut client) = build_imap_client(config_paths, account_name)?; + cmd.execute(printer, &mut account, &mut client) } #[cfg(feature = "jmap")] Self::Jmap(cmd) => { - let client = build_jmap_client(config_paths, account_name)?; - cmd.execute(printer, client) + let (mut account, mut client) = build_jmap_client(config_paths, account_name)?; + cmd.execute(printer, &mut account, &mut client) } #[cfg(feature = "maildir")] Self::Maildir(cmd) => { - let client = build_maildir_client(config_paths, account_name)?; - cmd.execute(printer, client) + let (mut account, mut client) = build_maildir_client(config_paths, account_name)?; + cmd.execute(printer, &mut account, &mut client) } #[cfg(feature = "m2dir")] Self::M2dir(cmd) => { - let client = build_m2dir_client(config_paths, account_name)?; - cmd.execute(printer, client) + let (mut account, mut client) = build_m2dir_client(config_paths, account_name)?; + cmd.execute(printer, &mut account, &mut client) } #[cfg(feature = "smtp")] Self::Smtp(cmd) => { - let client = build_smtp_client(config_paths, account_name)?; - cmd.execute(printer, client) + let (_account, mut client) = build_smtp_client(config_paths, account_name)?; + cmd.execute(printer, &mut client) } // --- Meta diff --git a/src/config.rs b/src/config.rs index a7c5c0d5..8eaa86a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,13 +15,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::{collections::HashMap, fs, path::Path, path::PathBuf, process::Command}; +use std::{collections::HashMap, fs, path::Path, path::PathBuf}; use anyhow::{Context, Result}; use comfy_table::ContentArrangement; use crossterm::style::Color; use pimalaya_config::{ - command, secret::Secret, toml::{TomlConfig, shell_expanded_string}, }; @@ -51,8 +50,6 @@ pub struct Config { #[serde(default)] pub mailbox: MailboxConfig, #[serde(default)] - pub message: MessageConfig, - #[serde(default)] pub attachment: AttachmentConfig, /// `account list` rendering options (global only — there is no /// per-account override for the listing of accounts). @@ -283,82 +280,6 @@ pub struct EnvelopeListTableConfig { pub size_color: Option, } -/// Message-level configuration: user-defined composers and readers. -/// -/// Composers produce a MIME draft on stdout (called by `compose-with`, -/// `reply-with`, `forward-with`). Readers consume a MIME message from -/// stdin and emit human-readable bytes on stdout (called by -/// `read-with`). Both are looked up by name; the entry flagged -/// `default = true` is used when no name is passed. -#[derive(Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct MessageConfig { - #[serde(default)] - pub composer: HashMap, - #[serde(default)] - pub reader: HashMap, -} - -/// Single composer entry under `[message.composer.]`. -/// -/// For all shell command strings defined below: -/// - The command is invoked via `sh -c`. -/// - stdin behavior varies by command as documented below. -/// - stdout is captured as the MIME draft. -/// - stderr is inherited so the composer can prompt the user. -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct ComposerConfig { - /// Command used to write a brand new message. - /// - /// This is invoked by the `compose-with` and `mailto` commands. - /// - /// - When invoked by `compose-with`, stdin is empty. - /// - When invoked by `mailto`, stdin is piped with a pre-filled RFC 5322 - /// draft skeleton built from the parsed RFC 6068 `mailto:` URI parameters - /// (such as to, cc, bcc, subject, and body). - #[serde(with = "command")] - pub compose: Command, - - /// Command used to reply to an existing message. - /// - /// This is invoked by the `reply-with` command. The original message's - /// MIME bytes are passed via stdin. - #[serde(with = "command")] - pub reply: Command, - - /// Command used to forward an existing message. - /// - /// This is invoked by the `forward-with` command. The original message's - /// MIME bytes are passed via stdin. - #[serde(with = "command")] - pub forward: Command, - - /// Marks this entry as the fallback when `compose-with` / - /// `reply-with` / `forward-with` are invoked without a name. - /// Exactly one composer should set this; if several do, the - /// first one returned by the config lookup wins. - #[serde(default)] - pub default: bool, -} - -/// Single reader entry under `[message.reader.]`. -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct ReaderConfig { - /// Shell command line invoked via `sh -c`. Stdin carries the - /// source MIME bytes; stdout is forwarded to the terminal (zero - /// bytes is fine — the reader may have spawned its own UI); - /// stderr is inherited. - #[serde(with = "command")] - pub command: Command, - - /// Marks this entry as the fallback when `read-with` is - /// invoked without a name. - #[serde(default)] - pub default: bool, -} - /// Global / per-account table rendering quirks shared across every list /// command (envelopes, mailboxes, attachments). The per-column color /// blocks live under `*.list.table.*-color` (see [`EnvelopeListTableConfig`] diff --git a/src/imap/cli.rs b/src/imap/cli.rs index 892f63d0..14d2a6f2 100644 --- a/src/imap/cli.rs +++ b/src/imap/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, envelope::cli::ImapEnvelopeCommand, flag::cli::ImapFlagCommand, id::ImapIdCommand, mailbox::cli::ImapMailboxCommand, message::cli::ImapMessageCommand, @@ -46,14 +47,19 @@ pub enum ImapCommand { } impl ImapCommand { - pub fn execute(self, printer: &mut impl Printer, client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { match self { - Self::Id(cmd) => cmd.execute(printer, client), + Self::Id(cmd) => cmd.execute(printer, account, client), - Self::Envelopes(cmd) => cmd.execute(printer, client), - Self::Flags(cmd) => cmd.execute(printer, client), - Self::Mailboxes(cmd) => cmd.execute(printer, client), - Self::Messages(cmd) => cmd.execute(printer, client), + Self::Envelopes(cmd) => cmd.execute(printer, account, client), + Self::Flags(cmd) => cmd.execute(printer, account, client), + Self::Mailboxes(cmd) => cmd.execute(printer, account, client), + Self::Messages(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/imap/client.rs b/src/imap/client.rs index df406ca0..7a73dc71 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -15,12 +15,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Himalaya wrapper around [`io_imap::client::ImapClientStd`] that -//! bundles the merged [`Account`] alongside the live IMAP client. +//! Himalaya wrapper around [`io_imap::client::ImapClientStd`]. //! //! This is what every IMAP-specific subcommand receives: the dispatch //! layer (`crate::cli`) opens the session up front via -//! [`build_imap_client`] and hands the ready-to-use wrapper down. +//! [`build_imap_client`] and hands the ready-to-use wrapper down, +//! together with the merged [`Account`] as a sibling argument. use std::{ ops::{Deref, DerefMut}, @@ -40,23 +40,21 @@ use crate::{ pub struct ImapClient { inner: Inner, - pub account: Account, } impl ImapClient { - /// Opens the IMAP connection (TCP/TLS/STARTTLS, greeting, SASL) - /// then wraps the resulting client alongside `account`. The - /// capability list reported by the connect handshake is discarded; - /// IMAP-specific subcommands that need it should call + /// Opens the IMAP connection (TCP/TLS/STARTTLS, greeting, SASL). + /// The capability list reported by the connect handshake is + /// discarded; IMAP-specific subcommands that need it should call /// [`Inner::capability`] explicitly. - pub fn new(config: ImapConfig, account: Account) -> Result { + pub fn new(config: ImapConfig) -> Result { let mut tls: Tls = config.tls.into(); tls.rustls.alpn = vec!["imap".into()]; let sasl: Option = config.sasl.map(Sasl::try_from).transpose()?; let auto_id = resolve_auto_id_params(&config.id)?; let server = parse_imap_server(&config.server)?; let (inner, _capability) = Inner::connect(&server, &tls, config.starttls, sasl, auto_id)?; - Ok(Self { inner, account }) + Ok(Self { inner }) } } @@ -92,11 +90,13 @@ impl DerefMut for ImapClient { /// Loads the configuration, picks the active account, builds the /// merged [`Account`] then opens the IMAP session. Bails when the -/// account has no `[imap]` block. +/// account has no `[imap]` block. Returns the live client paired +/// with the merged account so subcommands receive both as sibling +/// arguments. pub fn build_imap_client( config_paths: &[PathBuf], account_name: Option<&str>, -) -> Result { +) -> Result<(Account, ImapClient)> { let mut config = load_or_wizard(config_paths)?; let (name, mut ac) = config .take_account(account_name)? @@ -106,5 +106,6 @@ pub fn build_imap_client( .take() .ok_or_else(|| anyhow!("IMAP config is missing for account `{name}`"))?; let account = Account::from(config).merge(Account::from(ac)); - ImapClient::new(imap_config, account) + let client = ImapClient::new(imap_config)?; + Ok((account, client)) } diff --git a/src/imap/envelope/cli.rs b/src/imap/envelope/cli.rs index a0281bb1..a897e69f 100644 --- a/src/imap/envelope/cli.rs +++ b/src/imap/envelope/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, envelope::{ @@ -43,12 +44,17 @@ pub enum ImapEnvelopeCommand { } impl ImapEnvelopeCommand { - pub fn execute(self, printer: &mut impl Printer, client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), - Self::List(cmd) => cmd.execute(printer, client), - Self::Search(cmd) => cmd.execute(printer, client), - Self::Sort(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), + Self::List(cmd) => cmd.execute(printer, account, client), + Self::Search(cmd) => cmd.execute(printer, account, client), + Self::Sort(cmd) => cmd.execute(printer, account, client), Self::Thread(cmd) => cmd.execute(printer, client), } } diff --git a/src/imap/envelope/get.rs b/src/imap/envelope/get.rs index e8742c61..a39a14d2 100644 --- a/src/imap/envelope/get.rs +++ b/src/imap/envelope/get.rs @@ -27,6 +27,7 @@ use io_imap::types::{ use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, envelope::list::{decode_mime, format_address}, @@ -54,7 +55,12 @@ pub struct ImapEnvelopeGetCommand { } impl ImapEnvelopeGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { @@ -76,7 +82,7 @@ impl ImapEnvelopeGetCommand { }; let table = EnvelopeTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), envelope: items.into(), }; diff --git a/src/imap/envelope/list.rs b/src/imap/envelope/list.rs index a0173153..bab3d93a 100644 --- a/src/imap/envelope/list.rs +++ b/src/imap/envelope/list.rs @@ -32,6 +32,7 @@ use pimalaya_cli::printer::Printer; use rfc2047_decoder::{Decoder, RecoverStrategy}; use serde::Serialize; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag}, @@ -67,7 +68,12 @@ pub struct ImapEnvelopeListCommand { } impl ImapEnvelopeListCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; let exists = if self.mailbox_no_select.inner { @@ -100,13 +106,13 @@ impl ImapEnvelopeListCommand { let data = client.fetch(sequence_set, item_names, !self.sequence && has_sequence)?; let table = EnvelopesTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), colors: EnvelopeColors { - id: client.account.envelopes_list_table_id_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - date: client.account.envelopes_list_table_date_color(), + id: account.envelopes_list_table_id_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + date: account.envelopes_list_table_date_color(), }, envelopes: map_envelopes_table_entries(data), }; diff --git a/src/imap/envelope/search.rs b/src/imap/envelope/search.rs index a74ec7c1..ae73c73c 100644 --- a/src/imap/envelope/search.rs +++ b/src/imap/envelope/search.rs @@ -28,6 +28,7 @@ use io_imap::types::{ use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag}, @@ -75,7 +76,12 @@ pub struct ImapEnvelopeSearchCommand { } impl ImapEnvelopeSearchCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { @@ -86,9 +92,9 @@ impl ImapEnvelopeSearchCommand { let ids = client.search(criteria, !self.seq)?; let table = SearchTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), - id_color: client.account.envelopes_list_table_id_color(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), + id_color: account.envelopes_list_table_id_color(), ids: ids .into_iter() .map(|id| SearchResult { id: id.get() }) diff --git a/src/imap/envelope/sort.rs b/src/imap/envelope/sort.rs index 4b74a6b3..b121081a 100644 --- a/src/imap/envelope/sort.rs +++ b/src/imap/envelope/sort.rs @@ -27,6 +27,7 @@ use io_imap::types::{ use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, envelope::search::parse_query, mailbox::arg::MailboxNameOptionalArg, }; @@ -68,7 +69,12 @@ pub struct ImapEnvelopeSortCommand { } impl ImapEnvelopeSortCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.select(mailbox)?; @@ -82,7 +88,7 @@ impl ImapEnvelopeSortCommand { let ids = client.sort(sort_criteria, search_criteria, !self.seq)?; - let id_color = client.account.envelopes_list_table_id_color(); + let id_color = account.envelopes_list_table_id_color(); let table = SortResultsTable::new(ids, !self.seq, id_color); printer.out(table)?; diff --git a/src/imap/envelope/thread.rs b/src/imap/envelope/thread.rs index 519acffc..71c657ed 100644 --- a/src/imap/envelope/thread.rs +++ b/src/imap/envelope/thread.rs @@ -62,7 +62,7 @@ pub struct ImapEnvelopeThreadCommand { } impl ImapEnvelopeThreadCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { @@ -76,7 +76,7 @@ impl ImapEnvelopeThreadCommand { let all_ids = collect_thread_ids(&threads); let subjects = if !all_ids.is_empty() { - fetch_subjects(&mut client, &all_ids, !self.seq)? + fetch_subjects(client, &all_ids, !self.seq)? } else { HashMap::new() }; diff --git a/src/imap/flag/add.rs b/src/imap/flag/add.rs index a6f8c266..ab2cd83f 100644 --- a/src/imap/flag/add.rs +++ b/src/imap/flag/add.rs @@ -52,7 +52,7 @@ pub struct ImapFlagAddCommand { } impl ImapFlagAddCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/flag/cli.rs b/src/imap/flag/cli.rs index 516165be..eb3711b5 100644 --- a/src/imap/flag/cli.rs +++ b/src/imap/flag/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, flag::{ @@ -40,9 +41,14 @@ pub enum ImapFlagCommand { } impl ImapFlagCommand { - pub fn execute(self, printer: &mut impl Printer, client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), Self::Add(cmd) => cmd.execute(printer, client), Self::Set(cmd) => cmd.execute(printer, client), Self::Remove(cmd) => cmd.execute(printer, client), diff --git a/src/imap/flag/list.rs b/src/imap/flag/list.rs index f2f4d9a9..98b27329 100644 --- a/src/imap/flag/list.rs +++ b/src/imap/flag/list.rs @@ -24,6 +24,7 @@ use io_imap::types::flag::{Flag, FlagPerm}; use pimalaya_cli::printer::Printer; use serde::{Serialize, Serializer}; +use crate::account::context::Account; use crate::imap::{client::ImapClient, mailbox::arg::MailboxNameArg}; /// List available IMAP flags for the given mailbox. @@ -38,7 +39,12 @@ pub struct ImapFlagListCommand { } impl ImapFlagListCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; let data = client.select(mailbox)?; @@ -46,8 +52,8 @@ impl ImapFlagListCommand { let permanent_flags = data.permanent_flags.unwrap_or_default(); let table = FlagsTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), flags, permanent_flags, }; diff --git a/src/imap/flag/remove.rs b/src/imap/flag/remove.rs index f36bde9b..cf4adf56 100644 --- a/src/imap/flag/remove.rs +++ b/src/imap/flag/remove.rs @@ -52,7 +52,7 @@ pub struct ImapFlagRemoveCommand { } impl ImapFlagRemoveCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/flag/set.rs b/src/imap/flag/set.rs index 8f37cee7..97224dfd 100644 --- a/src/imap/flag/set.rs +++ b/src/imap/flag/set.rs @@ -52,7 +52,7 @@ pub struct ImapFlagSetCommand { } impl ImapFlagSetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/id.rs b/src/imap/id.rs index 1307768a..d62aac30 100644 --- a/src/imap/id.rs +++ b/src/imap/id.rs @@ -27,6 +27,7 @@ use io_imap::types::{ use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::{config::ImapIdConfig, imap::client::ImapClient}; /// Get information about the IMAP server. @@ -44,7 +45,12 @@ pub struct ImapIdCommand { } impl ImapIdCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mut params: HashMap, NString<'static>> = HashMap::new(); for key in ["name", "version", "vendor", "support-url"] { let (k, v) = build_canned_pair(key)?; @@ -58,7 +64,7 @@ impl ImapIdCommand { let params = client.id(Some(params.into_iter().collect()))?; let table = ServerIdTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), server_id: params .unwrap_or_default() .into_iter() diff --git a/src/imap/mailbox/cli.rs b/src/imap/mailbox/cli.rs index 6233d1e1..ce0ff773 100644 --- a/src/imap/mailbox/cli.rs +++ b/src/imap/mailbox/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, mailbox::{ @@ -55,17 +56,22 @@ pub enum ImapMailboxCommand { } impl ImapMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { match self { Self::Close(cmd) => cmd.execute(printer, client), Self::Create(cmd) => cmd.execute(printer, client), Self::Delete(cmd) => cmd.execute(printer, client), Self::Expunge(cmd) => cmd.execute(printer, client), - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), Self::Purge(cmd) => cmd.execute(printer, client), Self::Rename(cmd) => cmd.execute(printer, client), Self::Select(cmd) => cmd.execute(printer, client), - Self::Status(cmd) => cmd.execute(printer, client), + Self::Status(cmd) => cmd.execute(printer, account, client), Self::Subscribe(cmd) => cmd.execute(printer, client), Self::Unselect(cmd) => cmd.execute(printer, client), Self::Unsubscribe(cmd) => cmd.execute(printer, client), diff --git a/src/imap/mailbox/close.rs b/src/imap/mailbox/close.rs index a1ef5b09..2dc5ab0f 100644 --- a/src/imap/mailbox/close.rs +++ b/src/imap/mailbox/close.rs @@ -34,7 +34,7 @@ use crate::imap::client::ImapClient; pub struct ImapMailboxCloseCommand; impl ImapMailboxCloseCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { client.close()?; printer.out(Message::new("Mailbox successfully closed")) } diff --git a/src/imap/mailbox/create.rs b/src/imap/mailbox/create.rs index 3c7b58f3..e8015a7d 100644 --- a/src/imap/mailbox/create.rs +++ b/src/imap/mailbox/create.rs @@ -32,7 +32,7 @@ pub struct ImapMailboxCreateCommand { } impl ImapMailboxCreateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.create(mailbox)?; printer.out(Message::new("Mailbox successfully created")) diff --git a/src/imap/mailbox/delete.rs b/src/imap/mailbox/delete.rs index 9a776316..4a65f454 100644 --- a/src/imap/mailbox/delete.rs +++ b/src/imap/mailbox/delete.rs @@ -32,7 +32,7 @@ pub struct ImapMailboxDeleteCommand { } impl ImapMailboxDeleteCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.delete(mailbox)?; printer.out(Message::new("Mailbox successfully deleted")) diff --git a/src/imap/mailbox/expunge.rs b/src/imap/mailbox/expunge.rs index b67dad99..b548d994 100644 --- a/src/imap/mailbox/expunge.rs +++ b/src/imap/mailbox/expunge.rs @@ -37,7 +37,7 @@ pub struct ImapMailboxExpungeCommand { } impl ImapMailboxExpungeCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/mailbox/list.rs b/src/imap/mailbox/list.rs index 5569b709..a6c4420e 100644 --- a/src/imap/mailbox/list.rs +++ b/src/imap/mailbox/list.rs @@ -25,6 +25,7 @@ use io_imap::types::{core::QuotedChar, flag::FlagNameAttribute, mailbox::Mailbox use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::imap::client::ImapClient; /// List, search and filter mailboxes. @@ -48,7 +49,12 @@ pub struct ImapMailboxListCommand { } impl ImapMailboxListCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let reference = self.reference.try_into()?; let pattern = self.pattern.try_into()?; @@ -59,8 +65,8 @@ impl ImapMailboxListCommand { }; let table = MailboxesTable { - preset: client.account.table_preset().to_string(), - name_color: client.account.mailboxes_list_table_name_color(), + preset: account.table_preset().to_string(), + name_color: account.mailboxes_list_table_name_color(), mailboxes: mailboxes.into_iter().map(From::from).collect(), }; diff --git a/src/imap/mailbox/purge.rs b/src/imap/mailbox/purge.rs index d07e7483..44353b24 100644 --- a/src/imap/mailbox/purge.rs +++ b/src/imap/mailbox/purge.rs @@ -39,7 +39,7 @@ pub struct ImapMailboxPurgeCommand { } impl ImapMailboxPurgeCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/mailbox/rename.rs b/src/imap/mailbox/rename.rs index 94715e20..66088323 100644 --- a/src/imap/mailbox/rename.rs +++ b/src/imap/mailbox/rename.rs @@ -36,7 +36,7 @@ pub struct ImapMailboxRenameCommand { } impl ImapMailboxRenameCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let from = self.mailbox_source_name.inner.try_into()?; let to = self.mailbox_dest_name.inner.try_into()?; client.rename(from, to)?; diff --git a/src/imap/mailbox/select.rs b/src/imap/mailbox/select.rs index ed7c9f44..1fd9af6e 100644 --- a/src/imap/mailbox/select.rs +++ b/src/imap/mailbox/select.rs @@ -36,7 +36,7 @@ pub struct ImapMailboxSelectCommand { } impl ImapMailboxSelectCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.select(mailbox)?; printer.out(Message::new("Mailbox successfully selected")) diff --git a/src/imap/mailbox/status.rs b/src/imap/mailbox/status.rs index 10a87f22..1b1d0d7e 100644 --- a/src/imap/mailbox/status.rs +++ b/src/imap/mailbox/status.rs @@ -24,6 +24,7 @@ use io_imap::types::status::{StatusDataItem, StatusDataItemName}; use pimalaya_cli::printer::Printer; use serde::{Serialize, Serializer}; +use crate::account::context::Account; use crate::imap::{client::ImapClient, mailbox::arg::MailboxNameArg}; /// Get the status of the given mailbox. @@ -37,7 +38,12 @@ pub struct ImapMailboxStatusCommand { } impl ImapMailboxStatusCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; let item_names = vec![ StatusDataItemName::Messages, @@ -50,7 +56,7 @@ impl ImapMailboxStatusCommand { let items = client.status(mailbox, item_names)?; let table = MailboxStatusTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), status: items.into(), }; diff --git a/src/imap/mailbox/subscribe.rs b/src/imap/mailbox/subscribe.rs index 2dac09e3..bc764eb9 100644 --- a/src/imap/mailbox/subscribe.rs +++ b/src/imap/mailbox/subscribe.rs @@ -32,7 +32,7 @@ pub struct ImapMailboxSubscribeCommand { } impl ImapMailboxSubscribeCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.subscribe(mailbox)?; printer.out(Message::new("Mailbox successfully subscribed")) diff --git a/src/imap/mailbox/unselect.rs b/src/imap/mailbox/unselect.rs index de86e7d4..4b6c5872 100644 --- a/src/imap/mailbox/unselect.rs +++ b/src/imap/mailbox/unselect.rs @@ -33,7 +33,7 @@ use crate::imap::client::ImapClient; pub struct ImapMailboxUnselectCommand; impl ImapMailboxUnselectCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { client.unselect()?; printer.out(Message::new("Mailbox successfully unselected")) } diff --git a/src/imap/mailbox/unsubscribe.rs b/src/imap/mailbox/unsubscribe.rs index 6efd024b..9876b978 100644 --- a/src/imap/mailbox/unsubscribe.rs +++ b/src/imap/mailbox/unsubscribe.rs @@ -32,7 +32,7 @@ pub struct ImapMailboxUnsubscribeCommand { } impl ImapMailboxUnsubscribeCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.unsubscribe(mailbox)?; printer.out(Message::new("Mailbox successfully unsubscribed")) diff --git a/src/imap/message/cli.rs b/src/imap/message/cli.rs index b87015ca..de1b66bb 100644 --- a/src/imap/message/cli.rs +++ b/src/imap/message/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::imap::{ client::ImapClient, message::{ @@ -43,12 +44,17 @@ pub enum ImapMessageCommand { } impl ImapMessageCommand { - pub fn execute(self, printer: &mut impl Printer, client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { match self { Self::Save(cmd) => cmd.execute(printer, client), Self::Get(cmd) => cmd.execute(printer, client), Self::Read(cmd) => cmd.execute(printer, client), - Self::Export(cmd) => cmd.execute(printer, client), + Self::Export(cmd) => cmd.execute(printer, account, client), Self::Copy(cmd) => cmd.execute(printer, client), Self::Move(cmd) => cmd.execute(printer, client), } diff --git a/src/imap/message/copy.rs b/src/imap/message/copy.rs index 0d579abe..911a9847 100644 --- a/src/imap/message/copy.rs +++ b/src/imap/message/copy.rs @@ -48,7 +48,7 @@ pub struct ImapMessageCopyCommand { } impl ImapMessageCopyCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/message/export.rs b/src/imap/message/export.rs index c4842320..55420828 100644 --- a/src/imap/message/export.rs +++ b/src/imap/message/export.rs @@ -27,6 +27,7 @@ use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, Messag use mail_parser::{MessageParser, MimeHeaders}; use pimalaya_cli::printer::{Message, Printer}; +use crate::account::context::Account; use crate::imap::{client::ImapClient, mailbox::arg::MailboxNameOptionalFlag}; /// Export type for message export. @@ -73,7 +74,12 @@ pub struct ImapMessageExportCommand { } impl ImapMessageExportCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut ImapClient, + ) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; client.select(mailbox)?; @@ -133,9 +139,7 @@ impl ImapMessageExportCommand { // Generate filename from subject or message-id let filename = generate_eml_filename(&message, self.id); - let dir = self - .directory - .unwrap_or_else(|| client.account.downloads_dir()); + let dir = self.directory.unwrap_or_else(|| account.downloads_dir()); if !dir.exists() { fs::create_dir_all(&dir)?; diff --git a/src/imap/message/get.rs b/src/imap/message/get.rs index 3ba23745..952c31d2 100644 --- a/src/imap/message/get.rs +++ b/src/imap/message/get.rs @@ -49,7 +49,7 @@ pub struct ImapMessageGetCommand { } impl ImapMessageGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if self.id == 0 { bail!("ID must be non-zero"); diff --git a/src/imap/message/move.rs b/src/imap/message/move.rs index 2048eb9c..1fd86a48 100644 --- a/src/imap/message/move.rs +++ b/src/imap/message/move.rs @@ -49,7 +49,7 @@ pub struct ImapMessageMoveCommand { } impl ImapMessageMoveCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/message/read.rs b/src/imap/message/read.rs index 62f4b6db..a37b1b96 100644 --- a/src/imap/message/read.rs +++ b/src/imap/message/read.rs @@ -54,7 +54,7 @@ pub struct ImapMessageReadCommand { } impl ImapMessageReadCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox = self.mailbox_name.inner.try_into()?; if !self.mailbox_no_select.inner { diff --git a/src/imap/message/save.rs b/src/imap/message/save.rs index 6baf5fe9..3087ecee 100644 --- a/src/imap/message/save.rs +++ b/src/imap/message/save.rs @@ -46,7 +46,7 @@ pub struct ImapMessageSaveCommand { } impl ImapMessageSaveCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: ImapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> { let mailbox: Mailbox<'static> = self.mailbox.inner.try_into()?; let message = self.message.parse()?; let message = Literal::try_from(message)?; diff --git a/src/jmap/cli.rs b/src/jmap/cli.rs index db3af201..8bbf40ed 100644 --- a/src/jmap/cli.rs +++ b/src/jmap/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, email::cli::JmapEmailCommand, identity::cli::JmapIdentityCommand, mailbox::cli::JmapMailboxCommand, query::JmapQueryCommand, @@ -57,15 +58,20 @@ pub enum JmapCommand { } impl JmapCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Mailboxes(cmd) => cmd.execute(printer, client), - Self::Emails(cmd) => cmd.execute(printer, client), + Self::Mailboxes(cmd) => cmd.execute(printer, account, client), + Self::Emails(cmd) => cmd.execute(printer, account, client), - Self::Threads(cmd) => cmd.execute(printer, client), - Self::Identity(cmd) => cmd.execute(printer, client), - Self::Submission(cmd) => cmd.execute(printer, client), - Self::Vacation(cmd) => cmd.execute(printer, client), + Self::Threads(cmd) => cmd.execute(printer, account, client), + Self::Identity(cmd) => cmd.execute(printer, account, client), + Self::Submission(cmd) => cmd.execute(printer, account, client), + Self::Vacation(cmd) => cmd.execute(printer, account, client), Self::Query(cmd) => cmd.execute(printer, client), } } diff --git a/src/jmap/client.rs b/src/jmap/client.rs index 4c1bbf81..b76d62a4 100644 --- a/src/jmap/client.rs +++ b/src/jmap/client.rs @@ -43,7 +43,6 @@ use crate::{ pub struct JmapClient { inner: Inner, - pub account: Account, /// The original JMAP config block, kept around so commands like /// `email import` / `email export` can spin up their own /// auxiliary sessions (e.g. against the upload/download URL when @@ -53,9 +52,8 @@ pub struct JmapClient { impl JmapClient { /// Establishes the JMAP session (TLS, `/.well-known/jmap` - /// discovery) then wraps the resulting client alongside - /// `account`. - pub fn new(config: JmapConfig, account: Account) -> Result { + /// discovery). + pub fn new(config: JmapConfig) -> Result { let mut tls: Tls = config.tls.clone().into(); tls.rustls.alpn = vec!["http/1.1".into()]; @@ -65,11 +63,7 @@ impl JmapClient { let mut inner = Inner::connect(&url, &tls, http_auth)?; inner.session_get(&url)?; - Ok(Self { - inner, - account, - config, - }) + Ok(Self { inner, config }) } } @@ -89,11 +83,13 @@ impl DerefMut for JmapClient { /// Loads the configuration, picks the active account, builds the /// merged [`Account`] then opens the JMAP session. Bails when the -/// account has no `[jmap]` block. +/// account has no `[jmap]` block. Returns the live client paired +/// with the merged account so subcommands receive both as sibling +/// arguments. pub fn build_jmap_client( config_paths: &[PathBuf], account_name: Option<&str>, -) -> Result { +) -> Result<(Account, JmapClient)> { let mut config = load_or_wizard(config_paths)?; let (name, mut ac) = config .take_account(account_name)? @@ -103,7 +99,8 @@ pub fn build_jmap_client( .take() .ok_or_else(|| anyhow!("JMAP config is missing for account `{name}`"))?; let account = Account::from(config).merge(Account::from(ac)); - JmapClient::new(jmap_config, account) + let client = JmapClient::new(jmap_config)?; + Ok((account, client)) } /// Parses the JMAP `server` field into a [`Url`], defaulting bare diff --git a/src/jmap/email/cli.rs b/src/jmap/email/cli.rs index 3497e93a..d472a1f6 100644 --- a/src/jmap/email/cli.rs +++ b/src/jmap/email/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, email::{ @@ -47,10 +48,15 @@ pub enum JmapEmailCommand { } impl JmapEmailCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), - Self::Query(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), + Self::Query(cmd) => cmd.execute(printer, account, client), Self::Read(cmd) => cmd.execute(printer, client), Self::Update(cmd) => cmd.execute(printer, client), Self::Delete(cmd) => cmd.execute(printer, client), diff --git a/src/jmap/email/copy.rs b/src/jmap/email/copy.rs index 8b0947b5..00510b0e 100644 --- a/src/jmap/email/copy.rs +++ b/src/jmap/email/copy.rs @@ -41,7 +41,7 @@ pub struct JmapEmailCopyCommand { } impl JmapEmailCopyCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let mailbox_ids: BTreeMap = self.mailbox_id.into_iter().map(|m| (m, true)).collect(); diff --git a/src/jmap/email/delete.rs b/src/jmap/email/delete.rs index e1da85b2..833cbf81 100644 --- a/src/jmap/email/delete.rs +++ b/src/jmap/email/delete.rs @@ -31,7 +31,7 @@ pub struct JmapEmailDestroyCommand { } impl JmapEmailDestroyCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let mut args = JmapEmailSetArgs::default(); for id in self.ids { diff --git a/src/jmap/email/export.rs b/src/jmap/email/export.rs index 7ae7f67a..789eeacb 100644 --- a/src/jmap/email/export.rs +++ b/src/jmap/email/export.rs @@ -38,7 +38,7 @@ pub struct JmapEmailExportCommand { } impl JmapEmailExportCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let properties = Some(vec![EmailProperty::Id, EmailProperty::BlobId]); let output = client.email_get(vec![self.id.clone()], properties, false, false, 0)?; diff --git a/src/jmap/email/get.rs b/src/jmap/email/get.rs index f5b13127..57552630 100644 --- a/src/jmap/email/get.rs +++ b/src/jmap/email/get.rs @@ -20,6 +20,7 @@ use clap::Parser; use log::warn; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, email::query::{EmailsChars, EmailsColors, EmailsTable}, @@ -36,7 +37,12 @@ pub struct JmapEmailGetCommand { } impl JmapEmailGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let output = client.email_get(self.ids.clone(), None, false, false, 0)?; for id in output.not_found { @@ -44,19 +50,19 @@ impl JmapEmailGetCommand { } let table = EmailsTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), colors: EmailsColors { - id: client.account.envelopes_list_table_id_color(), - flags: client.account.envelopes_list_table_flags_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - date: client.account.envelopes_list_table_date_color(), + id: account.envelopes_list_table_id_color(), + flags: account.envelopes_list_table_flags_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + date: account.envelopes_list_table_date_color(), }, chars: EmailsChars { - unseen: client.account.envelopes_list_table_unseen_char(), - flagged: client.account.envelopes_list_table_flagged_char(), - attachment: client.account.envelopes_list_table_attachment_char(), + unseen: account.envelopes_list_table_unseen_char(), + flagged: account.envelopes_list_table_flagged_char(), + attachment: account.envelopes_list_table_attachment_char(), }, emails: output.emails, }; diff --git a/src/jmap/email/import.rs b/src/jmap/email/import.rs index dcdde2b6..87be388c 100644 --- a/src/jmap/email/import.rs +++ b/src/jmap/email/import.rs @@ -64,7 +64,7 @@ pub struct JmapEmailImportCommand { } impl JmapEmailImportCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let data = self.message.parse()?.into_bytes(); let session = client.session().expect("session loaded by new_jmap_client"); diff --git a/src/jmap/email/parse.rs b/src/jmap/email/parse.rs index d62bedfa..29b6b6e3 100644 --- a/src/jmap/email/parse.rs +++ b/src/jmap/email/parse.rs @@ -35,7 +35,7 @@ pub struct JmapEmailParseCommand { } impl JmapEmailParseCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let output = client.email_parse(self.blob_ids.clone(), None)?; for id in output.not_found { diff --git a/src/jmap/email/query.rs b/src/jmap/email/query.rs index 1efb8564..ebdd9aa7 100644 --- a/src/jmap/email/query.rs +++ b/src/jmap/email/query.rs @@ -27,6 +27,7 @@ use io_jmap::{ use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::jmap::client::JmapClient; /// Query JMAP emails (Email/query + Email/get). @@ -104,7 +105,12 @@ pub struct JmapEmailQueryCommand { } impl JmapEmailQueryCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let filter = { let f = EmailFilter { in_mailbox: self.mailbox, @@ -164,19 +170,19 @@ impl JmapEmailQueryCommand { )?; let table = EmailsTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), colors: EmailsColors { - id: client.account.envelopes_list_table_id_color(), - flags: client.account.envelopes_list_table_flags_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - date: client.account.envelopes_list_table_date_color(), + id: account.envelopes_list_table_id_color(), + flags: account.envelopes_list_table_flags_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + date: account.envelopes_list_table_date_color(), }, chars: EmailsChars { - unseen: client.account.envelopes_list_table_unseen_char(), - flagged: client.account.envelopes_list_table_flagged_char(), - attachment: client.account.envelopes_list_table_attachment_char(), + unseen: account.envelopes_list_table_unseen_char(), + flagged: account.envelopes_list_table_flagged_char(), + attachment: account.envelopes_list_table_attachment_char(), }, emails: output.emails, }; diff --git a/src/jmap/email/read.rs b/src/jmap/email/read.rs index a969f51c..468176c6 100644 --- a/src/jmap/email/read.rs +++ b/src/jmap/email/read.rs @@ -38,7 +38,7 @@ pub struct JmapEmailReadCommand { } impl JmapEmailReadCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let output = client.email_get(self.ids.clone(), None, !self.html, self.html, 0)?; for id in output.not_found { diff --git a/src/jmap/email/update.rs b/src/jmap/email/update.rs index d8c9b2e9..511d8832 100644 --- a/src/jmap/email/update.rs +++ b/src/jmap/email/update.rs @@ -57,7 +57,7 @@ pub struct JmapEmailUpdateCommand { } impl JmapEmailUpdateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let mut args = JmapEmailSetArgs::default(); for id in &self.ids { diff --git a/src/jmap/identity/cli.rs b/src/jmap/identity/cli.rs index a444c498..f2f8f8d7 100644 --- a/src/jmap/identity/cli.rs +++ b/src/jmap/identity/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, identity::{ @@ -45,9 +46,14 @@ pub enum JmapIdentityCommand { } impl JmapIdentityCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), Self::Create(cmd) => cmd.execute(printer, client), Self::Update(cmd) => cmd.execute(printer, client), Self::Delete(cmd) => cmd.execute(printer, client), diff --git a/src/jmap/identity/create.rs b/src/jmap/identity/create.rs index 8dadc302..aca85b18 100644 --- a/src/jmap/identity/create.rs +++ b/src/jmap/identity/create.rs @@ -41,7 +41,7 @@ pub struct JmapIdentityCreateCommand { } impl JmapIdentityCreateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let identity = IdentityCreate { name: self.name.clone(), email: self.email.clone(), diff --git a/src/jmap/identity/delete.rs b/src/jmap/identity/delete.rs index 9749e638..8ab7b1b0 100644 --- a/src/jmap/identity/delete.rs +++ b/src/jmap/identity/delete.rs @@ -31,7 +31,7 @@ pub struct JmapIdentityDeleteCommand { } impl JmapIdentityDeleteCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let mut args = JmapIdentitySetArgs::default(); for id in self.ids { diff --git a/src/jmap/identity/get.rs b/src/jmap/identity/get.rs index 6911f671..f7e88ac3 100644 --- a/src/jmap/identity/get.rs +++ b/src/jmap/identity/get.rs @@ -25,6 +25,7 @@ use log::warn; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::jmap::client::JmapClient; /// Get JMAP identities (Identity/get). @@ -39,7 +40,12 @@ pub struct JmapIdentityGetCommand { } impl JmapIdentityGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let output = client.identity_get(self.ids)?; for id in output.not_found { @@ -47,7 +53,7 @@ impl JmapIdentityGetCommand { } let table = IdentitiesTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), identities: output.identities, }; diff --git a/src/jmap/identity/update.rs b/src/jmap/identity/update.rs index 0ba4e8b0..da7ccf37 100644 --- a/src/jmap/identity/update.rs +++ b/src/jmap/identity/update.rs @@ -42,7 +42,7 @@ pub struct JmapIdentityUpdateCommand { } impl JmapIdentityUpdateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let patch = IdentityUpdate { name: self.name, reply_to: None, diff --git a/src/jmap/mailbox/cli.rs b/src/jmap/mailbox/cli.rs index 5e2fab93..f15e5f6a 100644 --- a/src/jmap/mailbox/cli.rs +++ b/src/jmap/mailbox/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, mailbox::{ @@ -41,10 +42,15 @@ pub enum JmapMailboxCommand { } impl JmapMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), - Self::Query(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), + Self::Query(cmd) => cmd.execute(printer, account, client), Self::Create(cmd) => cmd.execute(printer, client), Self::Update(cmd) => cmd.execute(printer, client), Self::Destroy(cmd) => cmd.execute(printer, client), diff --git a/src/jmap/mailbox/create.rs b/src/jmap/mailbox/create.rs index 1e457c4e..63348245 100644 --- a/src/jmap/mailbox/create.rs +++ b/src/jmap/mailbox/create.rs @@ -42,7 +42,7 @@ pub struct JmapMailboxCreateCommand { } impl JmapMailboxCreateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let new_mailbox = MailboxCreate { name: Some(self.name.clone()), parent_id: self.parent_id, diff --git a/src/jmap/mailbox/destroy.rs b/src/jmap/mailbox/destroy.rs index 9097e163..df439225 100644 --- a/src/jmap/mailbox/destroy.rs +++ b/src/jmap/mailbox/destroy.rs @@ -35,7 +35,7 @@ pub struct JmapMailboxDestroyCommand { } impl JmapMailboxDestroyCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let args = JmapMailboxSetArgs { destroy: Some(self.ids.clone()), on_destroy_remove_emails: if self.purge { Some(true) } else { None }, diff --git a/src/jmap/mailbox/get.rs b/src/jmap/mailbox/get.rs index 07430421..98a07525 100644 --- a/src/jmap/mailbox/get.rs +++ b/src/jmap/mailbox/get.rs @@ -20,6 +20,7 @@ use clap::Parser; use log::warn; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, mailbox::query::{MailboxColors, MailboxesTable}, @@ -34,7 +35,12 @@ pub struct JmapMailboxGetCommand { } impl JmapMailboxGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let output = client.mailbox_get(Some(self.ids.clone()), None)?; for id in output.not_found { @@ -42,12 +48,12 @@ impl JmapMailboxGetCommand { } let table = MailboxesTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), colors: MailboxColors { - id: client.account.mailboxes_list_table_id_color(), - name: client.account.mailboxes_list_table_name_color(), - total: client.account.mailboxes_list_table_total_color(), - unread: client.account.mailboxes_list_table_unread_color(), + id: account.mailboxes_list_table_id_color(), + name: account.mailboxes_list_table_name_color(), + total: account.mailboxes_list_table_total_color(), + unread: account.mailboxes_list_table_unread_color(), }, mailboxes: output.mailboxes, }; diff --git a/src/jmap/mailbox/query.rs b/src/jmap/mailbox/query.rs index f4e5ef0a..1be89c98 100644 --- a/src/jmap/mailbox/query.rs +++ b/src/jmap/mailbox/query.rs @@ -26,6 +26,7 @@ use io_jmap::rfc8621::mailbox::{ use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::jmap::client::JmapClient; /// Query JMAP mailboxes (Mailbox/query + Mailbox/get). @@ -72,7 +73,12 @@ pub struct JmapMailboxQueryCommand { } impl JmapMailboxQueryCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let filter = { let f = MailboxFilter { parent_id: self.parent_id, @@ -105,12 +111,12 @@ impl JmapMailboxQueryCommand { )?; let table = MailboxesTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), colors: MailboxColors { - id: client.account.mailboxes_list_table_id_color(), - name: client.account.mailboxes_list_table_name_color(), - total: client.account.mailboxes_list_table_total_color(), - unread: client.account.mailboxes_list_table_unread_color(), + id: account.mailboxes_list_table_id_color(), + name: account.mailboxes_list_table_name_color(), + total: account.mailboxes_list_table_total_color(), + unread: account.mailboxes_list_table_unread_color(), }, mailboxes: output.mailboxes, }; diff --git a/src/jmap/mailbox/update.rs b/src/jmap/mailbox/update.rs index d75d84d4..06cbc365 100644 --- a/src/jmap/mailbox/update.rs +++ b/src/jmap/mailbox/update.rs @@ -57,7 +57,7 @@ pub struct JmapMailboxUpdateCommand { } impl JmapMailboxUpdateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let is_subscribed = if self.subscribe { Some(true) } else if self.unsubscribe { diff --git a/src/jmap/query.rs b/src/jmap/query.rs index e7b186c2..4bf650eb 100644 --- a/src/jmap/query.rs +++ b/src/jmap/query.rs @@ -54,7 +54,7 @@ pub struct JmapQueryCommand { } impl JmapQueryCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let raw = if self.method_calls.is_empty() || self.method_calls.first().map(|s| s.as_str()) == Some("-") { diff --git a/src/jmap/submission/cancel.rs b/src/jmap/submission/cancel.rs index d53f3665..30e2cb8b 100644 --- a/src/jmap/submission/cancel.rs +++ b/src/jmap/submission/cancel.rs @@ -33,7 +33,7 @@ pub struct JmapSubmissionCancelCommand { } impl JmapSubmissionCancelCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let output = client.email_submission_cancel(self.ids.clone())?; if !output.not_updated.is_empty() { diff --git a/src/jmap/submission/cli.rs b/src/jmap/submission/cli.rs index 9cde94cb..bc86320b 100644 --- a/src/jmap/submission/cli.rs +++ b/src/jmap/submission/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, submission::{ @@ -43,11 +44,16 @@ pub enum JmapSubmissionCommand { } impl JmapSubmissionCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), - Self::Query(cmd) => cmd.execute(printer, client), - Self::Create(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), + Self::Query(cmd) => cmd.execute(printer, account, client), + Self::Create(cmd) => cmd.execute(printer, account, client), Self::Cancel(cmd) => cmd.execute(printer, client), } } diff --git a/src/jmap/submission/create.rs b/src/jmap/submission/create.rs index fd6cc3cc..0492fef3 100644 --- a/src/jmap/submission/create.rs +++ b/src/jmap/submission/create.rs @@ -24,6 +24,7 @@ use io_jmap::rfc8621::email_submission::{ }; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, error::format_set_error, submission::query::SubmissionsTable, }; @@ -52,7 +53,12 @@ pub struct JmapSubmissionCreateCommand { } impl JmapSubmissionCreateCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let envelope = if let Some(mail_from_addr) = self.mail_from { let rcpt_to = self .rcpt_to @@ -91,7 +97,7 @@ impl JmapSubmissionCreateCommand { } let table = SubmissionsTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), submissions: output.created.into_values().collect(), }; diff --git a/src/jmap/submission/get.rs b/src/jmap/submission/get.rs index 11b066de..2c41fe3e 100644 --- a/src/jmap/submission/get.rs +++ b/src/jmap/submission/get.rs @@ -20,6 +20,7 @@ use clap::Parser; use log::warn; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{client::JmapClient, submission::query::SubmissionsTable}; /// Get JMAP email submissions by ID (EmailSubmission/get). @@ -31,7 +32,12 @@ pub struct JmapSubmissionGetCommand { } impl JmapSubmissionGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let output = client.email_submission_get(Some(self.ids.clone()))?; for id in output.not_found { @@ -39,7 +45,7 @@ impl JmapSubmissionGetCommand { } let table = SubmissionsTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), submissions: output.submissions, }; diff --git a/src/jmap/submission/query.rs b/src/jmap/submission/query.rs index a3a15c4b..3b848df0 100644 --- a/src/jmap/submission/query.rs +++ b/src/jmap/submission/query.rs @@ -24,6 +24,7 @@ use io_jmap::rfc8621::email_submission::{EmailSubmission, EmailSubmissionFilter, use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::jmap::client::JmapClient; /// CLI proxy for [`UndoStatus`]. @@ -69,7 +70,12 @@ pub struct JmapSubmissionQueryCommand { } impl JmapSubmissionQueryCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let filter = { let f = EmailSubmissionFilter { undo_status: self.undo_status.map(Into::into), @@ -91,7 +97,7 @@ impl JmapSubmissionQueryCommand { )?; let table = SubmissionsTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), submissions: output.submissions, }; diff --git a/src/jmap/thread/cli.rs b/src/jmap/thread/cli.rs index 11b97b4b..5602e420 100644 --- a/src/jmap/thread/cli.rs +++ b/src/jmap/thread/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{client::JmapClient, thread::get::JmapThreadGetCommand}; /// Manage JMAP threads. @@ -29,9 +30,14 @@ pub enum JmapThreadCommand { } impl JmapThreadCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/jmap/thread/get.rs b/src/jmap/thread/get.rs index 00f01eae..d33c087e 100644 --- a/src/jmap/thread/get.rs +++ b/src/jmap/thread/get.rs @@ -25,6 +25,7 @@ use log::warn; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::jmap::client::JmapClient; /// Get JMAP threads by ID (Thread/get). @@ -38,7 +39,12 @@ pub struct JmapThreadGetCommand { } impl JmapThreadGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let output = client.thread_get(self.ids.clone())?; for id in output.not_found { @@ -46,7 +52,7 @@ impl JmapThreadGetCommand { } printer.out(ThreadsTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), threads: output.threads, }) } diff --git a/src/jmap/vacation/cli.rs b/src/jmap/vacation/cli.rs index c3206ec7..fc8c0057 100644 --- a/src/jmap/vacation/cli.rs +++ b/src/jmap/vacation/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::jmap::{ client::JmapClient, vacation::{get::JmapVacationGetCommand, set::JmapVacationSetCommand}, @@ -34,9 +35,14 @@ pub enum JmapVacationCommand { } impl JmapVacationCommand { - pub fn execute(self, printer: &mut impl Printer, client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), Self::Set(cmd) => cmd.execute(printer, client), } } diff --git a/src/jmap/vacation/get.rs b/src/jmap/vacation/get.rs index 0cd2cdee..303b907e 100644 --- a/src/jmap/vacation/get.rs +++ b/src/jmap/vacation/get.rs @@ -24,6 +24,7 @@ use io_jmap::rfc8621::{capabilities::VACATION_RESPONSE, vacation_response::Vacat use pimalaya_cli::printer::{Message, Printer}; use serde::Serialize; +use crate::account::context::Account; use crate::jmap::client::JmapClient; /// Get the JMAP vacation response (VacationResponse/get). @@ -31,7 +32,12 @@ use crate::jmap::client::JmapClient; pub struct JmapVacationGetCommand; impl JmapVacationGetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut JmapClient, + ) -> Result<()> { let has_vacation = client .session() .map(|s| s.capabilities.contains_key(VACATION_RESPONSE)) @@ -46,7 +52,7 @@ impl JmapVacationGetCommand { }; let table = VacationTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), vacation, }; diff --git a/src/jmap/vacation/set.rs b/src/jmap/vacation/set.rs index 75d12552..5bc7d9a9 100644 --- a/src/jmap/vacation/set.rs +++ b/src/jmap/vacation/set.rs @@ -57,7 +57,7 @@ pub struct JmapVacationSetCommand { } impl JmapVacationSetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut JmapClient) -> Result<()> { let has_vacation = client .session() .map(|s| s.capabilities.contains_key(VACATION_RESPONSE)) diff --git a/src/m2dir/cli.rs b/src/m2dir/cli.rs index 5f8a391a..fe383162 100644 --- a/src/m2dir/cli.rs +++ b/src/m2dir/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::m2dir::{ client::M2dirClient, create::M2dirMailboxCreateCommand, delete::M2dirMailboxDeleteCommand, envelope::cli::M2dirEnvelopeCommand, flag::cli::M2dirFlagCommand, @@ -48,15 +49,20 @@ pub enum M2dirCommand { } impl M2dirCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { match self { Self::Create(cmd) => cmd.execute(printer, client), Self::Delete(cmd) => cmd.execute(printer, client), - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), Self::Messages(cmd) => cmd.execute(printer, client), - Self::Flags(cmd) => cmd.execute(printer, client), - Self::Envelopes(cmd) => cmd.execute(printer, client), + Self::Flags(cmd) => cmd.execute(printer, account, client), + Self::Envelopes(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/m2dir/client.rs b/src/m2dir/client.rs index 347028a0..4205724b 100644 --- a/src/m2dir/client.rs +++ b/src/m2dir/client.rs @@ -15,8 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Himalaya wrapper around [`io_m2dir::client::M2dirClient`] bundling -//! the merged [`Account`] alongside the m2dir client. +//! Himalaya wrapper around [`io_m2dir::client::M2dirClient`]. use std::{ ops::{Deref, DerefMut}, @@ -31,15 +30,14 @@ use crate::{account::context::Account, cli::load_or_wizard, config::M2dirConfig} pub struct M2dirClient { inner: Inner, - pub account: Account, } impl M2dirClient { /// Builds an [`M2dirClient`] rooted at the configured m2store /// path. - pub fn new(config: M2dirConfig, account: Account) -> Self { + pub fn new(config: M2dirConfig) -> Self { let inner = Inner::new(config.root.to_string_lossy().into_owned()); - Self { inner, account } + Self { inner } } } @@ -59,11 +57,13 @@ impl DerefMut for M2dirClient { /// Loads the configuration, picks the active account, builds the /// merged [`Account`] then opens the m2dir client. Bails when the -/// account has no `[m2dir]` block. +/// account has no `[m2dir]` block. Returns the client paired with +/// the merged account so subcommands receive both as sibling +/// arguments. pub fn build_m2dir_client( config_paths: &[PathBuf], account_name: Option<&str>, -) -> Result { +) -> Result<(Account, M2dirClient)> { let mut config = load_or_wizard(config_paths)?; let (name, mut ac) = config .take_account(account_name)? @@ -73,5 +73,5 @@ pub fn build_m2dir_client( .take() .ok_or_else(|| anyhow!("m2dir config is missing for account `{name}`"))?; let account = Account::from(config).merge(Account::from(ac)); - Ok(M2dirClient::new(m2dir_config, account)) + Ok((account, M2dirClient::new(m2dir_config))) } diff --git a/src/m2dir/create.rs b/src/m2dir/create.rs index 902243f7..57b3c743 100644 --- a/src/m2dir/create.rs +++ b/src/m2dir/create.rs @@ -32,7 +32,7 @@ pub struct M2dirMailboxCreateCommand { } impl M2dirMailboxCreateCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { client.init_store()?; client.create_mailbox(&self.m2dir_name.inner)?; printer.out(Message::new("m2dir folder successfully created")) diff --git a/src/m2dir/delete.rs b/src/m2dir/delete.rs index dfdde34f..ebdb3503 100644 --- a/src/m2dir/delete.rs +++ b/src/m2dir/delete.rs @@ -29,7 +29,7 @@ pub struct M2dirMailboxDeleteCommand { } impl M2dirMailboxDeleteCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir_name.inner)?; client.delete_mailbox(path)?; diff --git a/src/m2dir/envelope/cli.rs b/src/m2dir/envelope/cli.rs index abe0b2aa..850f960f 100644 --- a/src/m2dir/envelope/cli.rs +++ b/src/m2dir/envelope/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::m2dir::{ client::M2dirClient, envelope::{get::M2dirEnvelopeGetCommand, list::M2dirEnvelopeListCommand}, @@ -35,10 +36,15 @@ pub enum M2dirEnvelopeCommand { } impl M2dirEnvelopeCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), - Self::List(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), + Self::List(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/m2dir/envelope/get.rs b/src/m2dir/envelope/get.rs index 2766f778..210ef000 100644 --- a/src/m2dir/envelope/get.rs +++ b/src/m2dir/envelope/get.rs @@ -24,6 +24,7 @@ use mail_parser::{Header, MessageParser}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::m2dir::{ arg::{M2dirNameFlag, MessageIdArg}, client::M2dirClient, @@ -42,7 +43,12 @@ pub struct M2dirEnvelopeGetCommand { } impl M2dirEnvelopeGetCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; @@ -54,7 +60,7 @@ impl M2dirEnvelopeGetCommand { }; let table = EnvelopeTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), headers: parsed.headers(), }; diff --git a/src/m2dir/envelope/list.rs b/src/m2dir/envelope/list.rs index f2860fe2..80b841ca 100644 --- a/src/m2dir/envelope/list.rs +++ b/src/m2dir/envelope/list.rs @@ -24,6 +24,7 @@ use mail_parser::MessageParser; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::m2dir::{arg::M2dirNameFlag, client::M2dirClient}; /// List M2DIR envelopes from the given mailbox. @@ -37,7 +38,12 @@ pub struct M2dirEnvelopeListCommand { } impl M2dirEnvelopeListCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; @@ -72,13 +78,13 @@ impl M2dirEnvelopeListCommand { envelopes.sort_by(|a, b| a.date.cmp(&b.date)); let table = EnvelopesTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), colors: EnvelopeColors { - id: client.account.envelopes_list_table_id_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - date: client.account.envelopes_list_table_date_color(), + id: account.envelopes_list_table_id_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + date: account.envelopes_list_table_date_color(), }, envelopes, }; diff --git a/src/m2dir/flag/add.rs b/src/m2dir/flag/add.rs index be5fa21a..3c06e11f 100644 --- a/src/m2dir/flag/add.rs +++ b/src/m2dir/flag/add.rs @@ -42,7 +42,7 @@ pub struct M2dirFlagAddCommand { } impl M2dirFlagAddCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/m2dir/flag/cli.rs b/src/m2dir/flag/cli.rs index 826a6825..3a2541af 100644 --- a/src/m2dir/flag/cli.rs +++ b/src/m2dir/flag/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::m2dir::{ client::M2dirClient, flag::{ @@ -40,9 +41,14 @@ pub enum M2dirFlagCommand { } impl M2dirFlagCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), Self::Add(cmd) => cmd.execute(printer, client), Self::Set(cmd) => cmd.execute(printer, client), Self::Remove(cmd) => cmd.execute(printer, client), diff --git a/src/m2dir/flag/list.rs b/src/m2dir/flag/list.rs index d4b3d52c..896c8bbb 100644 --- a/src/m2dir/flag/list.rs +++ b/src/m2dir/flag/list.rs @@ -23,6 +23,7 @@ use comfy_table::{Cell, ContentArrangement, Row, Table}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::m2dir::{ arg::{M2dirNameFlag, MessageIdArg}, client::M2dirClient, @@ -41,15 +42,20 @@ pub struct M2dirFlagListCommand { } impl M2dirFlagListCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; let flags = client.read_flags(&m2dir, &self.id.inner)?; let table = FlagsTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), flags: flags.iter().map(str::to_owned).collect(), }; diff --git a/src/m2dir/flag/remove.rs b/src/m2dir/flag/remove.rs index 81828655..7855b094 100644 --- a/src/m2dir/flag/remove.rs +++ b/src/m2dir/flag/remove.rs @@ -40,7 +40,7 @@ pub struct M2dirFlagRemoveCommand { } impl M2dirFlagRemoveCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/m2dir/flag/set.rs b/src/m2dir/flag/set.rs index 8f31570c..eb33b117 100644 --- a/src/m2dir/flag/set.rs +++ b/src/m2dir/flag/set.rs @@ -40,7 +40,7 @@ pub struct M2dirFlagSetCommand { } impl M2dirFlagSetCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/m2dir/list.rs b/src/m2dir/list.rs index d7ad1756..73c8bc4f 100644 --- a/src/m2dir/list.rs +++ b/src/m2dir/list.rs @@ -24,6 +24,7 @@ use io_m2dir::m2dir::M2dir; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::m2dir::client::M2dirClient; /// List m2dir folders found under the store root. @@ -31,12 +32,17 @@ use crate::m2dir::client::M2dirClient; pub struct M2dirMailboxListCommand; impl M2dirMailboxListCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut M2dirClient, + ) -> Result<()> { let m2dirs = client.list_mailboxes()?; let table = M2dirsTable { - preset: client.account.table_preset().to_string(), - name_color: client.account.mailboxes_list_table_name_color(), + preset: account.table_preset().to_string(), + name_color: account.mailboxes_list_table_name_color(), rows: m2dirs.into_iter().map(From::from).collect(), }; diff --git a/src/m2dir/message/cli.rs b/src/m2dir/message/cli.rs index 9c1c7109..08e4f316 100644 --- a/src/m2dir/message/cli.rs +++ b/src/m2dir/message/cli.rs @@ -40,7 +40,7 @@ pub enum M2dirMessageCommand { } impl M2dirMessageCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { match self { Self::Save(cmd) => cmd.execute(printer, client), Self::Get(cmd) => cmd.execute(printer, client), diff --git a/src/m2dir/message/export.rs b/src/m2dir/message/export.rs index 2a5577cc..1345362e 100644 --- a/src/m2dir/message/export.rs +++ b/src/m2dir/message/export.rs @@ -57,7 +57,7 @@ pub struct M2dirMessageExportCommand { } impl M2dirMessageExportCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/m2dir/message/get.rs b/src/m2dir/message/get.rs index 2e11f4ec..badb9700 100644 --- a/src/m2dir/message/get.rs +++ b/src/m2dir/message/get.rs @@ -41,7 +41,7 @@ pub struct M2dirMessageGetCommand { } impl M2dirMessageGetCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/m2dir/message/read.rs b/src/m2dir/message/read.rs index 508a041e..868d38a3 100644 --- a/src/m2dir/message/read.rs +++ b/src/m2dir/message/read.rs @@ -48,7 +48,7 @@ pub struct M2dirMessageReadCommand { } impl M2dirMessageReadCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/m2dir/message/save.rs b/src/m2dir/message/save.rs index 4fc7b530..6c50b2ec 100644 --- a/src/m2dir/message/save.rs +++ b/src/m2dir/message/save.rs @@ -50,7 +50,7 @@ pub struct M2dirMessageSaveCommand { } impl M2dirMessageSaveCommand { - pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut M2dirClient) -> Result<()> { let store = client.open_store()?; let path = store.resolve_folder_path(&self.m2dir.inner)?; let m2dir = client.open_m2dir(path)?; diff --git a/src/maildir/cli.rs b/src/maildir/cli.rs index 20131c00..9213b2e4 100644 --- a/src/maildir/cli.rs +++ b/src/maildir/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::maildir::{ client::MaildirClient, create::MaildirMailboxCreateCommand, delete::MaildirMailboxDeleteCommand, envelope::cli::MaildirEnvelopeCommand, @@ -48,16 +49,21 @@ pub enum MaildirCommand { } impl MaildirCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut MaildirClient, + ) -> Result<()> { match self { Self::Create(cmd) => cmd.execute(printer, client), Self::Rename(cmd) => cmd.execute(printer, client), Self::Delete(cmd) => cmd.execute(printer, client), - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), Self::Messages(cmd) => cmd.execute(printer, client), - Self::Flags(cmd) => cmd.execute(printer, client), - Self::Envelopes(cmd) => cmd.execute(printer, client), + Self::Flags(cmd) => cmd.execute(printer, account, client), + Self::Envelopes(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/maildir/client.rs b/src/maildir/client.rs index f1101966..904b049e 100644 --- a/src/maildir/client.rs +++ b/src/maildir/client.rs @@ -15,12 +15,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Himalaya wrapper around [`io_maildir::client::MaildirClient`] -//! that bundles the merged [`Account`] alongside the maildir client. +//! Himalaya wrapper around [`io_maildir::client::MaildirClient`]. //! //! Built up front by the dispatch layer (`crate::cli`) via //! [`build_maildir_client`] and handed down to every maildir-specific -//! subcommand. +//! subcommand, together with the merged [`Account`] as a sibling +//! argument. use std::{ ops::{Deref, DerefMut}, @@ -35,7 +35,6 @@ use crate::{account::context::Account, cli::load_or_wizard, config::MaildirConfi pub struct MaildirClient { inner: Inner, - pub account: Account, /// Filesystem root of the configured maildir. Kept on the wrapper /// so commands can join sub-paths (per-mailbox) without needing /// the original [`MaildirConfig`]. @@ -45,14 +44,10 @@ pub struct MaildirClient { impl MaildirClient { /// Builds a [`MaildirClient`] rooted at the configured maildir /// path. - pub fn new(config: MaildirConfig, account: Account) -> Self { + pub fn new(config: MaildirConfig) -> Self { let root = config.root.clone(); let inner = Inner::new(root.to_string_lossy().into_owned()); - Self { - inner, - account, - root, - } + Self { inner, root } } /// Resolves a maildir CLI argument: tries `path` as-is first, then @@ -83,11 +78,13 @@ impl DerefMut for MaildirClient { /// Loads the configuration, picks the active account, builds the /// merged [`Account`] then opens the maildir client. Bails when the -/// account has no `[maildir]` block. +/// account has no `[maildir]` block. Returns the client paired with +/// the merged account so subcommands receive both as sibling +/// arguments. pub fn build_maildir_client( config_paths: &[PathBuf], account_name: Option<&str>, -) -> Result { +) -> Result<(Account, MaildirClient)> { let mut config = load_or_wizard(config_paths)?; let (name, mut ac) = config .take_account(account_name)? @@ -97,5 +94,5 @@ pub fn build_maildir_client( .take() .ok_or_else(|| anyhow!("Maildir config is missing for account `{name}`"))?; let account = Account::from(config).merge(Account::from(ac)); - Ok(MaildirClient::new(maildir_config, account)) + Ok((account, MaildirClient::new(maildir_config))) } diff --git a/src/maildir/create.rs b/src/maildir/create.rs index eb4aad4b..bf8306fc 100644 --- a/src/maildir/create.rs +++ b/src/maildir/create.rs @@ -32,7 +32,7 @@ pub struct MaildirMailboxCreateCommand { } impl MaildirMailboxCreateCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let path = client .root .join(&self.maildir_name.inner) diff --git a/src/maildir/delete.rs b/src/maildir/delete.rs index 3c5f6a6b..0bbf3efc 100644 --- a/src/maildir/delete.rs +++ b/src/maildir/delete.rs @@ -32,7 +32,7 @@ pub struct MaildirMailboxDeleteCommand { } impl MaildirMailboxDeleteCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let path = client .root .join(&self.maildir_path.inner) diff --git a/src/maildir/envelope/cli.rs b/src/maildir/envelope/cli.rs index 83a4dd6a..41f2df76 100644 --- a/src/maildir/envelope/cli.rs +++ b/src/maildir/envelope/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::maildir::{ client::MaildirClient, envelope::{get::MaildirEnvelopeGetCommand, list::MaildirEnvelopeListCommand}, @@ -36,10 +37,15 @@ pub enum MaildirEnvelopeCommand { } impl MaildirEnvelopeCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut MaildirClient, + ) -> Result<()> { match self { - Self::Get(cmd) => cmd.execute(printer, client), - Self::List(cmd) => cmd.execute(printer, client), + Self::Get(cmd) => cmd.execute(printer, account, client), + Self::List(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/maildir/envelope/get.rs b/src/maildir/envelope/get.rs index cdfc5712..ab28c5d1 100644 --- a/src/maildir/envelope/get.rs +++ b/src/maildir/envelope/get.rs @@ -24,6 +24,7 @@ use mail_parser::Header; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::maildir::{ arg::{MaildirPathFlag, MessageIdArg}, client::MaildirClient, @@ -43,7 +44,12 @@ pub struct MaildirEnvelopeGetCommand { } impl MaildirEnvelopeGetCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut MaildirClient, + ) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let message = client.get(maildir, &self.id.inner)?; @@ -55,7 +61,7 @@ impl MaildirEnvelopeGetCommand { }; let table = EnvelopeTable { - preset: client.account.table_preset().to_string(), + preset: account.table_preset().to_string(), headers: parsed.headers(), }; diff --git a/src/maildir/envelope/list.rs b/src/maildir/envelope/list.rs index 79ffec49..c2a1683d 100644 --- a/src/maildir/envelope/list.rs +++ b/src/maildir/envelope/list.rs @@ -23,6 +23,7 @@ use comfy_table::{Cell, Color, ContentArrangement, Row, Table}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::maildir::{arg::MaildirPathFlag, client::MaildirClient}; /// List MAILDIR envelopes from the given mailbox. @@ -37,7 +38,12 @@ pub struct MaildirEnvelopeListCommand { } impl MaildirEnvelopeListCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut MaildirClient, + ) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let entries: Vec<_> = client.list_entries(maildir)?.into_iter().collect(); @@ -76,13 +82,13 @@ impl MaildirEnvelopeListCommand { envelopes.sort_by(|a, b| a.date.cmp(&b.date)); let table = EnvelopesTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), colors: EnvelopeColors { - id: client.account.envelopes_list_table_id_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - date: client.account.envelopes_list_table_date_color(), + id: account.envelopes_list_table_id_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + date: account.envelopes_list_table_date_color(), }, envelopes, }; diff --git a/src/maildir/flag/add.rs b/src/maildir/flag/add.rs index b52fe578..e02339e9 100644 --- a/src/maildir/flag/add.rs +++ b/src/maildir/flag/add.rs @@ -43,7 +43,7 @@ pub struct MaildirFlagAddCommand { } impl MaildirFlagAddCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let flags = MaildirFlags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/flag/cli.rs b/src/maildir/flag/cli.rs index e04bc38a..bf357bef 100644 --- a/src/maildir/flag/cli.rs +++ b/src/maildir/flag/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::maildir::{ client::MaildirClient, flag::{ @@ -40,9 +41,14 @@ pub enum MaildirFlagCommand { } impl MaildirFlagCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut MaildirClient, + ) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account), Self::Add(cmd) => cmd.execute(printer, client), Self::Set(cmd) => cmd.execute(printer, client), Self::Remove(cmd) => cmd.execute(printer, client), diff --git a/src/maildir/flag/list.rs b/src/maildir/flag/list.rs index 8b046a37..1b1e7b8c 100644 --- a/src/maildir/flag/list.rs +++ b/src/maildir/flag/list.rs @@ -24,7 +24,7 @@ use io_maildir::flag::MaildirFlag; use pimalaya_cli::printer::Printer; use serde::Serialize; -use crate::maildir::client::MaildirClient; +use crate::account::context::Account; /// List available MAILDIR flags for the given mailbox. /// @@ -35,10 +35,10 @@ use crate::maildir::client::MaildirClient; pub struct MaildirFlagListCommand; impl MaildirFlagListCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, account: &mut Account) -> Result<()> { let table = FlagsTable { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), flags: vec![ FlagRow::new(MaildirFlag::Passed), FlagRow::new(MaildirFlag::Replied), diff --git a/src/maildir/flag/remove.rs b/src/maildir/flag/remove.rs index 4fce223f..3e2b567e 100644 --- a/src/maildir/flag/remove.rs +++ b/src/maildir/flag/remove.rs @@ -43,7 +43,7 @@ pub struct MaildirFlagRemoveCommand { } impl MaildirFlagRemoveCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let flags = MaildirFlags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/flag/set.rs b/src/maildir/flag/set.rs index 94125d1c..09f1bae7 100644 --- a/src/maildir/flag/set.rs +++ b/src/maildir/flag/set.rs @@ -43,7 +43,7 @@ pub struct MaildirFlagSetCommand { } impl MaildirFlagSetCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let flags = MaildirFlags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/list.rs b/src/maildir/list.rs index 4c7cc00b..4f618e1a 100644 --- a/src/maildir/list.rs +++ b/src/maildir/list.rs @@ -24,6 +24,7 @@ use io_maildir::maildir::Maildir; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::maildir::client::MaildirClient; /// List, search and filter maildirs. @@ -35,12 +36,17 @@ use crate::maildir::client::MaildirClient; pub struct MaildirMailboxListCommand; impl MaildirMailboxListCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut MaildirClient, + ) -> Result<()> { let maildirs = client.list_maildirs()?; let table = MaildirsTable { - preset: client.account.table_preset().to_string(), - name_color: client.account.mailboxes_list_table_name_color(), + preset: account.table_preset().to_string(), + name_color: account.mailboxes_list_table_name_color(), rows: maildirs.into_iter().map(From::from).collect(), }; diff --git a/src/maildir/message/cli.rs b/src/maildir/message/cli.rs index 9c807015..54e57389 100644 --- a/src/maildir/message/cli.rs +++ b/src/maildir/message/cli.rs @@ -44,7 +44,7 @@ pub enum MaildirMessageCommand { } impl MaildirMessageCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { match self { Self::Save(cmd) => cmd.execute(printer, client), Self::Get(cmd) => cmd.execute(printer, client), diff --git a/src/maildir/message/copy.rs b/src/maildir/message/copy.rs index 7daf5e9d..d6a6d8ff 100644 --- a/src/maildir/message/copy.rs +++ b/src/maildir/message/copy.rs @@ -43,7 +43,7 @@ pub struct MaildirMessageCopyCommand { } impl MaildirMessageCopyCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let source = client.resolve_maildir(&self.source.inner)?; let target = client.resolve_maildir(&self.target.inner)?; diff --git a/src/maildir/message/export.rs b/src/maildir/message/export.rs index df237a1c..4542dc8d 100644 --- a/src/maildir/message/export.rs +++ b/src/maildir/message/export.rs @@ -57,7 +57,7 @@ pub struct MaildirMessageExportCommand { } impl MaildirMessageExportCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let msg = client.get(maildir, &self.id.inner)?; diff --git a/src/maildir/message/get.rs b/src/maildir/message/get.rs index 9a2976aa..ffebc4dc 100644 --- a/src/maildir/message/get.rs +++ b/src/maildir/message/get.rs @@ -41,7 +41,7 @@ pub struct MaildirMessageGetCommand { } impl MaildirMessageGetCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let msg = client.get(maildir, &self.id.inner)?; diff --git a/src/maildir/message/move.rs b/src/maildir/message/move.rs index 46167c08..454a9b7c 100644 --- a/src/maildir/message/move.rs +++ b/src/maildir/message/move.rs @@ -43,7 +43,7 @@ pub struct MaildirMessageMoveCommand { } impl MaildirMessageMoveCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let source = client.resolve_maildir(&self.source.inner)?; let target = client.resolve_maildir(&self.target.inner)?; diff --git a/src/maildir/message/read.rs b/src/maildir/message/read.rs index 4fb6f5e2..8ec128c5 100644 --- a/src/maildir/message/read.rs +++ b/src/maildir/message/read.rs @@ -48,7 +48,7 @@ pub struct MaildirMessageReadCommand { } impl MaildirMessageReadCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let message = client.get(maildir, &self.id.inner)?; diff --git a/src/maildir/message/save.rs b/src/maildir/message/save.rs index b2baa470..218edc66 100644 --- a/src/maildir/message/save.rs +++ b/src/maildir/message/save.rs @@ -56,7 +56,7 @@ pub struct MaildirMessageSaveCommand { } impl MaildirMessageSaveCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let maildir = client.resolve_maildir(&self.maildir.inner)?; let msg = self.message.parse()?; let flags = MaildirFlags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/rename.rs b/src/maildir/rename.rs index 0805e7be..fee95c32 100644 --- a/src/maildir/rename.rs +++ b/src/maildir/rename.rs @@ -37,7 +37,7 @@ pub struct MaildirMailboxRenameCommand { } impl MaildirMailboxRenameCommand { - pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut MaildirClient) -> Result<()> { let path = client .root .join(&self.maildir_path.inner) diff --git a/src/shared/attachments/cli.rs b/src/shared/attachments/cli.rs index 82e25d78..ccc33e4f 100644 --- a/src/shared/attachments/cli.rs +++ b/src/shared/attachments/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ attachments::{download::AttachmentDownloadCommand, list::AttachmentListCommand}, client::EmailClient, @@ -36,10 +37,15 @@ pub enum AttachmentCommand { } impl AttachmentCommand { - pub fn execute(self, printer: &mut impl Printer, client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, client), - Self::Download(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), + Self::Download(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/shared/attachments/download.rs b/src/shared/attachments/download.rs index 3d7d0528..641ba822 100644 --- a/src/shared/attachments/download.rs +++ b/src/shared/attachments/download.rs @@ -26,6 +26,7 @@ use clap::Parser; use mail_parser::{MessageParser, MimeHeaders}; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ attachments::list::{Attachment, AttachmentColors, Attachments, mime_string}, client::EmailClient, @@ -66,18 +67,20 @@ pub struct AttachmentDownloadCommand { } impl AttachmentDownloadCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let mailbox = self.mailbox.resolve(&client.account)?; + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { + let mailbox = self.mailbox.resolve(account)?; let raw = client.get_message(&mailbox, &self.message_id)?; let Some(message) = MessageParser::new().parse(&raw) else { bail!("Failed to parse RFC 5322 message"); }; - let dir = self - .dir - .clone() - .unwrap_or_else(|| client.account.downloads_dir()); + let dir = self.dir.clone().unwrap_or_else(|| account.downloads_dir()); if !dir.exists() { fs::create_dir_all(&dir)?; @@ -126,17 +129,17 @@ impl AttachmentDownloadCommand { } let attachments = Attachments { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), with_inline: written.iter().any(|a| a.inline), with_path: true, colors: AttachmentColors { - id: client.account.attachments_list_table_id_color(), - filename: client.account.attachments_list_table_filename_color(), - r#type: client.account.attachments_list_table_type_color(), - size: client.account.attachments_list_table_size_color(), - inline: client.account.attachments_list_table_inline_color(), - path: client.account.attachments_list_table_path_color(), + id: account.attachments_list_table_id_color(), + filename: account.attachments_list_table_filename_color(), + r#type: account.attachments_list_table_type_color(), + size: account.attachments_list_table_size_color(), + inline: account.attachments_list_table_inline_color(), + path: account.attachments_list_table_path_color(), }, attachments: written, }; diff --git a/src/shared/attachments/list.rs b/src/shared/attachments/list.rs index 550ca9f3..b33e0507 100644 --- a/src/shared/attachments/list.rs +++ b/src/shared/attachments/list.rs @@ -25,6 +25,7 @@ use mail_parser::{MessageParser, MessagePart, MimeHeaders}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::shared::{client::EmailClient, mailboxes::arg::MailboxArg}; /// List the attachments carried by a single message in the active @@ -53,8 +54,13 @@ pub struct AttachmentListCommand { } impl AttachmentListCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let mailbox = self.mailbox.resolve(&client.account)?; + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { + let mailbox = self.mailbox.resolve(account)?; let raw = client.get_message(&mailbox, &self.message_id)?; let Some(message) = MessageParser::new().parse(&raw) else { @@ -83,17 +89,17 @@ impl AttachmentListCommand { } let attachments = Attachments { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), with_inline: self.inline, with_path: false, colors: AttachmentColors { - id: client.account.attachments_list_table_id_color(), - filename: client.account.attachments_list_table_filename_color(), - r#type: client.account.attachments_list_table_type_color(), - size: client.account.attachments_list_table_size_color(), - inline: client.account.attachments_list_table_inline_color(), - path: client.account.attachments_list_table_path_color(), + id: account.attachments_list_table_id_color(), + filename: account.attachments_list_table_filename_color(), + r#type: account.attachments_list_table_type_color(), + size: account.attachments_list_table_size_color(), + inline: account.attachments_list_table_inline_color(), + path: account.attachments_list_table_path_color(), }, attachments, }; diff --git a/src/shared/client.rs b/src/shared/client.rs index f90ed6cb..3b85e7f5 100644 --- a/src/shared/client.rs +++ b/src/shared/client.rs @@ -18,9 +18,11 @@ //! Cross-protocol [`EmailClient`] for the shared subcommands //! (`mailboxes`, `envelopes`, `flags`, `messages`, `attachments`). //! -//! Wraps [`io_email::client::EmailClientStd`] and bundles the active -//! [`Account`] (display, identity, composer/reader registries) the -//! shared commands need alongside the I/O client. Implements +//! Wraps [`io_email::client::EmailClientStd`]. The active +//! [`Account`] is threaded as a sibling argument through every +//! `execute` chain rather than being bundled into the client; this +//! keeps account access (`resolve_mailbox`, identity, etc.) borrow- +//! disjoint from `&mut EmailClient` calls. Implements //! [`Deref`]/[`DerefMut`] onto the inner client so callers can call //! its methods directly. //! @@ -45,7 +47,6 @@ use crate::{ pub struct EmailClient { inner: EmailClientStd, - pub account: Account, } impl EmailClient { @@ -53,7 +54,7 @@ impl EmailClient { config: Config, mut account_config: AccountConfig, backend: Backend, - ) -> Result { + ) -> Result<(Account, Self)> { let mut inner = EmailClientStd::new(); let mut configured = false; @@ -156,7 +157,7 @@ impl EmailClient { let account = Account::from(config).merge(Account::from(account_config)); - Ok(Self { inner, account }) + Ok((account, Self { inner })) } } diff --git a/src/shared/envelopes/cli.rs b/src/shared/envelopes/cli.rs index a1a29f60..ef90cea3 100644 --- a/src/shared/envelopes/cli.rs +++ b/src/shared/envelopes/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, envelopes::{list::EnvelopeListCommand, search::EnvelopeSearchCommand}, @@ -38,10 +39,15 @@ pub enum EnvelopeCommand { } impl EnvelopeCommand { - pub fn execute(self, printer: &mut impl Printer, client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, client), - Self::Search(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), + Self::Search(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/shared/envelopes/list.rs b/src/shared/envelopes/list.rs index 2eee3af9..ea17925d 100644 --- a/src/shared/envelopes/list.rs +++ b/src/shared/envelopes/list.rs @@ -26,6 +26,7 @@ use io_email::{address::Address, envelope::Envelope, flag::Flag}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::shared::{client::EmailClient, mailboxes::arg::MailboxArg}; /// List envelopes for the active account, regardless of the underlying @@ -75,39 +76,44 @@ pub struct EnvelopeListCommand { } impl EnvelopeListCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { let page = Some(self.page).filter(|p| *p > 0); let page_size = self .page_size - .or(Some(client.account.envelopes_list_page_size())) + .or(Some(account.envelopes_list_page_size())) .filter(|p| *p > 0); - let mailbox = self.mailbox.resolve(&client.account)?; + let mailbox = self.mailbox.resolve(account)?; let envelopes = client.list_envelopes(&mailbox, page, page_size, self.has_attachment)?; let envelopes = Envelopes { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), max_width: self.max_width, - datetime_fmt: client.account.datetime_fmt().to_string(), - datetime_local_tz: client.account.datetime_local_tz(), + datetime_fmt: account.datetime_fmt().to_string(), + datetime_local_tz: account.datetime_local_tz(), recipient: self.recipient, with_attachment: self.has_attachment, chars: FlagChars { - unseen: client.account.envelopes_list_table_unseen_char(), - replied: client.account.envelopes_list_table_replied_char(), - flagged: client.account.envelopes_list_table_flagged_char(), - attachment: client.account.envelopes_list_table_attachment_char(), + unseen: account.envelopes_list_table_unseen_char(), + replied: account.envelopes_list_table_replied_char(), + flagged: account.envelopes_list_table_flagged_char(), + attachment: account.envelopes_list_table_attachment_char(), }, colors: EnvelopeColors { - id: client.account.envelopes_list_table_id_color(), - flags: client.account.envelopes_list_table_flags_color(), - att: client.account.envelopes_list_table_att_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - to: client.account.envelopes_list_table_to_color(), - date: client.account.envelopes_list_table_date_color(), - size: client.account.envelopes_list_table_size_color(), + id: account.envelopes_list_table_id_color(), + flags: account.envelopes_list_table_flags_color(), + att: account.envelopes_list_table_att_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + to: account.envelopes_list_table_to_color(), + date: account.envelopes_list_table_date_color(), + size: account.envelopes_list_table_size_color(), }, envelopes, }; diff --git a/src/shared/envelopes/search.rs b/src/shared/envelopes/search.rs index 4e077bcf..eec15e69 100644 --- a/src/shared/envelopes/search.rs +++ b/src/shared/envelopes/search.rs @@ -23,6 +23,7 @@ use clap::Parser; use io_email::search::{error::Error as SearchQueryError, query::SearchEmailsQuery}; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, envelopes::list::{EnvelopeColors, Envelopes, FlagChars}, @@ -81,13 +82,18 @@ pub struct EnvelopeSearchCommand { } impl EnvelopeSearchCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { let page = Some(self.page).filter(|p| *p > 0); let page_size = self .page_size - .or(Some(client.account.envelopes_list_page_size())) + .or(Some(account.envelopes_list_page_size())) .filter(|p| *p > 0); - let mailbox = self.mailbox.resolve(&client.account)?; + let mailbox = self.mailbox.resolve(account)?; let query = parse_query(self.query.as_deref()); let envelopes = client.search_envelopes( @@ -99,28 +105,28 @@ impl EnvelopeSearchCommand { )?; let envelopes = Envelopes { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), max_width: self.max_width, - datetime_fmt: client.account.datetime_fmt().to_string(), - datetime_local_tz: client.account.datetime_local_tz(), + datetime_fmt: account.datetime_fmt().to_string(), + datetime_local_tz: account.datetime_local_tz(), recipient: self.recipient, with_attachment: self.has_attachment, chars: FlagChars { - unseen: client.account.envelopes_list_table_unseen_char(), - replied: client.account.envelopes_list_table_replied_char(), - flagged: client.account.envelopes_list_table_flagged_char(), - attachment: client.account.envelopes_list_table_attachment_char(), + unseen: account.envelopes_list_table_unseen_char(), + replied: account.envelopes_list_table_replied_char(), + flagged: account.envelopes_list_table_flagged_char(), + attachment: account.envelopes_list_table_attachment_char(), }, colors: EnvelopeColors { - id: client.account.envelopes_list_table_id_color(), - flags: client.account.envelopes_list_table_flags_color(), - att: client.account.envelopes_list_table_att_color(), - subject: client.account.envelopes_list_table_subject_color(), - from: client.account.envelopes_list_table_from_color(), - to: client.account.envelopes_list_table_to_color(), - date: client.account.envelopes_list_table_date_color(), - size: client.account.envelopes_list_table_size_color(), + id: account.envelopes_list_table_id_color(), + flags: account.envelopes_list_table_flags_color(), + att: account.envelopes_list_table_att_color(), + subject: account.envelopes_list_table_subject_color(), + from: account.envelopes_list_table_from_color(), + to: account.envelopes_list_table_to_color(), + date: account.envelopes_list_table_date_color(), + size: account.envelopes_list_table_size_color(), }, envelopes, }; diff --git a/src/shared/flags/add.rs b/src/shared/flags/add.rs index 46049b9a..7ff1d386 100644 --- a/src/shared/flags/add.rs +++ b/src/shared/flags/add.rs @@ -23,6 +23,7 @@ use io_email::flag::{Flag, FlagOp}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, flags::arg::{FlagsArg, MessageIdsArg}, @@ -41,8 +42,13 @@ pub struct FlagAddCommand { } impl FlagAddCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let mailbox = self.mailbox.resolve(&client.account)?; + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { + let mailbox = self.mailbox.resolve(account)?; let ids: Vec<&str> = self.message_ids.inner.iter().map(String::as_str).collect(); let flags: Vec = self.flags.inner.iter().map(Into::into).collect(); diff --git a/src/shared/flags/cli.rs b/src/shared/flags/cli.rs index 428dd448..eb07398e 100644 --- a/src/shared/flags/cli.rs +++ b/src/shared/flags/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, flags::{add::FlagAddCommand, remove::FlagRemoveCommand, set::FlagSetCommand}, @@ -36,11 +37,16 @@ pub enum FlagCommand { } impl FlagCommand { - pub fn execute(self, printer: &mut impl Printer, client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { match self { - Self::Add(cmd) => cmd.execute(printer, client), - Self::Set(cmd) => cmd.execute(printer, client), - Self::Remove(cmd) => cmd.execute(printer, client), + Self::Add(cmd) => cmd.execute(printer, account, client), + Self::Set(cmd) => cmd.execute(printer, account, client), + Self::Remove(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/shared/flags/remove.rs b/src/shared/flags/remove.rs index f8d69a31..9449f3c8 100644 --- a/src/shared/flags/remove.rs +++ b/src/shared/flags/remove.rs @@ -23,6 +23,7 @@ use io_email::flag::{Flag, FlagOp}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, flags::arg::{FlagsArg, MessageIdsArg}, @@ -41,8 +42,13 @@ pub struct FlagRemoveCommand { } impl FlagRemoveCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let mailbox = self.mailbox.resolve(&client.account)?; + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { + let mailbox = self.mailbox.resolve(account)?; let ids: Vec<&str> = self.message_ids.inner.iter().map(String::as_str).collect(); let flags: Vec = self.flags.inner.iter().map(Into::into).collect(); diff --git a/src/shared/flags/set.rs b/src/shared/flags/set.rs index 157b7676..3c64ccb6 100644 --- a/src/shared/flags/set.rs +++ b/src/shared/flags/set.rs @@ -23,6 +23,7 @@ use io_email::flag::{Flag, FlagOp}; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, flags::arg::{FlagsArg, MessageIdsArg}, @@ -41,8 +42,13 @@ pub struct FlagSetCommand { } impl FlagSetCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let mailbox = self.mailbox.resolve(&client.account)?; + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { + let mailbox = self.mailbox.resolve(account)?; let ids: Vec<&str> = self.message_ids.inner.iter().map(String::as_str).collect(); let flags: Vec = self.flags.inner.iter().map(Into::into).collect(); diff --git a/src/shared/mailboxes/cli.rs b/src/shared/mailboxes/cli.rs index 5b144a47..a0f1263e 100644 --- a/src/shared/mailboxes/cli.rs +++ b/src/shared/mailboxes/cli.rs @@ -19,6 +19,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{client::EmailClient, mailboxes::list::MailboxListCommand}; /// Shared API to manage mailboxes for the active account. @@ -31,9 +32,14 @@ pub enum MailboxCommand { } impl MailboxCommand { - pub fn execute(self, printer: &mut impl Printer, client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, account, client), } } } diff --git a/src/shared/mailboxes/list.rs b/src/shared/mailboxes/list.rs index c601a7e9..7530ea85 100644 --- a/src/shared/mailboxes/list.rs +++ b/src/shared/mailboxes/list.rs @@ -24,6 +24,7 @@ use io_email::mailbox::Mailbox; use pimalaya_cli::printer::Printer; use serde::Serialize; +use crate::account::context::Account; use crate::shared::client::EmailClient; /// Shared API to list mailboxes for the active account. @@ -48,19 +49,24 @@ pub struct MailboxListCommand { } impl MailboxListCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { let mailboxes = client.list_mailboxes(self.counts)?; let mailboxes = Mailboxes { - preset: client.account.table_preset().to_string(), - arrangement: client.account.table_arrangement(), + preset: account.table_preset().to_string(), + arrangement: account.table_arrangement(), max_width: self.max_width, with_counts: self.counts, colors: MailboxColors { - id: client.account.mailboxes_list_table_id_color(), - name: client.account.mailboxes_list_table_name_color(), - total: client.account.mailboxes_list_table_total_color(), - unread: client.account.mailboxes_list_table_unread_color(), + id: account.mailboxes_list_table_id_color(), + name: account.mailboxes_list_table_name_color(), + total: account.mailboxes_list_table_total_color(), + unread: account.mailboxes_list_table_unread_color(), }, mailboxes, }; diff --git a/src/shared/messages/add.rs b/src/shared/messages/add.rs index a2d4aca2..1806369a 100644 --- a/src/shared/messages/add.rs +++ b/src/shared/messages/add.rs @@ -49,7 +49,7 @@ pub struct MessageAddCommand { } impl MessageAddCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> { let raw = self.message.parse()?.into_bytes(); let flags: Vec = self.flag.iter().map(Into::into).collect(); let id = client.add_message(&self.mailbox, &flags, raw)?; diff --git a/src/shared/messages/arg.rs b/src/shared/messages/arg.rs index abe65930..7153cf9c 100644 --- a/src/shared/messages/arg.rs +++ b/src/shared/messages/arg.rs @@ -48,7 +48,7 @@ use pimalaya_cli::clap::parsers::path_parser; pub struct MessageArg { /// Can be a path to a file, raw message contents or nothing if /// piped via standard input. - #[arg(name = "message-raw", value_name = "MESSAGE", trailing_var_arg = true)] + #[arg(name = "message-raw", value_name = "MESSAGE", raw = true)] pub raw: Vec, } diff --git a/src/shared/messages/cli.rs b/src/shared/messages/cli.rs index abc98e66..865e72ff 100644 --- a/src/shared/messages/cli.rs +++ b/src/shared/messages/cli.rs @@ -19,15 +19,13 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, messages::{ - add::MessageAddCommand, compose::MessageComposeCommand, - compose_with::MessageComposeWithCommand, copy::MessageCopyCommand, - forward::MessageForwardCommand, forward_with::MessageForwardWithCommand, - mailto::MessageMailtoCommand, mv::MessageMoveCommand, read::MessageReadCommand, - read_with::MessageReadWithCommand, reply::MessageReplyCommand, - reply_with::MessageReplyWithCommand, send::MessageSendCommand, + add::MessageAddCommand, compose::MessageComposeCommand, copy::MessageCopyCommand, + forward::MessageForwardCommand, mv::MessageMoveCommand, read::MessageReadCommand, + reply::MessageReplyCommand, send::MessageSendCommand, }, }; @@ -35,48 +33,42 @@ use crate::shared::{ /// /// A message is composed of headers (key-value properties) and a body /// (suite of MIME parts). The built-in `compose` / `reply` / `forward` -/// / `read` subcommands cover simple cases via CLI flags. For -/// non-default workflows, the `-with` variants delegate composition -/// and rendering to a user-defined command from -/// `[message.composer.*]` / `[message.reader.*]`. +/// / `read` subcommands cover simple cases via CLI flags. Richer +/// composition is delegated to standalone tools (e.g. +/// [`mml`](https://github.com/pimalaya/mml)) wired up through shell +/// pipelines into `messages send` / `messages add`. #[derive(Debug, Subcommand)] pub enum MessageCommand { #[command(visible_alias = "save")] Add(MessageAddCommand), #[command(visible_alias = "write", alias = "new")] Compose(MessageComposeCommand), - #[command(visible_alias = "write-with")] - ComposeWith(MessageComposeWithCommand), #[command(visible_alias = "cp")] Copy(MessageCopyCommand), #[command(visible_alias = "fwd")] Forward(MessageForwardCommand), - ForwardWith(MessageForwardWithCommand), - Mailto(MessageMailtoCommand), #[command(visible_alias = "mv")] Move(MessageMoveCommand), Read(MessageReadCommand), - ReadWith(MessageReadWithCommand), Reply(MessageReplyCommand), - ReplyWith(MessageReplyWithCommand), Send(MessageSendCommand), } impl MessageCommand { - pub fn execute(self, printer: &mut impl Printer, client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { match self { Self::Add(cmd) => cmd.execute(printer, client), - Self::Compose(cmd) => cmd.execute(printer, client), - Self::ComposeWith(cmd) => cmd.execute(printer, client), + Self::Compose(cmd) => cmd.execute(printer, account, client), Self::Copy(cmd) => cmd.execute(printer, client), - Self::Forward(cmd) => cmd.execute(printer, client), - Self::ForwardWith(cmd) => cmd.execute(printer, client), - Self::Mailto(cmd) => cmd.execute(printer, client), + Self::Forward(cmd) => cmd.execute(printer, account, client), Self::Move(cmd) => cmd.execute(printer, client), Self::Read(cmd) => cmd.execute(printer, client), - Self::ReadWith(cmd) => cmd.execute(printer, client), - Self::Reply(cmd) => cmd.execute(printer, client), - Self::ReplyWith(cmd) => cmd.execute(printer, client), + Self::Reply(cmd) => cmd.execute(printer, account, client), Self::Send(cmd) => cmd.execute(printer, client), } } diff --git a/src/shared/messages/compose.rs b/src/shared/messages/compose.rs index 7a4d72a4..15afc52a 100644 --- a/src/shared/messages/compose.rs +++ b/src/shared/messages/compose.rs @@ -21,6 +21,7 @@ use anyhow::Result; use clap::Parser; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, messages::{ @@ -36,8 +37,10 @@ use crate::shared::{ /// produced RFC 5322 bytes are written to stdout by default; pass /// `--save ` to append a copy, `--send` to push through the /// account's SMTP/JMAP send path, or both. For richer composition -/// (multipart MIME, MML directives, signing/encryption, TUI editing, -/// …) use `compose-with ` instead. +/// (multipart MIME, MML directives, signing/encryption, editor-driven +/// workflows), chain a standalone composer like +/// [`mml`](https://github.com/pimalaya/mml) into `messages send` / +/// `messages add` via a tempfile or bash/zsh process substitution. #[derive(Debug, Parser)] pub struct MessageComposeCommand { /// Sender address (`From` header). @@ -99,7 +102,12 @@ pub struct MessageComposeCommand { } impl MessageComposeCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { let raw = builder::build( BuilderArgs { from: self.from.as_deref(), @@ -116,6 +124,13 @@ impl MessageComposeCommand { None, )?; - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) + output::route( + printer, + account, + client, + raw, + self.save.as_deref(), + self.send, + ) } } diff --git a/src/shared/messages/compose_with.rs b/src/shared/messages/compose_with.rs deleted file mode 100644 index a4767235..00000000 --- a/src/shared/messages/compose_with.rs +++ /dev/null @@ -1,76 +0,0 @@ -// This file is part of Himalaya, a CLI to manage emails. -// -// Copyright (C) 2022-2026 soywod -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use anyhow::{Result, bail}; -use clap::Parser; -use pimalaya_cli::printer::Printer; -use pimalaya_config::command::shell; - -use crate::shared::{ - client::EmailClient, - messages::{output, runner}, -}; - -/// Compose a new message by delegating to a user-defined composer. -/// -/// Looks `` up in `[message.composer.]` and runs its -/// `command` via `sh -c`. With no ``, falls back to the entry -/// flagged `default = true`. The escape hatch `--command ""` -/// lets you run an ad-hoc command without editing the config. -/// -/// The composer takes the terminal: stdin is left empty (new -/// message — no source), stderr is inherited (composer prompts/ -/// errors). The composer's stdout must be a valid RFC 5322 message, -/// which himalaya then routes through `--save` / `--send`, or to -/// stdout if neither is set. -#[derive(Debug, Parser)] -pub struct MessageComposeWithCommand { - /// Name of an entry in `[message.composer.*]`. Optional — when - /// omitted, the composer flagged `default = true` is used. - #[arg(value_name = "NAME", conflicts_with = "command")] - pub name: Option, - - /// Ad-hoc shell command, mutually exclusive with ``. - /// Useful for trying the feature before editing the config. - #[arg(long, value_name = "SHELL")] - pub command: Option, - - #[arg(long, value_name = "MAILBOX")] - pub save: Option, - - #[arg(long)] - pub send: bool, -} - -impl MessageComposeWithCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let mut command = self.command.map(|cmd| shell(&cmd)); - let command = command.as_mut().unwrap_or( - &mut client - .account - .get_composer_mut(self.name.as_deref())? - .compose, - ); - - let raw = runner::run(command, &[])?; - if raw.is_empty() { - bail!("composer `{command:?}` produced no output"); - } - - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) - } -} diff --git a/src/shared/messages/copy.rs b/src/shared/messages/copy.rs index 64f82e85..6a3e0725 100644 --- a/src/shared/messages/copy.rs +++ b/src/shared/messages/copy.rs @@ -49,7 +49,7 @@ pub struct MessageCopyCommand { } impl MessageCopyCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> { let ids: Vec<&str> = self.ids.inner.iter().map(String::as_str).collect(); client.copy_messages(&self.from, &self.to, &ids)?; printer.out(Message::new("Message(s) successfully copied")) diff --git a/src/shared/messages/forward.rs b/src/shared/messages/forward.rs index d62f5aa4..3ab29595 100644 --- a/src/shared/messages/forward.rs +++ b/src/shared/messages/forward.rs @@ -21,6 +21,7 @@ use anyhow::Result; use clap::Parser; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, messages::{ @@ -34,7 +35,9 @@ use crate::shared::{ /// Fetches the source, pre-fills `Fwd:` on the subject and the /// `References` header, and quotes the source body. The produced /// MIME is written to stdout, or routed via `--save` / `--send`. -/// For non-default composition, use `forward-with `. +/// For richer composition, pipe `messages read ` into a +/// standalone composer (`mml forward`, etc.) and feed its output +/// back into `messages send` / `messages add`. #[derive(Debug, Parser)] pub struct MessageForwardCommand { #[arg(value_name = "ID")] @@ -101,7 +104,12 @@ pub struct MessageForwardCommand { } impl MessageForwardCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { let source = client.get_message(&self.mailbox, &self.id)?; let raw = builder::build( @@ -125,6 +133,13 @@ impl MessageForwardCommand { }), )?; - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) + output::route( + printer, + account, + client, + raw, + self.save.as_deref(), + self.send, + ) } } diff --git a/src/shared/messages/forward_with.rs b/src/shared/messages/forward_with.rs deleted file mode 100644 index 721ce410..00000000 --- a/src/shared/messages/forward_with.rs +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of Himalaya, a CLI to manage emails. -// -// Copyright (C) 2022-2026 soywod -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use anyhow::{Result, bail}; -use clap::Parser; -use pimalaya_cli::printer::Printer; -use pimalaya_config::command::shell; - -use crate::shared::{ - client::EmailClient, - messages::{output, runner}, -}; - -/// Forward a message by delegating to a user-defined composer. -/// -/// Same shape as `reply-with`: fetches the source, pipes it on -/// stdin to the named (or default) composer, captures stdout as the -/// MIME draft. -#[derive(Debug, Parser)] -pub struct MessageForwardWithCommand { - #[arg(value_name = "ID")] - pub id: String, - - #[arg( - long = "mailbox", - short = 'm', - value_name = "NAME", - default_value = "Inbox" - )] - pub mailbox: String, - - #[arg(value_name = "NAME", conflicts_with = "command")] - pub name: Option, - - #[arg(long, value_name = "SHELL")] - pub command: Option, - - #[arg(long, value_name = "MAILBOX")] - pub save: Option, - - #[arg(long)] - pub send: bool, -} - -impl MessageForwardWithCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let source = client.get_message(&self.mailbox, &self.id)?; - - let mut command = self.command.map(|cmd| shell(&cmd)); - let command = command.as_mut().unwrap_or( - &mut client - .account - .get_composer_mut(self.name.as_deref())? - .forward, - ); - - let raw = runner::run(command, &source)?; - if raw.is_empty() { - bail!("composer `{command:?}` produced no output"); - } - - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) - } -} diff --git a/src/shared/messages/mailto.rs b/src/shared/messages/mailto.rs deleted file mode 100644 index a0583eca..00000000 --- a/src/shared/messages/mailto.rs +++ /dev/null @@ -1,246 +0,0 @@ -// This file is part of Himalaya, a CLI to manage emails. -// -// Copyright (C) 2022-2026 soywod -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use anyhow::{Result, anyhow, bail}; -use clap::Parser; -use percent_encoding::percent_decode_str; -use pimalaya_cli::printer::Printer; -use pimalaya_config::command::shell; -use url::Url; - -use crate::shared::{ - client::EmailClient, - messages::{ - builder::{self, BuilderArgs}, - output, runner, - }, -}; - -/// Compose a new message from a `mailto:` URI, opening a user-defined -/// composer with the URI's recipient and headers prefilled. -/// -/// The URI is parsed per RFC 6068: the path is the comma-separated -/// `To:` list; supported query params are `to`, `cc`, `bcc`, `subject` -/// and `body` (any other param is ignored). The parsed fields are -/// folded into a draft RFC 5322 skeleton via the built-in MIME -/// assembler, then piped on stdin to the composer (same contract as -/// `messages compose-with` / `reply-with` / `forward-with`). The -/// composer's stdout is routed through `--save` / `--send`, or to -/// stdout if neither is set. -#[derive(Debug, Parser)] -pub struct MessageMailtoCommand { - /// `mailto:` URI as defined by RFC 6068. - #[arg(value_name = "URI")] - pub uri: String, - - /// Name of an entry in `[message.composer.*]`. Optional: when - /// omitted, the composer flagged `default = true` is used. - #[arg(value_name = "NAME", conflicts_with = "command")] - pub name: Option, - - /// Ad-hoc shell command, mutually exclusive with ``. - #[arg(long, value_name = "SHELL")] - pub command: Option, - - /// Save the produced message to the given mailbox. - #[arg(long, value_name = "MAILBOX")] - pub save: Option, - - /// Submit the produced message through the account's send backend. - #[arg(long)] - pub send: bool, -} - -impl MessageMailtoCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let fields = parse_mailto_uri(&self.uri)?; - - let to: Vec = fields.to; - let cc: Vec = fields.cc; - let bcc: Vec = fields.bcc; - - let draft = builder::build( - BuilderArgs { - from: None, - to: &to, - cc: &cc, - bcc: &bcc, - subject: fields.subject.as_deref(), - body: fields.body.as_deref(), - body_file: None, - attach: &[], - signature: None, - signature_file: None, - }, - None, - )?; - - let mut command = self.command.map(|cmd| shell(&cmd)); - let command = command.as_mut().unwrap_or( - &mut client - .account - .get_composer_mut(self.name.as_deref())? - .compose, - ); - - let raw = runner::run(command, &draft)?; - if raw.is_empty() { - bail!("composer `{command:?}` produced no output"); - } - - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) - } -} - -/// Fields extracted from a `mailto:` URI. Unrecognised query params -/// are silently ignored. -struct MailtoFields { - to: Vec, - cc: Vec, - bcc: Vec, - subject: Option, - body: Option, -} - -/// Parses a `mailto:` URI per RFC 6068. -/// -/// The path carries one or more comma-separated recipient addresses -/// (percent-decoded). The query string carries the headers `to`, `cc`, -/// `bcc`, `subject`, and `body`; addresses in `to` / `cc` / `bcc` may -/// themselves be comma-separated. Any other parameter is dropped. -fn parse_mailto_uri(uri: &str) -> Result { - let url = Url::parse(uri).map_err(|err| anyhow!("invalid mailto URI `{uri}`: {err}"))?; - if url.scheme() != "mailto" { - bail!("expected `mailto:` URI, got scheme `{}`", url.scheme()); - } - - let mut to = split_addresses(url.path()); - let mut cc = Vec::new(); - let mut bcc = Vec::new(); - let mut subject = None; - let mut body = None; - - for (key, value) in url.query_pairs() { - match key.as_ref().to_ascii_lowercase().as_str() { - "to" => to.extend(split_addresses(value.as_ref())), - "cc" => cc.extend(split_addresses(value.as_ref())), - "bcc" => bcc.extend(split_addresses(value.as_ref())), - "subject" => subject = Some(value.into_owned()), - "body" => body = Some(value.into_owned()), - _ => {} - } - } - - Ok(MailtoFields { - to, - cc, - bcc, - subject, - body, - }) -} - -/// Splits a comma-separated address list, percent-decodes each entry, -/// and drops the empties. Used both for the URI path and for the `to` -/// / `cc` / `bcc` query params (query values already come decoded -/// from `query_pairs`, but the comma split applies to both shapes). -fn split_addresses(raw: &str) -> Vec { - raw.split(',') - .filter_map(|part| { - let decoded = percent_decode_str(part).decode_utf8_lossy().into_owned(); - let trimmed = decoded.trim().to_owned(); - if trimmed.is_empty() { - None - } else { - Some(trimmed) - } - }) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn single_recipient() { - let f = parse_mailto_uri("mailto:bob@example.org").unwrap(); - assert_eq!(f.to, vec!["bob@example.org"]); - assert!(f.cc.is_empty()); - assert!(f.bcc.is_empty()); - assert!(f.subject.is_none()); - assert!(f.body.is_none()); - } - - #[test] - fn comma_separated_path() { - let f = parse_mailto_uri("mailto:a@x.org,b@x.org").unwrap(); - assert_eq!(f.to, vec!["a@x.org", "b@x.org"]); - } - - #[test] - fn percent_decoded_path() { - let f = parse_mailto_uri("mailto:bob%40example.org").unwrap(); - assert_eq!(f.to, vec!["bob@example.org"]); - } - - #[test] - fn subject_and_body_via_query() { - let f = parse_mailto_uri( - "mailto:bob@example.org?subject=Hello%20World&body=Hi%20Bob%2C%0AHow%20are%20you%3F", - ) - .unwrap(); - assert_eq!(f.subject.as_deref(), Some("Hello World")); - assert_eq!(f.body.as_deref(), Some("Hi Bob,\nHow are you?")); - } - - #[test] - fn cc_and_bcc_lists() { - let f = parse_mailto_uri( - "mailto:bob@example.org?cc=carol@example.org,dave@example.org&bcc=eve@example.org", - ) - .unwrap(); - assert_eq!(f.cc, vec!["carol@example.org", "dave@example.org"]); - assert_eq!(f.bcc, vec!["eve@example.org"]); - } - - #[test] - fn empty_path_with_to_param() { - let f = parse_mailto_uri("mailto:?to=bob@example.org&subject=Hi").unwrap(); - assert_eq!(f.to, vec!["bob@example.org"]); - assert_eq!(f.subject.as_deref(), Some("Hi")); - } - - #[test] - fn case_insensitive_query_keys() { - let f = parse_mailto_uri("mailto:bob@example.org?Subject=Hi&BODY=Yo").unwrap(); - assert_eq!(f.subject.as_deref(), Some("Hi")); - assert_eq!(f.body.as_deref(), Some("Yo")); - } - - #[test] - fn unknown_params_are_ignored() { - let f = parse_mailto_uri("mailto:bob@example.org?foo=bar&subject=Hi").unwrap(); - assert_eq!(f.subject.as_deref(), Some("Hi")); - assert!(f.body.is_none()); - } - - #[test] - fn non_mailto_scheme_is_rejected() { - assert!(parse_mailto_uri("https://example.org").is_err()); - } -} diff --git a/src/shared/messages/mod.rs b/src/shared/messages/mod.rs index 1f6c725e..24e8b600 100644 --- a/src/shared/messages/mod.rs +++ b/src/shared/messages/mod.rs @@ -20,16 +20,10 @@ pub mod arg; pub mod builder; pub mod cli; pub mod compose; -pub mod compose_with; pub mod copy; pub mod forward; -pub mod forward_with; -pub mod mailto; pub mod mv; pub mod output; pub mod read; -pub mod read_with; pub mod reply; -pub mod reply_with; -pub mod runner; pub mod send; diff --git a/src/shared/messages/mv.rs b/src/shared/messages/mv.rs index 60626368..d6aa71e1 100644 --- a/src/shared/messages/mv.rs +++ b/src/shared/messages/mv.rs @@ -49,7 +49,7 @@ pub struct MessageMoveCommand { } impl MessageMoveCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> { let ids: Vec<&str> = self.ids.inner.iter().map(String::as_str).collect(); client.move_messages(&self.from, &self.to, &ids)?; printer.out(Message::new("Message(s) successfully moved")) diff --git a/src/shared/messages/output.rs b/src/shared/messages/output.rs index 3a90fc13..510ed524 100644 --- a/src/shared/messages/output.rs +++ b/src/shared/messages/output.rs @@ -15,27 +15,35 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Post-composer routing: where the produced MIME bytes go. +//! Post-build routing: where the produced MIME bytes go. //! -//! Used by `compose` / `reply` / `forward` (and their `-with` -//! variants). The same `--save ` / `--send` flags can combine: -//! `--save Sent --send` sends the message *and* appends a copy to the -//! `Sent` mailbox. With neither flag, the raw bytes are written to -//! stdout — same shape as a manual `mml compile > out.eml`. +//! Used by the built-in flag composers `compose` / `reply` / +//! `forward`. The same `--save ` / `--send` flags can combine: +//! `--save Sent --send` sends the message and appends a copy to the +//! `Sent` mailbox. The mailbox name is resolved through +//! [`Account::resolve_mailbox`] before the backend call so user +//! aliases (`mailbox.alias.sent = "[Gmail]/Sent Mail"`) apply. With +//! neither flag, the raw bytes are written to stdout: same shape as +//! a manual `mml compile > out.eml`. +//! +//! [`Account::resolve_mailbox`]: crate::account::context::Account::resolve_mailbox use std::io::{Write, stdout}; use anyhow::Result; +use io_email::flag::{Flag, IanaFlag}; use pimalaya_cli::printer::{Message, Printer}; -use crate::shared::client::EmailClient; +use crate::{account::context::Account, shared::client::EmailClient}; /// Routes `raw` through the requested combination of side-effects. -/// `save` writes a copy to the named mailbox before sending; `send` -/// pushes the message through the configured SMTP / JMAP send path. -/// With neither set, dumps `raw` to stdout and returns. +/// `save` writes a copy to the named mailbox (resolved through the +/// account's alias map) before sending; `send` pushes the message +/// through the configured SMTP / JMAP send path. With neither set, +/// dumps `raw` to stdout and returns. pub fn route( printer: &mut impl Printer, + account: &Account, client: &mut EmailClient, raw: Vec, save: Option<&str>, @@ -47,14 +55,21 @@ pub fn route( return Ok(()); } - if let Some(mailbox) = save { - client.add_message(mailbox, &[], raw.clone())?; + if let Some(name) = save { + let mailbox = account.resolve_mailbox(name); + client.add_message(mailbox, &[Flag::from_iana(IanaFlag::Seen)], raw.clone())?; } if send { client.send_message(raw)?; - return printer.out(Message::new("Message successfully sent")); } - printer.out(Message::new("Message saved")) + let msg = match (save.is_some(), send) { + (true, true) => "Message successfully saved and sent", + (false, true) => "Message successfully saved", + (true, false) => "Message successfully sent", + (false, false) => "Nothing done with this message", + }; + + printer.out(Message::new(msg)) } diff --git a/src/shared/messages/read.rs b/src/shared/messages/read.rs index 1a8a641a..ea6e5a88 100644 --- a/src/shared/messages/read.rs +++ b/src/shared/messages/read.rs @@ -33,8 +33,8 @@ use crate::shared::client::EmailClient; /// Fetches the message and renders headers + text bodies. Pass /// `--raw` to dump the original RFC 5322 bytes to stdout instead, /// or `--json` to emit the parsed message as JSON. For a custom -/// pretty-printer (mml interpret, w3m, your own viewer, …) use -/// `read-with `. +/// pretty-printer (`mml interpret`, w3m, your own viewer), pipe the +/// `--raw` output into the renderer of your choice. #[derive(Debug, Parser)] pub struct MessageReadCommand { /// Identifier of the message (IMAP UID, JMAP email id, or Maildir @@ -59,7 +59,7 @@ pub struct MessageReadCommand { } impl MessageReadCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> { if self.raw && printer.is_json() { bail!("`--raw` and `--json` cannot be combined"); } diff --git a/src/shared/messages/read_with.rs b/src/shared/messages/read_with.rs deleted file mode 100644 index 621463ad..00000000 --- a/src/shared/messages/read_with.rs +++ /dev/null @@ -1,76 +0,0 @@ -// This file is part of Himalaya, a CLI to manage emails. -// -// Copyright (C) 2022-2026 soywod -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use std::io::{Write, stdout}; - -use anyhow::Result; -use clap::Parser; -use pimalaya_cli::printer::Printer; -use pimalaya_config::command::shell; - -use crate::shared::{client::EmailClient, messages::runner}; - -/// Read a message by delegating to a user-defined reader. -/// -/// Fetches the source and pipes it on stdin to the named (or -/// default) reader. The reader's stdout is forwarded to the -/// terminal — zero bytes is fine (the reader may have spawned its -/// own UI), non-empty bytes are written as-is. -#[derive(Debug, Parser)] -pub struct MessageReadWithCommand { - /// Identifier of the message. - #[arg(value_name = "ID")] - pub id: String, - - /// Mailbox the message lives in. Ignored for JMAP. - #[arg( - long = "mailbox", - short = 'm', - value_name = "NAME", - default_value = "Inbox" - )] - pub mailbox: String, - - /// Name of an entry in `[message.reader.*]`. Optional — when - /// omitted, the reader flagged `default = true` is used. - #[arg(value_name = "NAME", conflicts_with = "command")] - pub name: Option, - - /// Ad-hoc shell command, mutually exclusive with ``. - #[arg(long, value_name = "SHELL")] - pub command: Option, -} - -impl MessageReadWithCommand { - pub fn execute(self, _printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let source = client.get_message(&self.mailbox, &self.id)?; - - let mut command = self.command.map(|cmd| shell(&cmd)); - let command = command - .as_mut() - .unwrap_or(&mut client.account.get_reader_mut(self.name.as_deref())?.command); - - let bytes = runner::run(command, &source)?; - - if !bytes.is_empty() { - let mut out = stdout().lock(); - out.write_all(&bytes)?; - } - - Ok(()) - } -} diff --git a/src/shared/messages/reply.rs b/src/shared/messages/reply.rs index 64def89c..755bc95f 100644 --- a/src/shared/messages/reply.rs +++ b/src/shared/messages/reply.rs @@ -21,6 +21,7 @@ use anyhow::Result; use clap::Parser; use pimalaya_cli::printer::Printer; +use crate::account::context::Account; use crate::shared::{ client::EmailClient, messages::{ @@ -35,7 +36,9 @@ use crate::shared::{ /// and the `Re:` subject, optionally derives recipients from /// `Reply-To`/`From`, and quotes the source text body. The produced /// MIME is written to stdout, or routed via `--save` / `--send`. -/// For non-default composition, use `reply-with `. +/// For richer composition, pipe `messages read ` into a +/// standalone composer (`mml reply`, etc.) and feed its output back +/// into `messages send` / `messages add`. #[derive(Debug, Parser)] pub struct MessageReplyCommand { /// Identifier of the source message (IMAP UID, JMAP id, Maildir @@ -112,7 +115,12 @@ pub struct MessageReplyCommand { } impl MessageReplyCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute( + self, + printer: &mut impl Printer, + account: &mut Account, + client: &mut EmailClient, + ) -> Result<()> { let source = client.get_message(&self.mailbox, &self.id)?; let raw = builder::build( @@ -136,6 +144,13 @@ impl MessageReplyCommand { }), )?; - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) + output::route( + printer, + account, + client, + raw, + self.save.as_deref(), + self.send, + ) } } diff --git a/src/shared/messages/reply_with.rs b/src/shared/messages/reply_with.rs deleted file mode 100644 index b4c9b1bb..00000000 --- a/src/shared/messages/reply_with.rs +++ /dev/null @@ -1,80 +0,0 @@ -// This file is part of Himalaya, a CLI to manage emails. -// -// Copyright (C) 2022-2026 soywod -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -use anyhow::{Result, bail}; -use clap::Parser; -use pimalaya_cli::printer::Printer; -use pimalaya_config::command::shell; - -use crate::shared::{ - client::EmailClient, - messages::{output, runner}, -}; - -/// Reply to a message by delegating to a user-defined composer. -/// -/// Fetches the source message, then runs the named (or default) -/// composer with the source MIME piped on stdin. The composer must -/// consume stdin first if it wants user interaction — TUI composers -/// can re-open `/dev/tty` once stdin is drained (vim/less/fzf all do -/// this). The produced MIME is routed through `--save` / `--send`, -/// or stdout if neither is set. -#[derive(Debug, Parser)] -pub struct MessageReplyWithCommand { - /// Identifier of the source message. - #[arg(value_name = "ID")] - pub id: String, - - /// Mailbox the source message lives in. Ignored for JMAP. - #[arg( - long = "mailbox", - short = 'm', - value_name = "NAME", - default_value = "Inbox" - )] - pub mailbox: String, - - #[arg(value_name = "NAME", conflicts_with = "command")] - pub name: Option, - - #[arg(long, value_name = "SHELL")] - pub command: Option, - - #[arg(long, value_name = "MAILBOX")] - pub save: Option, - - #[arg(long)] - pub send: bool, -} - -impl MessageReplyWithCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { - let source = client.get_message(&self.mailbox, &self.id)?; - - let mut command = self.command.map(|cmd| shell(&cmd)); - let command = command - .as_mut() - .unwrap_or(&mut client.account.get_composer_mut(self.name.as_deref())?.reply); - - let raw = runner::run(command, &source)?; - if raw.is_empty() { - bail!("composer `{command:?}` produced no output"); - } - - output::route(printer, &mut client, raw, self.save.as_deref(), self.send) - } -} diff --git a/src/shared/messages/runner.rs b/src/shared/messages/runner.rs deleted file mode 100644 index 4a15360f..00000000 --- a/src/shared/messages/runner.rs +++ /dev/null @@ -1,69 +0,0 @@ -// This file is part of Himalaya, a CLI to manage emails. -// -// Copyright (C) 2022-2026 soywod -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU Affero General Public License as published by the Free -// Software Foundation, either version 3 of the License, or (at your option) any -// later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more -// details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//! Spawns user-defined composer and reader commands. -//! -//! A composer/reader is just a shell command line invoked via -//! `sh -c`. Himalaya pipes source MIME bytes (empty for new messages) -//! into the child's stdin, captures stdout (the produced MIME draft -//! or the interpreted text), and inherits stderr so the spawned -//! command can prompt the user or print errors directly to the -//! terminal. TUI composers that need interactive input can re-open -//! `/dev/tty` once they've consumed stdin — standard Unix practice. - -use std::{ - io::Write, - process::{Command, Stdio}, -}; - -use anyhow::{Result, anyhow, bail}; - -/// Spawns `command`, writes `stdin_bytes` to its -/// stdin, and returns the captured stdout bytes. -/// Stderr is inherited. -/// Bails on a non-zero exit status. -pub fn run(command: &mut Command, stdin_bytes: &[u8]) -> Result> { - let mut child = command - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - .map_err(|err| anyhow!("spawn `{command:?}`: {err}"))?; - - if let Some(mut stdin) = child.stdin.take() { - stdin - .write_all(stdin_bytes) - .map_err(|err| anyhow!("write stdin to `{command:?}`: {err}"))?; - } - - let output = child - .wait_with_output() - .map_err(|err| anyhow!("wait `{command:?}`: {err}"))?; - - if !output.status.success() { - bail!( - "`{command:?}` exited with status {}", - output - .status - .code() - .map(|c| c.to_string()) - .unwrap_or_else(|| "?".to_string()) - ); - } - - Ok(output.stdout) -} diff --git a/src/shared/messages/send.rs b/src/shared/messages/send.rs index 116fc45a..df6cd5ae 100644 --- a/src/shared/messages/send.rs +++ b/src/shared/messages/send.rs @@ -37,7 +37,7 @@ pub struct MessageSendCommand { } impl MessageSendCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> { let raw = self.message.parse()?.into_bytes(); client.send_message(raw)?; printer.out(Message::new("Message successfully sent")) diff --git a/src/smtp/cli.rs b/src/smtp/cli.rs index d47abf12..75437ccc 100644 --- a/src/smtp/cli.rs +++ b/src/smtp/cli.rs @@ -35,7 +35,7 @@ pub enum SmtpCommand { } impl SmtpCommand { - pub fn execute(self, printer: &mut impl Printer, client: SmtpClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut SmtpClient) -> Result<()> { match self { Self::Messages(cmd) => cmd.execute(printer, client), } diff --git a/src/smtp/client.rs b/src/smtp/client.rs index 351b7d68..0a26e2fa 100644 --- a/src/smtp/client.rs +++ b/src/smtp/client.rs @@ -15,12 +15,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Himalaya wrapper around [`io_smtp::client::SmtpClientStd`] that -//! bundles the merged [`Account`] alongside the live SMTP client. +//! Himalaya wrapper around [`io_smtp::client::SmtpClientStd`]. //! //! Built up front by the dispatch layer (`crate::cli`) via //! [`build_smtp_client`] and handed down to every SMTP-specific -//! subcommand. SMTP send is stateless after auth, so no session +//! subcommand, together with the merged [`Account`] as a sibling +//! argument. SMTP send is stateless after auth, so no session //! context needs to follow the stream. use std::{ @@ -39,21 +39,19 @@ use crate::{account::context::Account, cli::load_or_wizard, config::SmtpConfig}; pub struct SmtpClient { inner: Inner, - #[allow(dead_code)] - pub account: Account, } impl SmtpClient { /// Opens the SMTP connection (TCP/TLS/STARTTLS, greeting, EHLO, - /// SASL) then wraps the resulting client alongside `account`. - pub fn new(config: SmtpConfig, account: Account) -> Result { + /// SASL). + pub fn new(config: SmtpConfig) -> Result { let mut tls: Tls = config.tls.into(); tls.rustls.alpn = vec!["smtp".into()]; let sasl: Option = config.sasl.map(Sasl::try_from).transpose()?; let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into(); let server = parse_smtp_server(&config.server)?; let inner = Inner::connect(&server, &tls, config.starttls, domain, sasl)?; - Ok(Self { inner, account }) + Ok(Self { inner }) } } @@ -89,11 +87,13 @@ impl DerefMut for SmtpClient { /// Loads the configuration, picks the active account, builds the /// merged [`Account`] then opens the SMTP session. Bails when the -/// account has no `[smtp]` block. +/// account has no `[smtp]` block. Returns the live client paired +/// with the merged account so subcommands receive both as sibling +/// arguments. pub fn build_smtp_client( config_paths: &[PathBuf], account_name: Option<&str>, -) -> Result { +) -> Result<(Account, SmtpClient)> { let mut config = load_or_wizard(config_paths)?; let (name, mut ac) = config .take_account(account_name)? @@ -103,5 +103,6 @@ pub fn build_smtp_client( .take() .ok_or_else(|| anyhow!("SMTP config is missing for account `{name}`"))?; let account = Account::from(config).merge(Account::from(ac)); - SmtpClient::new(smtp_config, account) + let client = SmtpClient::new(smtp_config)?; + Ok((account, client)) } diff --git a/src/smtp/message/cli.rs b/src/smtp/message/cli.rs index 1b71b1d7..78c5c317 100644 --- a/src/smtp/message/cli.rs +++ b/src/smtp/message/cli.rs @@ -32,7 +32,7 @@ pub enum SmtpMessageCommand { } impl SmtpMessageCommand { - pub fn execute(self, printer: &mut impl Printer, client: SmtpClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut SmtpClient) -> Result<()> { match self { Self::Send(cmd) => cmd.execute(printer, client), } diff --git a/src/smtp/message/send.rs b/src/smtp/message/send.rs index fa248b93..73b70d47 100644 --- a/src/smtp/message/send.rs +++ b/src/smtp/message/send.rs @@ -41,7 +41,7 @@ pub struct SmtpMessageSendCommand { } impl SmtpMessageSendCommand { - pub fn execute(self, printer: &mut impl Printer, mut client: SmtpClient) -> Result<()> { + pub fn execute(self, printer: &mut impl Printer, client: &mut SmtpClient) -> Result<()> { let message = self.message.parse()?; let (reverse_path, forward_paths) = into_smtp_msg(message.as_bytes())?; client.send(reverse_path, forward_paths, message.into_bytes())?; diff --git a/src/wizard/discover.rs b/src/wizard/discover.rs index 34fdf4d3..24b246c2 100644 --- a/src/wizard/discover.rs +++ b/src/wizard/discover.rs @@ -116,7 +116,6 @@ pub fn run_or_exit(target: &Path) -> Result { table: Default::default(), envelope: Default::default(), mailbox: Default::default(), - message: Default::default(), attachment: Default::default(), account: Default::default(), accounts: HashMap::from([(account_name, account)]),