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.
This commit is contained in:
Clément DOUIN
2026-06-01 15:19:45 +02:00
parent 3a1a981b8c
commit 662bd26eb1
151 changed files with 864 additions and 1291 deletions
+8
View File
@@ -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 <PATH>` 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 <mailbox>` 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
+1 -1
View File
@@ -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`).
+8 -9
View File
@@ -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 <path-or-raw>` split into the explicit `--file <PATH>` and positional `<raw>`.
- 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 <URI>` 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 <PATH>` 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.<name>]` and `[message.reader.<name>]`, 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).
+9 -29
View File
@@ -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 <URI>` 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
-26
View File
@@ -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 <name>`, 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
# --------------------------------------------------------------------------------
+3 -60
View File
@@ -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<String, String>,
/// User-defined composers. Only sourced from the global
/// [`Config`]; account-level configs do not override these.
pub composer: HashMap<String, ComposerConfig>,
/// User-defined readers. See [`Account::composer`].
pub reader: HashMap<String, ReaderConfig>,
}
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 <name> 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 <name> 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<Config> 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<AccountConfig> for Account {
attachments_list_table: config.attachment.list.table,
mailbox_alias: lowercase_alias_keys(config.mailbox.alias),
composer: HashMap::new(),
reader: HashMap::new(),
}
}
}
+20 -20
View File
@@ -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
+1 -80
View File
@@ -15,13 +15,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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<Color>,
}
/// 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<String, ComposerConfig>,
#[serde(default)]
pub reader: HashMap<String, ReaderConfig>,
}
/// Single composer entry under `[message.composer.<name>]`.
///
/// 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.<name>]`.
#[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`]
+12 -6
View File
@@ -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),
}
}
}
+14 -13
View File
@@ -15,12 +15,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! 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<Self> {
pub fn new(config: ImapConfig) -> Result<Self> {
let mut tls: Tls = config.tls.into();
tls.rustls.alpn = vec!["imap".into()];
let sasl: Option<Sasl> = 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<ImapClient> {
) -> 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))
}
+11 -5
View File
@@ -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),
}
}
+8 -2
View File
@@ -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(),
};
+13 -7
View File
@@ -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),
};
+10 -4
View File
@@ -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() })
+8 -2
View File
@@ -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)?;
+2 -2
View File
@@ -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()
};
+1 -1
View File
@@ -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 {
+8 -2
View File
@@ -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),
+9 -3
View File
@@ -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,
};
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+8 -2
View File
@@ -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<IString<'static>, 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()
+9 -3
View File
@@ -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),
+1 -1
View File
@@ -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"))
}
+1 -1
View File
@@ -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"))
+1 -1
View File
@@ -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"))
+1 -1
View File
@@ -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 {
+9 -3
View File
@@ -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(),
};
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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)?;
+1 -1
View File
@@ -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"))
+8 -2
View File
@@ -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(),
};
+1 -1
View File
@@ -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"))
+1 -1
View File
@@ -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"))
}
+1 -1
View File
@@ -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"))
+8 -2
View File
@@ -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),
}
+1 -1
View File
@@ -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 {
+8 -4
View File
@@ -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)?;
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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)?;
+13 -7
View File
@@ -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),
}
}
+9 -12
View File
@@ -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<Self> {
/// discovery).
pub fn new(config: JmapConfig) -> Result<Self> {
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<JmapClient> {
) -> 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
+9 -3
View File
@@ -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),
+1 -1
View File
@@ -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<String, bool> =
self.mailbox_id.into_iter().map(|m| (m, true)).collect();
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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)?;
+17 -11
View File
@@ -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,
};
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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 {
+17 -11
View File
@@ -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,
};
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+8 -2
View File
@@ -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),
+1 -1
View File
@@ -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(),
+1 -1
View File
@@ -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 {
+8 -2
View File
@@ -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,
};
+1 -1
View File
@@ -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,
+9 -3
View File
@@ -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),
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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 },
+12 -6
View File
@@ -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,
};
+12 -6
View File
@@ -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,
};
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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("-")
{
+1 -1
View File
@@ -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() {
+10 -4
View File
@@ -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),
}
}
+8 -2
View File
@@ -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(),
};
+8 -2
View File
@@ -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,
};
+8 -2
View File
@@ -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,
};
+8 -2
View File
@@ -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),
}
}
}
+8 -2
View File
@@ -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,
})
}
+8 -2
View File
@@ -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),
}
}
+8 -2
View File
@@ -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,
};
+1 -1
View File
@@ -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))
+10 -4
View File
@@ -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),
}
}
}
+8 -8
View File
@@ -15,8 +15,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! 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<M2dirClient> {
) -> 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)))
}
+1 -1
View File
@@ -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"))
+1 -1
View File
@@ -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)?;
+9 -3
View File
@@ -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),
}
}
}
+8 -2
View File
@@ -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(),
};
+13 -7
View File
@@ -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,
};
+1 -1
View File
@@ -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)?;
+8 -2
View File
@@ -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),
+9 -3
View File
@@ -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(),
};
+1 -1
View File
@@ -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)?;
+1 -1
View File
@@ -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)?;
+9 -3
View File
@@ -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(),
};
+1 -1
View File
@@ -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),
+1 -1
View File
@@ -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)?;
+1 -1
View File
@@ -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)?;
+1 -1
View File
@@ -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)?;
+1 -1
View File
@@ -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)?;
+10 -4
View File
@@ -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),
}
}
}
+10 -13
View File
@@ -15,12 +15,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! 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<MaildirClient> {
) -> 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)))
}
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+9 -3
View File
@@ -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),
}
}
}
+8 -2
View File
@@ -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(),
};

Some files were not shown because too many files have changed in this diff Show More