mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-19 14:57:55 +08:00
refactor: split composer config into subparts compose, reply and forward
* feat: Add configs for `reply-with` and `forward-with` commands Config extended from: (Old) ```toml [message.composer.mml] command = "mml compose" ``` , where `compose-with`, `reply-with`, and `forward-with` all share the same composer command, to: (New) ```toml [message.composer.mml] default = true compose-command = "mml compose" reply-command = "mml reply" forward-command = "mml forward" ``` * docs: ComposerConfig * refactor(account): simplify composer resolution with `get_composer` - Implement `Account::get_composer`, and `Account::get_reader` to fetch config by name or default. - Remove the redundant `resolve_composer`, `default_composer` `resolve_reader`, and `default_reader` helpers. - Simplify the configuration retrieval architecture. * refactor: update composer and reader config to use std::process::Command - Remove `_command` suffix from `compose`, `reply`, and `forward` configuration fields. - Replace composer and reader config command type from `String` to `std::process::Command`. - Remove `Clone` derives from `Config`, `Account`, and related structs due to `Command` type limitations. Refs: #687
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use pimalaya_config::command::shell;
|
||||
|
||||
use crate::shared::{
|
||||
client::EmailClient,
|
||||
@@ -57,16 +58,17 @@ pub struct MessageComposeWithCommand {
|
||||
|
||||
impl MessageComposeWithCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> {
|
||||
let command = match self.command.as_deref() {
|
||||
Some(cmd) => cmd.to_owned(),
|
||||
None => {
|
||||
runner::resolve_composer(&client.account.composer, self.name.as_deref())?.to_owned()
|
||||
}
|
||||
};
|
||||
let mut command = self.command.map(|cmd| shell(&cmd));
|
||||
let command = command.as_mut().unwrap_or(
|
||||
&mut client
|
||||
.account
|
||||
.get_composer_mut(self.name.as_deref())?
|
||||
.compose,
|
||||
);
|
||||
|
||||
let raw = runner::run(&command, &[])?;
|
||||
let raw = runner::run(command, &[])?;
|
||||
if raw.is_empty() {
|
||||
bail!("composer `{command}` produced no output");
|
||||
bail!("composer `{command:?}` produced no output");
|
||||
}
|
||||
|
||||
output::route(printer, &mut client, raw, self.save.as_deref(), self.send)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use pimalaya_config::command::shell;
|
||||
|
||||
use crate::shared::{
|
||||
client::EmailClient,
|
||||
@@ -59,16 +60,17 @@ impl MessageForwardWithCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> {
|
||||
let source = client.get_message(&self.mailbox, &self.id)?;
|
||||
|
||||
let command = match self.command.as_deref() {
|
||||
Some(cmd) => cmd.to_owned(),
|
||||
None => {
|
||||
runner::resolve_composer(&client.account.composer, self.name.as_deref())?.to_owned()
|
||||
}
|
||||
};
|
||||
let mut command = self.command.map(|cmd| shell(&cmd));
|
||||
let command = command.as_mut().unwrap_or(
|
||||
&mut client
|
||||
.account
|
||||
.get_composer_mut(self.name.as_deref())?
|
||||
.forward,
|
||||
);
|
||||
|
||||
let raw = runner::run(&command, &source)?;
|
||||
let raw = runner::run(command, &source)?;
|
||||
if raw.is_empty() {
|
||||
bail!("composer `{command}` produced no output");
|
||||
bail!("composer `{command:?}` produced no output");
|
||||
}
|
||||
|
||||
output::route(printer, &mut client, raw, self.save.as_deref(), self.send)
|
||||
|
||||
@@ -19,6 +19,7 @@ use anyhow::{Result, anyhow, bail};
|
||||
use clap::Parser;
|
||||
use percent_encoding::percent_decode_str;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use pimalaya_config::command::shell;
|
||||
use url::Url;
|
||||
|
||||
use crate::shared::{
|
||||
@@ -88,16 +89,17 @@ impl MessageMailtoCommand {
|
||||
None,
|
||||
)?;
|
||||
|
||||
let command = match self.command.as_deref() {
|
||||
Some(cmd) => cmd.to_owned(),
|
||||
None => {
|
||||
runner::resolve_composer(&client.account.composer, self.name.as_deref())?.to_owned()
|
||||
}
|
||||
};
|
||||
let mut command = self.command.map(|cmd| shell(&cmd));
|
||||
let command = command.as_mut().unwrap_or(
|
||||
&mut client
|
||||
.account
|
||||
.get_composer_mut(self.name.as_deref())?
|
||||
.compose,
|
||||
);
|
||||
|
||||
let raw = runner::run(&command, &draft)?;
|
||||
let raw = runner::run(command, &draft)?;
|
||||
if raw.is_empty() {
|
||||
bail!("composer `{command}` produced no output");
|
||||
bail!("composer `{command:?}` produced no output");
|
||||
}
|
||||
|
||||
output::route(printer, &mut client, raw, self.save.as_deref(), self.send)
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::io::{Write, stdout};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use pimalaya_config::command::shell;
|
||||
|
||||
use crate::shared::{client::EmailClient, messages::runner};
|
||||
|
||||
@@ -58,14 +59,12 @@ impl MessageReadWithCommand {
|
||||
pub fn execute(self, _printer: &mut impl Printer, mut client: EmailClient) -> Result<()> {
|
||||
let source = client.get_message(&self.mailbox, &self.id)?;
|
||||
|
||||
let command = match self.command.as_deref() {
|
||||
Some(cmd) => cmd.to_owned(),
|
||||
None => {
|
||||
runner::resolve_reader(&client.account.reader, self.name.as_deref())?.to_owned()
|
||||
}
|
||||
};
|
||||
let mut command = self.command.map(|cmd| shell(&cmd));
|
||||
let command = command
|
||||
.as_mut()
|
||||
.unwrap_or(&mut client.account.get_reader_mut(self.name.as_deref())?.command);
|
||||
|
||||
let bytes = runner::run(&command, &source)?;
|
||||
let bytes = runner::run(command, &source)?;
|
||||
|
||||
if !bytes.is_empty() {
|
||||
let mut out = stdout().lock();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use pimalaya_config::command::shell;
|
||||
|
||||
use crate::shared::{
|
||||
client::EmailClient,
|
||||
@@ -64,16 +65,14 @@ impl MessageReplyWithCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, mut client: EmailClient) -> Result<()> {
|
||||
let source = client.get_message(&self.mailbox, &self.id)?;
|
||||
|
||||
let command = match self.command.as_deref() {
|
||||
Some(cmd) => cmd.to_owned(),
|
||||
None => {
|
||||
runner::resolve_composer(&client.account.composer, self.name.as_deref())?.to_owned()
|
||||
}
|
||||
};
|
||||
let mut command = self.command.map(|cmd| shell(&cmd));
|
||||
let command = command
|
||||
.as_mut()
|
||||
.unwrap_or(&mut client.account.get_composer_mut(self.name.as_deref())?.reply);
|
||||
|
||||
let raw = runner::run(&command, &source)?;
|
||||
let raw = runner::run(command, &source)?;
|
||||
if raw.is_empty() {
|
||||
bail!("composer `{command}` produced no output");
|
||||
bail!("composer `{command:?}` produced no output");
|
||||
}
|
||||
|
||||
output::route(printer, &mut client, raw, self.save.as_deref(), self.send)
|
||||
|
||||
@@ -26,90 +26,37 @@
|
||||
//! `/dev/tty` once they've consumed stdin — standard Unix practice.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::Write,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
|
||||
use crate::config::{ComposerConfig, ReaderConfig};
|
||||
|
||||
/// Resolves a composer entry to its shell command line. 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 resolve_composer<'a>(
|
||||
composers: &'a HashMap<String, ComposerConfig>,
|
||||
name: Option<&str>,
|
||||
) -> Result<&'a str> {
|
||||
match name {
|
||||
Some(name) => match composers.get(name) {
|
||||
Some(entry) => Ok(entry.command.as_str()),
|
||||
None => bail!("no composer named `{name}` in [message.composer]"),
|
||||
},
|
||||
None => default_composer(composers).map(|entry| entry.command.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`resolve_composer`] but for readers.
|
||||
pub fn resolve_reader<'a>(
|
||||
readers: &'a HashMap<String, ReaderConfig>,
|
||||
name: Option<&str>,
|
||||
) -> Result<&'a str> {
|
||||
match name {
|
||||
Some(name) => match readers.get(name) {
|
||||
Some(entry) => Ok(entry.command.as_str()),
|
||||
None => bail!("no reader named `{name}` in [message.reader]"),
|
||||
},
|
||||
None => default_reader(readers).map(|entry| entry.command.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_composer(composers: &HashMap<String, ComposerConfig>) -> Result<&ComposerConfig> {
|
||||
composers.values().find(|c| c.default).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"no composer specified and no default in [message.composer.*]; \
|
||||
pass a <name> or set `default = true` on one entry"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_reader(readers: &HashMap<String, ReaderConfig>) -> Result<&ReaderConfig> {
|
||||
readers.values().find(|c| c.default).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"no reader specified and no default in [message.reader.*]; \
|
||||
pass a <name> or set `default = true` on one entry"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawns `command` through `sh -c`, writes `stdin_bytes` to its
|
||||
/// stdin, and returns the captured stdout bytes. Stderr is inherited.
|
||||
/// Spawns `command`, writes `stdin_bytes` to its
|
||||
/// stdin, and returns the captured stdout bytes.
|
||||
/// Stderr is inherited.
|
||||
/// Bails on a non-zero exit status.
|
||||
pub fn run(command: &str, stdin_bytes: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut child = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
pub fn run(command: &mut Command, stdin_bytes: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut child = command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.map_err(|err| anyhow!("spawn `{command}`: {err}"))?;
|
||||
.map_err(|err| anyhow!("spawn `{command:?}`: {err}"))?;
|
||||
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin
|
||||
.write_all(stdin_bytes)
|
||||
.map_err(|err| anyhow!("write stdin to `{command}`: {err}"))?;
|
||||
.map_err(|err| anyhow!("write stdin to `{command:?}`: {err}"))?;
|
||||
}
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.map_err(|err| anyhow!("wait `{command}`: {err}"))?;
|
||||
.map_err(|err| anyhow!("wait `{command:?}`: {err}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"`{command}` exited with status {}",
|
||||
"`{command:?}` exited with status {}",
|
||||
output
|
||||
.status
|
||||
.code()
|
||||
|
||||
Reference in New Issue
Block a user