mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 11:27:53 +08:00
feat: add message send --save and message save --send args
This commit is contained in:
@@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Brought the `m2dir` backend to feature parity with `maildir` at the CLI level: `m2dir messages {save, get, read, export}`, `m2dir flags {list, add, set, remove}`, `m2dir envelopes {get, list}`. Flags are free-form UTF-8 strings persisted in the `.meta/<id>.flags` metadata file. Still missing relative to `maildir`: mailbox `rename`, message `copy` and `move` (need io-m2dir lib support first).
|
||||
|
||||
- Added `--save <MAILBOX>` to `messages send`, mirroring the existing flag on `messages compose` / `reply` / `forward`. Sends the message and appends a copy of it to the named mailbox. The mailbox name is resolved through the account's `[mailbox.alias]` map.
|
||||
|
||||
- Added `--send` to `messages add` (alias `messages save`), mirroring `messages send --save`. Appends the message to the mandatory `--mailbox` first and then pushes it through the account's send path. Success line now reads "Message {id} successfully added and sent" when `--send` is set.
|
||||
|
||||
### Changed
|
||||
|
||||
- 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).
|
||||
@@ -25,6 +29,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- 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"`.
|
||||
|
||||
- Extended mailbox-alias resolution across every shared `messages` subcommand that takes a mailbox name: `add -m`, `read -m`, `reply -m`, `forward -m`, `copy --from/--to`, `move --from/--to`. Previously the value was passed verbatim to the backend; now each goes through `account.resolve_mailbox`. The shared `mailboxes` / `envelopes` / `flags` / `attachments` commands already resolved through `MailboxArg`; this brings the `messages` group in line.
|
||||
|
||||
- Fixed the success-message dispatch in `handler::route`: `(save, send)` cases `(true, false)` and `(false, true)` were swapped, printing "saved" after a pure send and "sent" after a pure save.
|
||||
|
||||
### 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).
|
||||
|
||||
+39
-11
@@ -23,20 +23,33 @@ use io_email::flag::Flag;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::shared::{client::EmailClient, flags::arg::FlagArg, messages::arg::MessageArg};
|
||||
use crate::account::context::Account;
|
||||
use crate::shared::{
|
||||
client::EmailClient,
|
||||
flags::arg::FlagArg,
|
||||
messages::{
|
||||
arg::MessageArg,
|
||||
handler::{self, Outcome},
|
||||
},
|
||||
};
|
||||
|
||||
/// Add a raw RFC 5322 message to a mailbox.
|
||||
///
|
||||
/// The message can be passed as a positional file path, an inline raw
|
||||
/// string, or piped via stdin (see [`MessageArg`] for resolution
|
||||
/// order). IMAP appends via `APPEND` (RFC 3501); JMAP uploads the
|
||||
/// blob and imports it via `Email/import` (the destination mailbox
|
||||
/// is resolved from `--mailbox` by exact-match name); Maildir writes
|
||||
/// a new file under the target maildir's `cur/` subdir using the
|
||||
/// standard tmp-then-rename delivery protocol.
|
||||
/// order). The destination is resolved through the account's
|
||||
/// `[mailbox.alias]` map before the backend call. Pass `--send` to
|
||||
/// also push the message through the account's send path after the
|
||||
/// append (mirrors `messages send --save <MAILBOX>`).
|
||||
///
|
||||
/// IMAP appends via `APPEND` (RFC 3501); JMAP uploads the blob and
|
||||
/// imports it via `Email/import` (the destination mailbox is
|
||||
/// resolved by exact-match name); Maildir writes a new file under
|
||||
/// the target maildir's `cur/` subdir using the standard
|
||||
/// tmp-then-rename delivery protocol.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct MessageAddCommand {
|
||||
/// Destination mailbox name or path. Mandatory.
|
||||
/// Destination mailbox name or alias. Mandatory.
|
||||
#[arg(long = "mailbox", short = 'm', value_name = "NAME")]
|
||||
pub mailbox: String,
|
||||
|
||||
@@ -44,26 +57,41 @@ pub struct MessageAddCommand {
|
||||
#[arg(long = "flag", short = 'f', value_name = "FLAG", num_args = 0..)]
|
||||
pub flag: Vec<FlagArg>,
|
||||
|
||||
/// Send the message after appending it. Combines with the
|
||||
/// mandatory `--mailbox` to save-then-send.
|
||||
#[arg(long)]
|
||||
pub send: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub message: MessageArg,
|
||||
}
|
||||
|
||||
impl MessageAddCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> {
|
||||
pub fn execute(
|
||||
self,
|
||||
printer: &mut impl Printer,
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
let raw = self.message.parse()?.into_bytes();
|
||||
let flags: Vec<Flag> = self.flag.iter().map(Into::into).collect();
|
||||
let id = client.add_message(&self.mailbox, &flags, raw)?;
|
||||
printer.out(MessageAddOutput { id })
|
||||
let outcome = handler::apply(account, client, raw, &flags, Some(&self.mailbox), self.send)?;
|
||||
let Outcome::Saved { id, sent } = outcome else {
|
||||
unreachable!("--mailbox is mandatory; handler::apply always reports Saved");
|
||||
};
|
||||
printer.out(MessageAddOutput { id, sent })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MessageAddOutput {
|
||||
id: String,
|
||||
sent: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for MessageAddOutput {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Message {} successfully added", self.id)
|
||||
let suffix = if self.sent { " and sent" } else { "" };
|
||||
write!(f, "Message {} successfully added{suffix}", self.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +62,14 @@ impl MessageCommand {
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Self::Add(cmd) => cmd.execute(printer, client),
|
||||
Self::Add(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Compose(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Copy(cmd) => cmd.execute(printer, client),
|
||||
Self::Copy(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Forward(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Move(cmd) => cmd.execute(printer, client),
|
||||
Self::Read(cmd) => cmd.execute(printer, client),
|
||||
Self::Move(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Read(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Reply(cmd) => cmd.execute(printer, account, client),
|
||||
Self::Send(cmd) => cmd.execute(printer, client),
|
||||
Self::Send(cmd) => cmd.execute(printer, account, client),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::shared::{
|
||||
client::EmailClient,
|
||||
messages::{
|
||||
builder::{self, BuilderArgs},
|
||||
output,
|
||||
handler,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ impl MessageComposeCommand {
|
||||
None,
|
||||
)?;
|
||||
|
||||
output::route(
|
||||
handler::route(
|
||||
printer,
|
||||
account,
|
||||
client,
|
||||
|
||||
@@ -19,21 +19,24 @@ use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::account::context::Account;
|
||||
use crate::shared::{client::EmailClient, flags::arg::MessageIdsArg};
|
||||
|
||||
/// Copy message(s) from one mailbox to another within the active
|
||||
/// account.
|
||||
///
|
||||
/// IMAP uses `UID COPY` (RFC 3501); JMAP uses `Email/set` patches that
|
||||
/// add the destination to each email's `mailboxIds`; Maildir copies
|
||||
/// the underlying file. Cross-account / cross-backend copy is out of
|
||||
/// Both `--from` and `--to` are resolved through the account's
|
||||
/// `[mailbox.alias]` map before the backend call. IMAP uses
|
||||
/// `UID COPY` (RFC 3501); JMAP uses `Email/set` patches that add the
|
||||
/// destination to each email's `mailboxIds`; Maildir copies the
|
||||
/// underlying file. Cross-account / cross-backend copy is out of
|
||||
/// scope.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct MessageCopyCommand {
|
||||
#[command(flatten)]
|
||||
pub ids: MessageIdsArg,
|
||||
|
||||
/// Source mailbox name or path (IMAP/Maildir). For JMAP this is
|
||||
/// Source mailbox name or alias (IMAP/Maildir). For JMAP this is
|
||||
/// resolved by exact-match name against `Mailbox/get`.
|
||||
#[arg(
|
||||
long = "from",
|
||||
@@ -43,15 +46,22 @@ pub struct MessageCopyCommand {
|
||||
)]
|
||||
pub from: String,
|
||||
|
||||
/// Destination mailbox name or path. Mandatory.
|
||||
/// Destination mailbox name or alias. Mandatory.
|
||||
#[arg(long = "to", short = 't', value_name = "NAME")]
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
impl MessageCopyCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> {
|
||||
pub fn execute(
|
||||
self,
|
||||
printer: &mut impl Printer,
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
let from = account.resolve_mailbox(&self.from).to_owned();
|
||||
let to = account.resolve_mailbox(&self.to).to_owned();
|
||||
let ids: Vec<&str> = self.ids.inner.iter().map(String::as_str).collect();
|
||||
client.copy_messages(&self.from, &self.to, &ids)?;
|
||||
client.copy_messages(&from, &to, &ids)?;
|
||||
printer.out(Message::new("Message(s) successfully copied"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::shared::{
|
||||
client::EmailClient,
|
||||
messages::{
|
||||
builder::{self, BuilderArgs, PostingStyle, SourceArgs, SourceMode},
|
||||
output,
|
||||
handler,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -110,7 +110,8 @@ impl MessageForwardCommand {
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
let source = client.get_message(&self.mailbox, &self.id)?;
|
||||
let mailbox = account.resolve_mailbox(&self.mailbox).to_owned();
|
||||
let source = client.get_message(&mailbox, &self.id)?;
|
||||
|
||||
let raw = builder::build(
|
||||
BuilderArgs {
|
||||
@@ -133,7 +134,7 @@ impl MessageForwardCommand {
|
||||
}),
|
||||
)?;
|
||||
|
||||
output::route(
|
||||
handler::route(
|
||||
printer,
|
||||
account,
|
||||
client,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Post-build routing: where the produced MIME bytes go.
|
||||
//!
|
||||
//! [`apply`] performs the requested side-effects (stdout dump,
|
||||
//! save-to-mailbox, send, or save-then-send) and returns an
|
||||
//! [`Outcome`] describing what happened. [`route`] is a thin wrapper
|
||||
//! that prints a generic "Message successfully X" line based on the
|
||||
//! outcome, used by the built-in flag composers
|
||||
//! (`compose` / `reply` / `forward`) and by `messages send`.
|
||||
//!
|
||||
//! Callers that need a richer success line (e.g. `messages add`
|
||||
//! reporting the appended backend id) call [`apply`] directly and
|
||||
//! render their own output from the [`Outcome`].
|
||||
//!
|
||||
//! [`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::{account::context::Account, shared::client::EmailClient};
|
||||
|
||||
/// What [`apply`] actually did with `raw`.
|
||||
pub enum Outcome {
|
||||
/// Neither `save` nor `send`: bytes were written to stdout.
|
||||
Stdout,
|
||||
/// Saved to a mailbox; `id` is the backend-assigned id of the
|
||||
/// new message, `sent` is `true` when `send` was also requested.
|
||||
Saved { id: String, sent: bool },
|
||||
/// Sent only (no save). The send path returns no id.
|
||||
Sent,
|
||||
}
|
||||
|
||||
/// Performs the requested combination of side-effects without
|
||||
/// printing anything. `save` writes a copy to the named mailbox
|
||||
/// (resolved through the account's alias map) with `flags` attached;
|
||||
/// `send` pushes the message through the configured SMTP / JMAP send
|
||||
/// path. With neither set, dumps `raw` to stdout.
|
||||
pub fn apply(
|
||||
account: &Account,
|
||||
client: &mut EmailClient,
|
||||
raw: Vec<u8>,
|
||||
flags: &[Flag],
|
||||
save: Option<&str>,
|
||||
send: bool,
|
||||
) -> Result<Outcome> {
|
||||
if !send && save.is_none() {
|
||||
let mut out = stdout().lock();
|
||||
out.write_all(&raw)?;
|
||||
return Ok(Outcome::Stdout);
|
||||
}
|
||||
|
||||
let saved_id = match save {
|
||||
Some(name) => {
|
||||
let mailbox = account.resolve_mailbox(name);
|
||||
Some(client.add_message(mailbox, flags, raw.clone())?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
if send {
|
||||
client.send_message(raw)?;
|
||||
}
|
||||
|
||||
Ok(match saved_id {
|
||||
Some(id) => Outcome::Saved { id, sent: send },
|
||||
None => Outcome::Sent,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generic wrapper over [`apply`]: hard-codes `\Seen` as the saved
|
||||
/// flag and prints a "Message successfully X" line. Used by the
|
||||
/// built-in flag composers and by `messages send`.
|
||||
pub fn route(
|
||||
printer: &mut impl Printer,
|
||||
account: &Account,
|
||||
client: &mut EmailClient,
|
||||
raw: Vec<u8>,
|
||||
save: Option<&str>,
|
||||
send: bool,
|
||||
) -> Result<()> {
|
||||
let outcome = apply(
|
||||
account,
|
||||
client,
|
||||
raw,
|
||||
&[Flag::from_iana(IanaFlag::Seen)],
|
||||
save,
|
||||
send,
|
||||
)?;
|
||||
let msg = match outcome {
|
||||
Outcome::Stdout => return Ok(()),
|
||||
Outcome::Saved { sent: true, .. } => "Message successfully saved and sent",
|
||||
Outcome::Saved { sent: false, .. } => "Message successfully saved",
|
||||
Outcome::Sent => "Message successfully sent",
|
||||
};
|
||||
printer.out(Message::new(msg))
|
||||
}
|
||||
@@ -22,8 +22,8 @@ pub mod cli;
|
||||
pub mod compose;
|
||||
pub mod copy;
|
||||
pub mod forward;
|
||||
pub mod handler;
|
||||
pub mod mv;
|
||||
pub mod output;
|
||||
pub mod read;
|
||||
pub mod reply;
|
||||
pub mod send;
|
||||
|
||||
@@ -19,21 +19,24 @@ use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::account::context::Account;
|
||||
use crate::shared::{client::EmailClient, flags::arg::MessageIdsArg};
|
||||
|
||||
/// Move message(s) from one mailbox to another within the active
|
||||
/// account.
|
||||
///
|
||||
/// IMAP uses `UID MOVE` (RFC 6851); JMAP uses `Email/set` patches that
|
||||
/// remove the source and add the destination from each email's
|
||||
/// `mailboxIds`; Maildir renames the underlying file. Cross-account /
|
||||
/// cross-backend move is out of scope.
|
||||
/// Both `--from` and `--to` are resolved through the account's
|
||||
/// `[mailbox.alias]` map before the backend call. IMAP uses
|
||||
/// `UID MOVE` (RFC 6851); JMAP uses `Email/set` patches that remove
|
||||
/// the source and add the destination from each email's
|
||||
/// `mailboxIds`; Maildir renames the underlying file. Cross-account
|
||||
/// / cross-backend move is out of scope.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct MessageMoveCommand {
|
||||
#[command(flatten)]
|
||||
pub ids: MessageIdsArg,
|
||||
|
||||
/// Source mailbox name or path (IMAP/Maildir). For JMAP this is
|
||||
/// Source mailbox name or alias (IMAP/Maildir). For JMAP this is
|
||||
/// resolved by exact-match name against `Mailbox/get`.
|
||||
#[arg(
|
||||
long = "from",
|
||||
@@ -43,15 +46,22 @@ pub struct MessageMoveCommand {
|
||||
)]
|
||||
pub from: String,
|
||||
|
||||
/// Destination mailbox name or path. Mandatory.
|
||||
/// Destination mailbox name or alias. Mandatory.
|
||||
#[arg(long = "to", short = 't', value_name = "NAME")]
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
impl MessageMoveCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> {
|
||||
pub fn execute(
|
||||
self,
|
||||
printer: &mut impl Printer,
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
let from = account.resolve_mailbox(&self.from).to_owned();
|
||||
let to = account.resolve_mailbox(&self.to).to_owned();
|
||||
let ids: Vec<&str> = self.ids.inner.iter().map(String::as_str).collect();
|
||||
client.move_messages(&self.from, &self.to, &ids)?;
|
||||
client.move_messages(&from, &to, &ids)?;
|
||||
printer.out(Message::new("Message(s) successfully moved"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Post-build routing: where the produced MIME bytes go.
|
||||
//!
|
||||
//! Used by the built-in flag composers `compose` / `reply` /
|
||||
//! `forward`. The same `--save <mbox>` / `--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::{account::context::Account, shared::client::EmailClient};
|
||||
|
||||
/// Routes `raw` through the requested combination of side-effects.
|
||||
/// `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<u8>,
|
||||
save: Option<&str>,
|
||||
send: bool,
|
||||
) -> Result<()> {
|
||||
if !send && save.is_none() {
|
||||
let mut out = stdout().lock();
|
||||
out.write_all(&raw)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -26,6 +26,7 @@ use mail_parser::{Message, MessageParser};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::account::context::Account;
|
||||
use crate::shared::client::EmailClient;
|
||||
|
||||
/// Read a message from the active account (built-in flag reader).
|
||||
@@ -42,8 +43,8 @@ pub struct MessageReadCommand {
|
||||
#[arg(value_name = "ID")]
|
||||
pub id: String,
|
||||
|
||||
/// Mailbox name or path (IMAP mailbox / Maildir path). Ignored for
|
||||
/// JMAP, which addresses messages by id directly.
|
||||
/// Mailbox name or alias (IMAP mailbox / Maildir path). Ignored
|
||||
/// for JMAP, which addresses messages by id directly.
|
||||
#[arg(
|
||||
long = "mailbox",
|
||||
short = 'm',
|
||||
@@ -59,12 +60,18 @@ pub struct MessageReadCommand {
|
||||
}
|
||||
|
||||
impl MessageReadCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> {
|
||||
pub fn execute(
|
||||
self,
|
||||
printer: &mut impl Printer,
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
if self.raw && printer.is_json() {
|
||||
bail!("`--raw` and `--json` cannot be combined");
|
||||
}
|
||||
|
||||
let raw = client.get_message(&self.mailbox, &self.id)?;
|
||||
let mailbox = account.resolve_mailbox(&self.mailbox).to_owned();
|
||||
let raw = client.get_message(&mailbox, &self.id)?;
|
||||
|
||||
if self.raw {
|
||||
let mut out = stdout().lock();
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::shared::{
|
||||
client::EmailClient,
|
||||
messages::{
|
||||
builder::{self, BuilderArgs, PostingStyle, SourceArgs, SourceMode},
|
||||
output,
|
||||
handler,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -121,7 +121,8 @@ impl MessageReplyCommand {
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
let source = client.get_message(&self.mailbox, &self.id)?;
|
||||
let mailbox = account.resolve_mailbox(&self.mailbox).to_owned();
|
||||
let source = client.get_message(&mailbox, &self.id)?;
|
||||
|
||||
let raw = builder::build(
|
||||
BuilderArgs {
|
||||
@@ -144,7 +145,7 @@ impl MessageReplyCommand {
|
||||
}),
|
||||
)?;
|
||||
|
||||
output::route(
|
||||
handler::route(
|
||||
printer,
|
||||
account,
|
||||
client,
|
||||
|
||||
@@ -17,9 +17,13 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::shared::{client::EmailClient, messages::arg::MessageArg};
|
||||
use crate::account::context::Account;
|
||||
use crate::shared::{
|
||||
client::EmailClient,
|
||||
messages::{arg::MessageArg, handler},
|
||||
};
|
||||
|
||||
/// Send a message via the active account.
|
||||
///
|
||||
@@ -29,17 +33,27 @@ use crate::shared::{client::EmailClient, messages::arg::MessageArg};
|
||||
///
|
||||
/// The message can be passed as a positional file path, an inline
|
||||
/// raw string, or piped via stdin (see [`MessageArg`] for resolution
|
||||
/// order).
|
||||
/// order). Pass `--save <MAILBOX>` to also append a copy of the
|
||||
/// sent message to a mailbox; the mailbox name is resolved through
|
||||
/// the account's `[mailbox.alias]` map before the backend call.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct MessageSendCommand {
|
||||
/// Append a copy of the sent message to this mailbox.
|
||||
#[arg(long, value_name = "MAILBOX")]
|
||||
pub save: Option<String>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub message: MessageArg,
|
||||
}
|
||||
|
||||
impl MessageSendCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut EmailClient) -> Result<()> {
|
||||
pub fn execute(
|
||||
self,
|
||||
printer: &mut impl Printer,
|
||||
account: &mut Account,
|
||||
client: &mut EmailClient,
|
||||
) -> Result<()> {
|
||||
let raw = self.message.parse()?.into_bytes();
|
||||
client.send_message(raw)?;
|
||||
printer.out(Message::new("Message successfully sent"))
|
||||
handler::route(printer, account, client, raw, self.save.as_deref(), true)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user