mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 20:07:57 +08:00
add flag commands
This commit is contained in:
+7
-1
@@ -2,7 +2,10 @@ use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::command::MailboxCommand};
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{flag::command::FlagCommand, mailbox::command::MailboxCommand},
|
||||
};
|
||||
|
||||
/// IMAP CLI (requires `imap` cargo feature).
|
||||
///
|
||||
@@ -12,6 +15,8 @@ use crate::{config::ImapConfig, imap::mailbox::command::MailboxCommand};
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[command(rename_all = "lowercase")]
|
||||
pub enum ImapCommand {
|
||||
#[command(subcommand)]
|
||||
Flags(FlagCommand),
|
||||
#[command(subcommand)]
|
||||
#[command(aliases = ["mboxes", "mbox"])]
|
||||
Mailboxes(MailboxCommand),
|
||||
@@ -20,6 +25,7 @@ pub enum ImapCommand {
|
||||
impl ImapCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
match self {
|
||||
Self::Flags(cmd) => cmd.execute(printer, config),
|
||||
Self::Mailboxes(cmd) => cmd.execute(printer, config),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{select::*, store::*},
|
||||
types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream};
|
||||
|
||||
/// Add flags to messages.
|
||||
///
|
||||
/// This command adds the specified flags to messages identified by
|
||||
/// the given sequence set.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct AddFlagsCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalFlag,
|
||||
|
||||
/// The sequence set of messages (e.g., "1", "1,2,3", "1:*").
|
||||
#[arg(name = "sequence_set", value_name = "SEQUENCE")]
|
||||
pub sequence_set: String,
|
||||
|
||||
/// The flags to add (e.g., "\\Seen", "\\Flagged").
|
||||
#[arg(short, long, required = true, num_args = 1..)]
|
||||
pub flags: Vec<String>,
|
||||
|
||||
/// Use UID STORE instead of STORE.
|
||||
#[arg(long)]
|
||||
pub uid: bool,
|
||||
}
|
||||
|
||||
impl AddFlagsCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
// First, SELECT the mailbox
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse flags
|
||||
let flags: Vec<Flag<'static>> = self
|
||||
.flags
|
||||
.iter()
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Parse sequence set
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
|
||||
// Store flags
|
||||
let mut arg = None;
|
||||
let mut coroutine =
|
||||
ImapStoreSilent::new(context, sequence_set, StoreType::Add, flags, self.uid);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapStoreSilentResult::Ok { .. } => break,
|
||||
ImapStoreSilentResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully added"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
use std::{collections::BTreeMap, fmt};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::{
|
||||
coroutines::select::*,
|
||||
types::flag::{Flag, FlagPerm},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalArg, imap::stream};
|
||||
|
||||
/// List available flags for a mailbox.
|
||||
///
|
||||
/// This command displays the flags and permanent flags that are
|
||||
/// available in the given mailbox. These flags come from the SELECT
|
||||
/// response.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ListFlagsCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalArg,
|
||||
}
|
||||
|
||||
impl ListFlagsCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let (flags, permanent_flags) = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { data, .. } => {
|
||||
break (
|
||||
data.flags.unwrap_or_default(),
|
||||
data.permanent_flags.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
let table = FlagsTable { flags, permanent_flags };
|
||||
|
||||
printer.out(table)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct FlagEntry {
|
||||
pub name: String,
|
||||
pub permanent: bool,
|
||||
}
|
||||
|
||||
pub struct FlagsTable {
|
||||
flags: Vec<Flag<'static>>,
|
||||
permanent_flags: Vec<FlagPerm<'static>>,
|
||||
}
|
||||
|
||||
impl FlagsTable {
|
||||
fn build_entries(&self) -> Vec<FlagEntry> {
|
||||
let mut entries: BTreeMap<String, bool> = BTreeMap::new();
|
||||
|
||||
// Add flags
|
||||
for flag in &self.flags {
|
||||
entries.entry(flag.to_string()).or_insert(false);
|
||||
}
|
||||
|
||||
// Mark permanent flags
|
||||
for flag in &self.permanent_flags {
|
||||
let name = match flag {
|
||||
FlagPerm::Flag(f) => f.to_string(),
|
||||
FlagPerm::Asterisk => "\\*".to_string(),
|
||||
};
|
||||
entries.insert(name, true);
|
||||
}
|
||||
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|(name, permanent)| FlagEntry { name, permanent })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FlagsTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(presets::ASCII_MARKDOWN)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.set_header(Row::from([Cell::new("FLAG"), Cell::new("PERMANENT")]));
|
||||
|
||||
for entry in self.build_entries() {
|
||||
table.add_row(Row::from([
|
||||
Cell::new(&entry.name),
|
||||
Cell::new(if entry.permanent { "true" } else { "" }),
|
||||
]));
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
write!(f, "{table}")?;
|
||||
writeln!(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for FlagsTable {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.build_entries().serialize(serializer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
pub mod add;
|
||||
pub mod list;
|
||||
pub mod remove;
|
||||
pub mod set;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::flag::command::{
|
||||
add::AddFlagsCommand, list::ListFlagsCommand, remove::RemoveFlagsCommand,
|
||||
set::SetFlagsCommand,
|
||||
},
|
||||
};
|
||||
|
||||
/// Manage message flags.
|
||||
///
|
||||
/// A flag is a label attached to a message. This subcommand allows
|
||||
/// you to manage them: list available flags, add flags to messages,
|
||||
/// remove flags from messages, etc.
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum FlagCommand {
|
||||
List(ListFlagsCommand),
|
||||
Add(AddFlagsCommand),
|
||||
Set(SetFlagsCommand),
|
||||
Remove(RemoveFlagsCommand),
|
||||
}
|
||||
|
||||
impl FlagCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
match self {
|
||||
Self::List(cmd) => cmd.execute(printer, config),
|
||||
Self::Add(cmd) => cmd.execute(printer, config),
|
||||
Self::Set(cmd) => cmd.execute(printer, config),
|
||||
Self::Remove(cmd) => cmd.execute(printer, config),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{select::*, store::*},
|
||||
types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream};
|
||||
|
||||
/// Remove flags from messages.
|
||||
///
|
||||
/// This command removes the specified flags from messages identified
|
||||
/// by the given sequence set.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct RemoveFlagsCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalFlag,
|
||||
|
||||
/// The sequence set of messages (e.g., "1", "1,2,3", "1:*").
|
||||
#[arg(name = "sequence_set", value_name = "SEQUENCE")]
|
||||
pub sequence_set: String,
|
||||
|
||||
/// The flags to remove (e.g., "\\Seen", "\\Flagged").
|
||||
#[arg(short, long, required = true, num_args = 1..)]
|
||||
pub flags: Vec<String>,
|
||||
|
||||
/// Use UID STORE instead of STORE.
|
||||
#[arg(long)]
|
||||
pub uid: bool,
|
||||
}
|
||||
|
||||
impl RemoveFlagsCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
// First, SELECT the mailbox
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse flags
|
||||
let flags: Vec<Flag<'static>> = self
|
||||
.flags
|
||||
.iter()
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Parse sequence set
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
|
||||
// Store flags
|
||||
let mut arg = None;
|
||||
let mut coroutine =
|
||||
ImapStoreSilent::new(context, sequence_set, StoreType::Remove, flags, self.uid);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapStoreSilentResult::Ok { .. } => break,
|
||||
ImapStoreSilentResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully removed"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{select::*, store::*},
|
||||
types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream};
|
||||
|
||||
/// Set flags on messages (replacing existing flags).
|
||||
///
|
||||
/// This command replaces all existing flags on messages identified by
|
||||
/// the given sequence set with the specified flags.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct SetFlagsCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalFlag,
|
||||
|
||||
/// The sequence set of messages (e.g., "1", "1,2,3", "1:*").
|
||||
#[arg(name = "sequence_set", value_name = "SEQUENCE")]
|
||||
pub sequence_set: String,
|
||||
|
||||
/// The flags to set (e.g., "\\Seen", "\\Flagged").
|
||||
#[arg(short, long, required = true, num_args = 1..)]
|
||||
pub flags: Vec<String>,
|
||||
|
||||
/// Use UID STORE instead of STORE.
|
||||
#[arg(long)]
|
||||
pub uid: bool,
|
||||
}
|
||||
|
||||
impl SetFlagsCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
// First, SELECT the mailbox
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse flags
|
||||
let flags: Vec<Flag<'static>> = self
|
||||
.flags
|
||||
.iter()
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Parse sequence set
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
|
||||
// Store flags
|
||||
let mut arg = None;
|
||||
let mut coroutine =
|
||||
ImapStoreSilent::new(context, sequence_set, StoreType::Replace, flags, self.uid);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapStoreSilentResult::Ok { .. } => break,
|
||||
ImapStoreSilentResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully set"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod command;
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod command;
|
||||
pub mod flag;
|
||||
pub mod mailbox;
|
||||
pub mod stream;
|
||||
|
||||
Reference in New Issue
Block a user