mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-22 02:50:19 +00:00
wip: fix printer, make thread compatible with it
This commit is contained in:
parent
6cbfc57c83
commit
b773218c94
45 changed files with 556 additions and 694 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -2086,7 +2086,6 @@ dependencies = [
|
|||
"serde_json",
|
||||
"shellexpand-utils",
|
||||
"sled",
|
||||
"termcolor",
|
||||
"terminal_size 0.1.17",
|
||||
"tokio",
|
||||
"toml",
|
||||
|
@ -4624,15 +4623,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
|
|
|
@ -74,7 +74,6 @@ serde-toml-merge = "0.3"
|
|||
serde_json = "1"
|
||||
shellexpand-utils = "=0.2.1"
|
||||
sled = "=0.34.7"
|
||||
termcolor = "1"
|
||||
terminal_size = "0.1"
|
||||
tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
toml = "0.8"
|
||||
|
|
|
@ -24,7 +24,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
let account = self.account.name.as_ref().map(String::as_str);
|
||||
|
||||
printer.print_log("Checking configuration integrity…")?;
|
||||
printer.log("Checking configuration integrity…")?;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
account,
|
||||
|
@ -33,7 +33,7 @@ impl AccountCheckUpCommand {
|
|||
)?;
|
||||
let used_backends = toml_account_config.get_used_backends();
|
||||
|
||||
printer.print_log("Checking backend context integrity…")?;
|
||||
printer.log("Checking backend context integrity…")?;
|
||||
|
||||
let ctx_builder = backend::BackendContextBuilder::new(
|
||||
toml_account_config.clone(),
|
||||
|
@ -46,7 +46,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
#[cfg(feature = "maildir")]
|
||||
{
|
||||
printer.print_log("Checking Maildir integrity…")?;
|
||||
printer.log("Checking Maildir integrity…")?;
|
||||
|
||||
let maildir = ctx_builder
|
||||
.maildir
|
||||
|
@ -61,7 +61,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
#[cfg(feature = "imap")]
|
||||
{
|
||||
printer.print_log("Checking IMAP integrity…")?;
|
||||
printer.log("Checking IMAP integrity…")?;
|
||||
|
||||
let imap = ctx_builder
|
||||
.imap
|
||||
|
@ -76,7 +76,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
#[cfg(feature = "notmuch")]
|
||||
{
|
||||
printer.print_log("Checking Notmuch integrity…")?;
|
||||
printer.print("Checking Notmuch integrity…")?;
|
||||
|
||||
let notmuch = ctx_builder
|
||||
.notmuch
|
||||
|
@ -91,7 +91,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
#[cfg(feature = "smtp")]
|
||||
{
|
||||
printer.print_log("Checking SMTP integrity…")?;
|
||||
printer.log("Checking SMTP integrity…")?;
|
||||
|
||||
let smtp = ctx_builder
|
||||
.smtp
|
||||
|
@ -106,7 +106,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
#[cfg(feature = "sendmail")]
|
||||
{
|
||||
printer.print_log("Checking Sendmail integrity…")?;
|
||||
printer.log("Checking Sendmail integrity…")?;
|
||||
|
||||
let sendmail = ctx_builder
|
||||
.sendmail
|
||||
|
@ -119,6 +119,6 @@ impl AccountCheckUpCommand {
|
|||
}
|
||||
}
|
||||
|
||||
printer.print("Checkup successfully completed!")
|
||||
printer.out("Checkup successfully completed!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ impl AccountConfigureCommand {
|
|||
.await?;
|
||||
}
|
||||
|
||||
printer.print(format!(
|
||||
printer.out(format!(
|
||||
"Account {account} successfully {}configured!",
|
||||
if self.reset { "re" } else { "" }
|
||||
))
|
||||
|
|
|
@ -2,7 +2,11 @@ use clap::Parser;
|
|||
use color_eyre::Result;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{account::Accounts, config::TomlConfig, printer::Printer};
|
||||
use crate::{
|
||||
account::{Accounts, AccountsTable},
|
||||
config::TomlConfig,
|
||||
printer::Printer,
|
||||
};
|
||||
|
||||
/// List all accounts.
|
||||
///
|
||||
|
@ -23,9 +27,10 @@ impl AccountListCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing list accounts command");
|
||||
|
||||
let accounts: Accounts = config.accounts.iter().into();
|
||||
let accounts = Accounts::from(config.accounts.iter());
|
||||
let table = AccountsTable::from(accounts).with_some_width(self.table_max_width);
|
||||
|
||||
printer.print_table(accounts, self.table_max_width)?;
|
||||
printer.out(table)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,28 +138,28 @@ impl AccountSyncCommand {
|
|||
let mut hunks_count = report.folder.patch.len();
|
||||
|
||||
if !report.folder.patch.is_empty() {
|
||||
printer.print_log("Folders patch:")?;
|
||||
printer.log("Folders patch:")?;
|
||||
for (hunk, _) in report.folder.patch {
|
||||
printer.print_log(format!(" - {hunk}"))?;
|
||||
printer.log(format!(" - {hunk}"))?;
|
||||
}
|
||||
printer.print_log("")?;
|
||||
printer.log("")?;
|
||||
}
|
||||
|
||||
if !report.email.patch.is_empty() {
|
||||
printer.print_log("Envelopes patch:")?;
|
||||
printer.log("Envelopes patch:")?;
|
||||
for (hunk, _) in report.email.patch {
|
||||
hunks_count += 1;
|
||||
printer.print_log(format!(" - {hunk}"))?;
|
||||
printer.log(format!(" - {hunk}"))?;
|
||||
}
|
||||
printer.print_log("")?;
|
||||
printer.log("")?;
|
||||
}
|
||||
|
||||
printer.print(format!(
|
||||
printer.out(format!(
|
||||
"Estimated patch length for account {account_name} to be synchronized: {hunks_count}"
|
||||
))?;
|
||||
} else if printer.is_json() {
|
||||
sync_builder.sync().await?;
|
||||
printer.print(format!("Account {account_name} successfully synchronized!"))?;
|
||||
printer.out(format!("Account {account_name} successfully synchronized!"))?;
|
||||
} else {
|
||||
let multi = MultiProgress::new();
|
||||
let sub_progresses = Mutex::new(HashMap::new());
|
||||
|
@ -239,11 +239,11 @@ impl AccountSyncCommand {
|
|||
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
||||
.collect::<Vec<_>>();
|
||||
if !folders_patch_err.is_empty() {
|
||||
printer.print_log("")?;
|
||||
printer.print_log("Errors occurred while applying the folders patch:")?;
|
||||
printer.log("")?;
|
||||
printer.log("Errors occurred while applying the folders patch:")?;
|
||||
folders_patch_err
|
||||
.iter()
|
||||
.try_for_each(|(hunk, err)| printer.print_log(format!(" - {hunk}: {err}")))?;
|
||||
.try_for_each(|(hunk, err)| printer.log(format!(" - {hunk}: {err}")))?;
|
||||
}
|
||||
|
||||
let envelopes_patch_err = report
|
||||
|
@ -253,14 +253,14 @@ impl AccountSyncCommand {
|
|||
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
||||
.collect::<Vec<_>>();
|
||||
if !envelopes_patch_err.is_empty() {
|
||||
printer.print_log("")?;
|
||||
printer.print_log("Errors occurred while applying the envelopes patch:")?;
|
||||
printer.log("")?;
|
||||
printer.log("Errors occurred while applying the envelopes patch:")?;
|
||||
for (hunk, err) in envelopes_patch_err {
|
||||
printer.print_log(format!(" - {hunk}: {err}"))?;
|
||||
printer.log(format!(" - {hunk}: {err}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
printer.print(format!("Account {account_name} successfully synchronized!"))?;
|
||||
printer.out(format!("Account {account_name} successfully synchronized!"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -3,13 +3,10 @@ pub mod command;
|
|||
pub mod config;
|
||||
pub(crate) mod wizard;
|
||||
|
||||
use color_eyre::Result;
|
||||
use comfy_table::{presets, Attribute, Cell, Color, ContentArrangement, Row, Table};
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::{collections::hash_map::Iter, fmt, ops::Deref};
|
||||
|
||||
use crate::printer::{PrintTable, WriteColor};
|
||||
|
||||
use self::config::TomlAccountConfig;
|
||||
|
||||
/// Represents the printable account.
|
||||
|
@ -31,6 +28,16 @@ impl Account {
|
|||
default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_row(&self) -> Row {
|
||||
let mut row = Row::new();
|
||||
|
||||
row.add_cell(Cell::new(&self.name).fg(Color::Green));
|
||||
row.add_cell(Cell::new(&self.backend).fg(Color::Blue));
|
||||
row.add_cell(Cell::new(if self.default { "yes" } else { "" }).fg(Color::White));
|
||||
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Account {
|
||||
|
@ -39,28 +46,27 @@ impl fmt::Display for Account {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Account> for Row {
|
||||
fn from(account: Account) -> Self {
|
||||
let mut r = Row::new();
|
||||
r.add_cell(Cell::new(account.name).fg(Color::Green));
|
||||
r.add_cell(Cell::new(account.backend).fg(Color::Blue));
|
||||
r.add_cell(Cell::new(if account.default { "yes" } else { "" }).fg(Color::White));
|
||||
r
|
||||
}
|
||||
}
|
||||
impl From<&Account> for Row {
|
||||
fn from(account: &Account) -> Self {
|
||||
let mut r = Row::new();
|
||||
r.add_cell(Cell::new(&account.name).fg(Color::Green));
|
||||
r.add_cell(Cell::new(&account.backend).fg(Color::Blue));
|
||||
r.add_cell(Cell::new(if account.default { "yes" } else { "" }).fg(Color::White));
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the list of printable accounts.
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Accounts(pub Vec<Account>);
|
||||
pub struct Accounts(Vec<Account>);
|
||||
|
||||
impl Accounts {
|
||||
pub fn to_table(&self) -> Table {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("NAME").add_attribute(Attribute::Reverse),
|
||||
Cell::new("BACKENDS").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DEFAULT").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(self.iter().map(Account::to_row));
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Accounts {
|
||||
type Target = Vec<Account>;
|
||||
|
@ -70,51 +76,6 @@ impl Deref for Accounts {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Accounts> for Table {
|
||||
fn from(accounts: Accounts) -> Self {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("NAME").add_attribute(Attribute::Reverse),
|
||||
Cell::new("BACKENDS").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DEFAULT").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(accounts.0.into_iter().map(Row::from));
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Accounts> for Table {
|
||||
fn from(accounts: &Accounts) -> Self {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("NAME").add_attribute(Attribute::Reverse),
|
||||
Cell::new("BACKENDS").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DEFAULT").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(accounts.0.iter().map(Row::from));
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for Accounts {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, table_max_width: Option<u16>) -> Result<()> {
|
||||
let mut table = Table::from(self);
|
||||
if let Some(width) = table_max_width {
|
||||
table.set_width(width);
|
||||
}
|
||||
writeln!(writer)?;
|
||||
write!(writer, "{}", table)?;
|
||||
writeln!(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Iter<'_, String, TomlAccountConfig>> for Accounts {
|
||||
fn from(map: Iter<'_, String, TomlAccountConfig>) -> Self {
|
||||
let mut accounts: Vec<_> = map
|
||||
|
@ -169,3 +130,48 @@ impl From<Iter<'_, String, TomlAccountConfig>> for Accounts {
|
|||
Self(accounts)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AccountsTable {
|
||||
accounts: Accounts,
|
||||
width: Option<u16>,
|
||||
}
|
||||
|
||||
impl AccountsTable {
|
||||
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Accounts> for AccountsTable {
|
||||
fn from(accounts: Accounts) -> Self {
|
||||
Self {
|
||||
accounts,
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AccountsTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut table = self.accounts.to_table();
|
||||
|
||||
if let Some(width) = self.width {
|
||||
table.set_width(width);
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
write!(f, "{table}")?;
|
||||
writeln!(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for AccountsTable {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.accounts.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -701,7 +701,7 @@ impl Backend {
|
|||
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
|
||||
let envelopes = self.backend.list_envelopes(folder, opts).await?;
|
||||
let envelopes =
|
||||
Envelopes::from_backend(&self.backend.account_config, &id_mapper, envelopes)?;
|
||||
Envelopes::try_from_backend(&self.backend.account_config, &id_mapper, envelopes)?;
|
||||
Ok(envelopes)
|
||||
}
|
||||
|
||||
|
|
26
src/cli.rs
26
src/cli.rs
|
@ -14,7 +14,7 @@ use crate::{
|
|||
attachment::command::AttachmentSubcommand, command::MessageSubcommand,
|
||||
template::command::TemplateSubcommand,
|
||||
},
|
||||
output::{ColorFmt, OutputFmt},
|
||||
output::OutputFmt,
|
||||
printer::Printer,
|
||||
};
|
||||
|
||||
|
@ -52,30 +52,6 @@ pub struct Cli {
|
|||
#[arg(value_name = "FORMAT", value_enum, default_value_t = Default::default())]
|
||||
pub output: OutputFmt,
|
||||
|
||||
/// Control when to use colors
|
||||
///
|
||||
/// The default setting is 'auto', which means himalaya will try
|
||||
/// to guess when to use colors. For example, if himalaya is
|
||||
/// printing to a terminal, then it will use colors, but if it is
|
||||
/// redirected to a file or a pipe, then it will suppress color
|
||||
/// output. himalaya will suppress color output in some other
|
||||
/// circumstances as well. For example, if the TERM environment
|
||||
/// variable is not set or set to 'dumb', then himalaya will not
|
||||
/// use colors.
|
||||
///
|
||||
/// The possible values are:
|
||||
///
|
||||
/// - never: colors will never be used
|
||||
///
|
||||
/// - always: colors will always be used regardless of where output is sent
|
||||
///
|
||||
/// - ansi: like 'always', but emits ANSI escapes (even in a Windows console)
|
||||
///
|
||||
/// - auto: himalaya tries to be smart
|
||||
#[arg(long, short = 'C', global = true)]
|
||||
#[arg(value_name = "MODE", value_enum, default_value_t = Default::default())]
|
||||
pub color: ColorFmt,
|
||||
|
||||
/// Enable logs with spantrace.
|
||||
///
|
||||
/// This is the same as running the command with `RUST_LOG=debug`
|
||||
|
|
|
@ -12,7 +12,7 @@ use tracing::info;
|
|||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
envelope::EnvelopesTable, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
};
|
||||
|
||||
/// List all envelopes.
|
||||
|
@ -198,9 +198,9 @@ impl ListEnvelopesCommand {
|
|||
};
|
||||
|
||||
let envelopes = backend.list_envelopes(folder, opts).await?;
|
||||
let table = EnvelopesTable::from(envelopes).with_some_width(self.table_max_width);
|
||||
|
||||
printer.print_table(envelopes, self.table_max_width)?;
|
||||
|
||||
printer.out(table)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
use ariadne::{Label, Report, ReportKind, Source};
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use crossterm::{
|
||||
cursor::{self, MoveToColumn},
|
||||
style::{Color, Print, ResetColor, SetForegroundColor},
|
||||
terminal, ExecutableCommand,
|
||||
};
|
||||
use email::{
|
||||
account::config::AccountConfig,
|
||||
backend::feature::BackendFeatureSource,
|
||||
email::search_query,
|
||||
envelope::{list::ListEnvelopesOptions, ThreadedEnvelope},
|
||||
search_query::SearchEmailsQuery,
|
||||
backend::feature::BackendFeatureSource, email::search_query,
|
||||
envelope::list::ListEnvelopesOptions, search_query::SearchEmailsQuery,
|
||||
};
|
||||
use petgraph::graphmap::DiGraphMap;
|
||||
use std::{io::Write, process::exit};
|
||||
use std::process::exit;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
envelope::EnvelopesTree, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
};
|
||||
|
||||
/// Thread all envelopes.
|
||||
|
@ -49,7 +40,7 @@ pub struct ThreadEnvelopesCommand {
|
|||
}
|
||||
|
||||
impl ThreadEnvelopesCommand {
|
||||
pub async fn execute(self, _printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing thread envelopes command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
|
@ -106,198 +97,101 @@ impl ThreadEnvelopesCommand {
|
|||
None => backend.thread_envelopes(folder, opts).await,
|
||||
}?;
|
||||
|
||||
let mut stdout = std::io::stdout();
|
||||
write_tree(
|
||||
&account_config,
|
||||
&mut stdout,
|
||||
envelopes.graph(),
|
||||
ThreadedEnvelope {
|
||||
id: "0",
|
||||
message_id: "0",
|
||||
from: "",
|
||||
subject: "",
|
||||
date: Default::default(),
|
||||
},
|
||||
String::new(),
|
||||
0,
|
||||
)?;
|
||||
stdout.flush()?;
|
||||
let tree = EnvelopesTree::new(account_config, envelopes);
|
||||
|
||||
// printer.print_table(envelopes, self.table_max_width)?;
|
||||
printer.out(tree)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_tree(
|
||||
config: &AccountConfig,
|
||||
w: &mut impl std::io::Write,
|
||||
graph: &DiGraphMap<ThreadedEnvelope<'_>, u8>,
|
||||
parent: ThreadedEnvelope<'_>,
|
||||
pad: String,
|
||||
weight: u8,
|
||||
) -> std::io::Result<()> {
|
||||
let edges = graph
|
||||
.all_edges()
|
||||
.filter_map(|(a, b, w)| {
|
||||
if a == parent && *w == weight {
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// #[cfg(test)]
|
||||
// mod test {
|
||||
// use email::{account::config::AccountConfig, envelope::ThreadedEnvelope};
|
||||
// use petgraph::graphmap::DiGraphMap;
|
||||
|
||||
if parent.id == "0" {
|
||||
w.execute(Print("root"))?;
|
||||
} else {
|
||||
w.execute(SetForegroundColor(Color::Red))?
|
||||
.execute(Print(parent.id))?
|
||||
.execute(SetForegroundColor(Color::DarkGrey))?
|
||||
.execute(Print(") "))?
|
||||
.execute(ResetColor)?;
|
||||
// use super::write_tree;
|
||||
|
||||
if !parent.subject.is_empty() {
|
||||
w.execute(SetForegroundColor(Color::Green))?
|
||||
.execute(Print(parent.subject))?
|
||||
.execute(ResetColor)?
|
||||
.execute(Print(" "))?;
|
||||
}
|
||||
// macro_rules! e {
|
||||
// ($id:literal) => {
|
||||
// ThreadedEnvelope {
|
||||
// id: $id,
|
||||
// message_id: $id,
|
||||
// from: "",
|
||||
// subject: "",
|
||||
// date: Default::default(),
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
if !parent.from.is_empty() {
|
||||
w.execute(SetForegroundColor(Color::DarkGrey))?
|
||||
.execute(Print("<"))?
|
||||
.execute(SetForegroundColor(Color::Blue))?
|
||||
.execute(Print(parent.from))?
|
||||
.execute(SetForegroundColor(Color::DarkGrey))?
|
||||
.execute(Print(">"))?
|
||||
.execute(ResetColor)?;
|
||||
}
|
||||
// #[test]
|
||||
// fn tree_1() {
|
||||
// let config = AccountConfig::default();
|
||||
// let mut buf = Vec::new();
|
||||
// let mut graph = DiGraphMap::new();
|
||||
// graph.add_edge(e!("0"), e!("1"), 0);
|
||||
// graph.add_edge(e!("0"), e!("2"), 0);
|
||||
// graph.add_edge(e!("0"), e!("3"), 0);
|
||||
|
||||
let date = parent.format_date(config);
|
||||
let cursor_date_begin_col = terminal::size()?.0 - date.len() as u16;
|
||||
// write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap();
|
||||
// let buf = String::from_utf8_lossy(&buf);
|
||||
|
||||
w.execute(Print(" "))?
|
||||
.execute(SetForegroundColor(Color::DarkGrey))?
|
||||
.execute(Print("·".repeat(
|
||||
(cursor_date_begin_col - cursor::position()?.0 - 1) as usize,
|
||||
)))?
|
||||
.execute(ResetColor)?
|
||||
.execute(Print(" "))?;
|
||||
// let expected = "
|
||||
// 0
|
||||
// ├─ 1
|
||||
// ├─ 2
|
||||
// └─ 3
|
||||
// ";
|
||||
// assert_eq!(expected.trim_start(), buf)
|
||||
// }
|
||||
|
||||
w.execute(MoveToColumn(terminal::size()?.0 - date.len() as u16))?
|
||||
.execute(SetForegroundColor(Color::DarkYellow))?
|
||||
.execute(Print(date))?
|
||||
.execute(ResetColor)?;
|
||||
}
|
||||
// #[test]
|
||||
// fn tree_2() {
|
||||
// let config = AccountConfig::default();
|
||||
// let mut buf = Vec::new();
|
||||
// let mut graph = DiGraphMap::new();
|
||||
// graph.add_edge(e!("0"), e!("1"), 0);
|
||||
// graph.add_edge(e!("1"), e!("2"), 1);
|
||||
// graph.add_edge(e!("1"), e!("3"), 1);
|
||||
|
||||
writeln!(w)?;
|
||||
// write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap();
|
||||
// let buf = String::from_utf8_lossy(&buf);
|
||||
|
||||
let edges_count = edges.len();
|
||||
for (i, b) in edges.into_iter().enumerate() {
|
||||
let is_last = edges_count == i + 1;
|
||||
let (x, y) = if is_last {
|
||||
(' ', '└')
|
||||
} else {
|
||||
('│', '├')
|
||||
};
|
||||
// let expected = "
|
||||
// 0
|
||||
// └─ 1
|
||||
// ├─ 2
|
||||
// └─ 3
|
||||
// ";
|
||||
// assert_eq!(expected.trim_start(), buf)
|
||||
// }
|
||||
|
||||
write!(w, "{pad}{y}─ ")?;
|
||||
// #[test]
|
||||
// fn tree_3() {
|
||||
// let config = AccountConfig::default();
|
||||
// let mut buf = Vec::new();
|
||||
// let mut graph = DiGraphMap::new();
|
||||
// graph.add_edge(e!("0"), e!("1"), 0);
|
||||
// graph.add_edge(e!("1"), e!("2"), 1);
|
||||
// graph.add_edge(e!("2"), e!("22"), 2);
|
||||
// graph.add_edge(e!("1"), e!("3"), 1);
|
||||
// graph.add_edge(e!("0"), e!("4"), 0);
|
||||
// graph.add_edge(e!("4"), e!("5"), 1);
|
||||
// graph.add_edge(e!("5"), e!("6"), 2);
|
||||
|
||||
let pad = format!("{pad}{x} ");
|
||||
write_tree(config, w, graph, b, pad, weight + 1)?;
|
||||
}
|
||||
// write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap();
|
||||
// let buf = String::from_utf8_lossy(&buf);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use email::{account::config::AccountConfig, envelope::ThreadedEnvelope};
|
||||
use petgraph::graphmap::DiGraphMap;
|
||||
|
||||
use super::write_tree;
|
||||
|
||||
macro_rules! e {
|
||||
($id:literal) => {
|
||||
ThreadedEnvelope {
|
||||
id: $id,
|
||||
message_id: $id,
|
||||
from: "",
|
||||
subject: "",
|
||||
date: Default::default(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_1() {
|
||||
let config = AccountConfig::default();
|
||||
let mut buf = Vec::new();
|
||||
let mut graph = DiGraphMap::new();
|
||||
graph.add_edge(e!("0"), e!("1"), 0);
|
||||
graph.add_edge(e!("0"), e!("2"), 0);
|
||||
graph.add_edge(e!("0"), e!("3"), 0);
|
||||
|
||||
write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap();
|
||||
let buf = String::from_utf8_lossy(&buf);
|
||||
|
||||
let expected = "
|
||||
0
|
||||
├─ 1
|
||||
├─ 2
|
||||
└─ 3
|
||||
";
|
||||
assert_eq!(expected.trim_start(), buf)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_2() {
|
||||
let config = AccountConfig::default();
|
||||
let mut buf = Vec::new();
|
||||
let mut graph = DiGraphMap::new();
|
||||
graph.add_edge(e!("0"), e!("1"), 0);
|
||||
graph.add_edge(e!("1"), e!("2"), 1);
|
||||
graph.add_edge(e!("1"), e!("3"), 1);
|
||||
|
||||
write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap();
|
||||
let buf = String::from_utf8_lossy(&buf);
|
||||
|
||||
let expected = "
|
||||
0
|
||||
└─ 1
|
||||
├─ 2
|
||||
└─ 3
|
||||
";
|
||||
assert_eq!(expected.trim_start(), buf)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_3() {
|
||||
let config = AccountConfig::default();
|
||||
let mut buf = Vec::new();
|
||||
let mut graph = DiGraphMap::new();
|
||||
graph.add_edge(e!("0"), e!("1"), 0);
|
||||
graph.add_edge(e!("1"), e!("2"), 1);
|
||||
graph.add_edge(e!("2"), e!("22"), 2);
|
||||
graph.add_edge(e!("1"), e!("3"), 1);
|
||||
graph.add_edge(e!("0"), e!("4"), 0);
|
||||
graph.add_edge(e!("4"), e!("5"), 1);
|
||||
graph.add_edge(e!("5"), e!("6"), 2);
|
||||
|
||||
write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap();
|
||||
let buf = String::from_utf8_lossy(&buf);
|
||||
|
||||
let expected = "
|
||||
0
|
||||
├─ 1
|
||||
│ ├─ 2
|
||||
│ │ └─ 22
|
||||
│ └─ 3
|
||||
└─ 4
|
||||
└─ 5
|
||||
└─ 6
|
||||
";
|
||||
assert_eq!(expected.trim_start(), buf)
|
||||
}
|
||||
}
|
||||
// let expected = "
|
||||
// 0
|
||||
// ├─ 1
|
||||
// │ ├─ 2
|
||||
// │ │ └─ 22
|
||||
// │ └─ 3
|
||||
// └─ 4
|
||||
// └─ 5
|
||||
// └─ 6
|
||||
// ";
|
||||
// assert_eq!(expected.trim_start(), buf)
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -48,7 +48,7 @@ impl WatchEnvelopesCommand {
|
|||
)
|
||||
.await?;
|
||||
|
||||
printer.print_log(format!(
|
||||
printer.out(format!(
|
||||
"Start watching folder {folder} for envelopes changes…"
|
||||
))?;
|
||||
|
||||
|
|
|
@ -58,6 +58,6 @@ impl FlagAddCommand {
|
|||
|
||||
backend.add_flags(folder, &ids, &flags).await?;
|
||||
|
||||
printer.print(format!("Flag(s) {flags} successfully added!"))
|
||||
printer.out(format!("Flag(s) {flags} successfully added!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,6 @@ impl FlagRemoveCommand {
|
|||
|
||||
backend.remove_flags(folder, &ids, &flags).await?;
|
||||
|
||||
printer.print(format!("Flag(s) {flags} successfully removed!"))
|
||||
printer.out(format!("Flag(s) {flags} successfully removed!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,6 @@ impl FlagSetCommand {
|
|||
|
||||
backend.set_flags(folder, &ids, &flags).await?;
|
||||
|
||||
printer.print(format!("Flag(s) {flags} successfully replaced!"))
|
||||
printer.out(format!("Flag(s) {flags} successfully replaced!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,19 @@ pub mod config;
|
|||
pub mod flag;
|
||||
|
||||
use color_eyre::Result;
|
||||
use comfy_table::{presets, Attribute, Cell, Color, ContentArrangement, Row, Table};
|
||||
use email::account::config::AccountConfig;
|
||||
use serde::Serialize;
|
||||
use std::ops;
|
||||
use comfy_table::{presets, Attribute, Cell, ContentArrangement, Row, Table};
|
||||
use crossterm::{cursor, style::Stylize, terminal};
|
||||
use email::{
|
||||
account::config::AccountConfig,
|
||||
envelope::{ThreadedEnvelope, ThreadedEnvelopes},
|
||||
};
|
||||
use petgraph::graphmap::DiGraphMap;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::{fmt, ops::Deref, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
cache::IdMapper,
|
||||
flag::{Flag, Flags},
|
||||
printer::{PrintTable, WriteColor},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
|
@ -60,17 +64,17 @@ impl From<Envelope> for Row {
|
|||
row.add_cell(
|
||||
Cell::new(envelope.id)
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::Red),
|
||||
.fg(comfy_table::Color::Red),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(flags)
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::White),
|
||||
.fg(comfy_table::Color::White),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(envelope.subject)
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::Green),
|
||||
.fg(comfy_table::Color::Green),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(if let Some(name) = envelope.from.name {
|
||||
|
@ -79,12 +83,12 @@ impl From<Envelope> for Row {
|
|||
envelope.from.addr
|
||||
})
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::Blue),
|
||||
.fg(comfy_table::Color::Blue),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(envelope.date)
|
||||
.add_attributes(all_attributes)
|
||||
.fg(Color::Yellow),
|
||||
.fg(comfy_table::Color::Yellow),
|
||||
);
|
||||
|
||||
row
|
||||
|
@ -121,17 +125,17 @@ impl From<&Envelope> for Row {
|
|||
row.add_cell(
|
||||
Cell::new(&envelope.id)
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::Red),
|
||||
.fg(comfy_table::Color::Red),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(flags)
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::White),
|
||||
.fg(comfy_table::Color::White),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(&envelope.subject)
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::Green),
|
||||
.fg(comfy_table::Color::Green),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(if let Some(name) = &envelope.from.name {
|
||||
|
@ -140,12 +144,12 @@ impl From<&Envelope> for Row {
|
|||
&envelope.from.addr
|
||||
})
|
||||
.add_attributes(all_attributes.clone())
|
||||
.fg(Color::Blue),
|
||||
.fg(comfy_table::Color::Blue),
|
||||
)
|
||||
.add_cell(
|
||||
Cell::new(&envelope.date)
|
||||
.add_attributes(all_attributes)
|
||||
.fg(Color::Yellow),
|
||||
.fg(comfy_table::Color::Yellow),
|
||||
);
|
||||
|
||||
row
|
||||
|
@ -156,46 +160,8 @@ impl From<&Envelope> for Row {
|
|||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct Envelopes(Vec<Envelope>);
|
||||
|
||||
impl From<Envelopes> for Table {
|
||||
fn from(envelopes: Envelopes) -> Self {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("ID").add_attribute(Attribute::Reverse),
|
||||
Cell::new("FLAGS").add_attribute(Attribute::Reverse),
|
||||
Cell::new("SUBJECT").add_attribute(Attribute::Reverse),
|
||||
Cell::new("FROM").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DATE").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(envelopes.0.into_iter().map(Row::from));
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Envelopes> for Table {
|
||||
fn from(envelopes: &Envelopes) -> Self {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("ID").add_attribute(Attribute::Reverse),
|
||||
Cell::new("FLAGS").add_attribute(Attribute::Reverse),
|
||||
Cell::new("SUBJECT").add_attribute(Attribute::Reverse),
|
||||
Cell::new("FROM").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DATE").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(envelopes.0.iter().map(Row::from));
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl Envelopes {
|
||||
pub fn from_backend(
|
||||
pub fn try_from_backend(
|
||||
config: &AccountConfig,
|
||||
id_mapper: &IdMapper,
|
||||
envelopes: email::envelope::Envelopes,
|
||||
|
@ -222,9 +188,27 @@ impl Envelopes {
|
|||
|
||||
Ok(Envelopes(envelopes))
|
||||
}
|
||||
|
||||
pub fn to_table(&self) -> Table {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("ID").add_attribute(Attribute::Reverse),
|
||||
Cell::new("FLAGS").add_attribute(Attribute::Reverse),
|
||||
Cell::new("SUBJECT").add_attribute(Attribute::Reverse),
|
||||
Cell::new("FROM").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DATE").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(self.iter().map(Row::from));
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Envelopes {
|
||||
impl Deref for Envelopes {
|
||||
type Target = Vec<Envelope>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -232,15 +216,148 @@ impl ops::Deref for Envelopes {
|
|||
}
|
||||
}
|
||||
|
||||
impl PrintTable for Envelopes {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, table_max_width: Option<u16>) -> Result<()> {
|
||||
let mut table = Table::from(self);
|
||||
if let Some(width) = table_max_width {
|
||||
pub struct EnvelopesTable {
|
||||
envelopes: Envelopes,
|
||||
width: Option<u16>,
|
||||
}
|
||||
|
||||
impl EnvelopesTable {
|
||||
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Envelopes> for EnvelopesTable {
|
||||
fn from(envelopes: Envelopes) -> Self {
|
||||
Self {
|
||||
envelopes,
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EnvelopesTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut table = self.envelopes.to_table();
|
||||
|
||||
if let Some(width) = self.width {
|
||||
table.set_width(width);
|
||||
}
|
||||
writeln!(writer)?;
|
||||
write!(writer, "{}", table)?;
|
||||
writeln!(writer)?;
|
||||
|
||||
writeln!(f)?;
|
||||
write!(f, "{table}")?;
|
||||
writeln!(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for EnvelopesTable {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.envelopes.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnvelopesTree {
|
||||
config: Arc<AccountConfig>,
|
||||
envelopes: ThreadedEnvelopes,
|
||||
}
|
||||
|
||||
impl EnvelopesTree {
|
||||
pub fn new(config: Arc<AccountConfig>, envelopes: ThreadedEnvelopes) -> Self {
|
||||
Self { config, envelopes }
|
||||
}
|
||||
|
||||
pub fn fmt(
|
||||
f: &mut fmt::Formatter,
|
||||
config: &AccountConfig,
|
||||
graph: &DiGraphMap<ThreadedEnvelope<'_>, u8>,
|
||||
parent: ThreadedEnvelope<'_>,
|
||||
pad: String,
|
||||
weight: u8,
|
||||
) -> fmt::Result {
|
||||
let edges = graph
|
||||
.all_edges()
|
||||
.filter_map(|(a, b, w)| {
|
||||
if a == parent && *w == weight {
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if parent.id == "0" {
|
||||
f.write_str("root")?;
|
||||
} else {
|
||||
write!(f, "{}{}", parent.id.red(), ") ".dark_grey())?;
|
||||
|
||||
if !parent.subject.is_empty() {
|
||||
write!(f, "{} ", parent.subject.green())?;
|
||||
}
|
||||
|
||||
if !parent.from.is_empty() {
|
||||
let left = "<".dark_grey();
|
||||
let right = ">".dark_grey();
|
||||
write!(f, "{left}{}{right}", parent.from.blue())?;
|
||||
}
|
||||
|
||||
let date = parent.format_date(config);
|
||||
let cursor_date_begin_col = terminal::size().unwrap().0 - date.len() as u16;
|
||||
|
||||
let dots =
|
||||
"·".repeat((cursor_date_begin_col - cursor::position().unwrap().0 - 2) as usize);
|
||||
write!(f, " {} {}", dots.dark_grey(), date.dark_yellow())?;
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
|
||||
let edges_count = edges.len();
|
||||
for (i, b) in edges.into_iter().enumerate() {
|
||||
let is_last = edges_count == i + 1;
|
||||
let (x, y) = if is_last {
|
||||
(' ', '└')
|
||||
} else {
|
||||
('│', '├')
|
||||
};
|
||||
|
||||
write!(f, "{pad}{y}─ ")?;
|
||||
|
||||
let pad = format!("{pad}{x} ");
|
||||
Self::fmt(f, config, graph, b, pad, weight + 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EnvelopesTree {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
EnvelopesTree::fmt(
|
||||
f,
|
||||
&self.config,
|
||||
self.envelopes.graph(),
|
||||
ThreadedEnvelope {
|
||||
id: "0",
|
||||
message_id: "0",
|
||||
from: "",
|
||||
subject: "",
|
||||
date: Default::default(),
|
||||
},
|
||||
String::new(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for EnvelopesTree {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.envelopes.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,13 +67,13 @@ impl AttachmentDownloadCommand {
|
|||
let attachments = email.attachments()?;
|
||||
|
||||
if attachments.is_empty() {
|
||||
printer.print_log(format!("No attachment found for message {id}!"))?;
|
||||
printer.log(format!("No attachment found for message {id}!"))?;
|
||||
continue;
|
||||
} else {
|
||||
emails_count += 1;
|
||||
}
|
||||
|
||||
printer.print_log(format!(
|
||||
printer.log(format!(
|
||||
"{} attachment(s) found for message {id}!",
|
||||
attachments.len()
|
||||
))?;
|
||||
|
@ -84,7 +84,7 @@ impl AttachmentDownloadCommand {
|
|||
.unwrap_or_else(|| Uuid::new_v4().to_string())
|
||||
.into();
|
||||
let filepath = account_config.get_download_file_path(&filename)?;
|
||||
printer.print_log(format!("Downloading {:?}…", filepath))?;
|
||||
printer.log(format!("Downloading {:?}…", filepath))?;
|
||||
fs::write(&filepath, &attachment.body)
|
||||
.with_context(|| format!("cannot save attachment at {filepath:?}"))?;
|
||||
attachments_count += 1;
|
||||
|
@ -92,9 +92,9 @@ impl AttachmentDownloadCommand {
|
|||
}
|
||||
|
||||
match attachments_count {
|
||||
0 => printer.print("No attachment found!"),
|
||||
1 => printer.print("Downloaded 1 attachment!"),
|
||||
n => printer.print(format!(
|
||||
0 => printer.out("No attachment found!"),
|
||||
1 => printer.out("Downloaded 1 attachment!"),
|
||||
n => printer.out(format!(
|
||||
"Downloaded {} attachment(s) from {} messages(s)!",
|
||||
n, emails_count,
|
||||
)),
|
||||
|
|
|
@ -60,7 +60,7 @@ impl MessageCopyCommand {
|
|||
|
||||
backend.copy_messages(source, target, ids).await?;
|
||||
|
||||
printer.print(format!(
|
||||
printer.out(format!(
|
||||
"Message(s) successfully copied from {source} to {target}!"
|
||||
))
|
||||
}
|
||||
|
|
|
@ -58,6 +58,6 @@ impl MessageDeleteCommand {
|
|||
|
||||
backend.delete_messages(folder, ids).await?;
|
||||
|
||||
printer.print(format!("Message(s) successfully removed from {folder}!"))
|
||||
printer.out(format!("Message(s) successfully removed from {folder}!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ impl MessageMoveCommand {
|
|||
|
||||
backend.move_messages(source, target, ids).await?;
|
||||
|
||||
printer.print(format!(
|
||||
printer.out(format!(
|
||||
"Message(s) successfully moved from {source} to {target}!"
|
||||
))
|
||||
}
|
||||
|
|
|
@ -139,6 +139,6 @@ impl MessageReadCommand {
|
|||
glue = "\n\n";
|
||||
}
|
||||
|
||||
printer.print(bodies)
|
||||
printer.out(bodies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,6 @@ impl MessageSaveCommand {
|
|||
|
||||
backend.add_message(folder, msg.as_bytes()).await?;
|
||||
|
||||
printer.print(format!("Message successfully saved to {folder}!"))
|
||||
printer.out(format!("Message successfully saved to {folder}!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,6 @@ impl MessageSendCommand {
|
|||
|
||||
backend.send_message_then_save_copy(msg.as_bytes()).await?;
|
||||
|
||||
printer.print("Message successfully sent!")
|
||||
printer.out("Message successfully sent!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,6 @@ impl MessageThreadCommand {
|
|||
glue = "\n\n";
|
||||
}
|
||||
|
||||
printer.print(bodies)
|
||||
printer.out(bodies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,6 @@ impl TemplateForwardCommand {
|
|||
.build()
|
||||
.await?;
|
||||
|
||||
printer.print(tpl)
|
||||
printer.out(tpl)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,6 +81,6 @@ impl TemplateReplyCommand {
|
|||
.build()
|
||||
.await?;
|
||||
|
||||
printer.print(tpl)
|
||||
printer.out(tpl)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,6 @@ impl TemplateSaveCommand {
|
|||
|
||||
backend.add_message(folder, &msg).await?;
|
||||
|
||||
printer.print(format!("Template successfully saved to {folder}!"))
|
||||
printer.out(format!("Template successfully saved to {folder}!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,6 @@ impl TemplateSendCommand {
|
|||
|
||||
backend.send_message_then_save_copy(&msg).await?;
|
||||
|
||||
printer.print("Message successfully sent!")
|
||||
printer.out("Message successfully sent!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,6 @@ impl TemplateWriteCommand {
|
|||
.build()
|
||||
.await?;
|
||||
|
||||
printer.print(tpl)
|
||||
printer.out(tpl)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,2 @@
|
|||
pub mod arg;
|
||||
pub mod command;
|
||||
|
||||
use color_eyre::Result;
|
||||
use email::template::Template;
|
||||
|
||||
use crate::printer::{Print, WriteColor};
|
||||
|
||||
impl Print for Template {
|
||||
fn print(&self, writer: &mut dyn WriteColor) -> Result<()> {
|
||||
self.as_str().print(writer)?;
|
||||
Ok(writer.reset()?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,6 @@ impl AddFolderCommand {
|
|||
|
||||
backend.add_folder(folder).await?;
|
||||
|
||||
printer.print(format!("Folder {folder} successfully created!"))
|
||||
printer.log(format!("Folder {folder} successfully created!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,6 @@ impl FolderDeleteCommand {
|
|||
|
||||
backend.delete_folder(folder).await?;
|
||||
|
||||
printer.print(format!("Folder {folder} successfully deleted!"))
|
||||
printer.log(format!("Folder {folder} successfully deleted!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,6 @@ impl FolderExpungeCommand {
|
|||
|
||||
backend.expunge_folder(folder).await?;
|
||||
|
||||
printer.print(format!("Folder {folder} successfully expunged!"))
|
||||
printer.log(format!("Folder {folder} successfully expunged!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ use tracing::info;
|
|||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig, folder::Folders,
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
config::TomlConfig,
|
||||
folder::{Folders, FoldersTable},
|
||||
printer::Printer,
|
||||
};
|
||||
|
||||
|
@ -51,9 +54,10 @@ impl FolderListCommand {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let folders: Folders = backend.list_folders().await?.into();
|
||||
let folders = Folders::from(backend.list_folders().await?);
|
||||
let table = FoldersTable::from(folders).with_some_width(self.table_max_width);
|
||||
|
||||
printer.print_table(folders, self.table_max_width)?;
|
||||
printer.log(table)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,6 @@ impl FolderPurgeCommand {
|
|||
|
||||
backend.purge_folder(folder).await?;
|
||||
|
||||
printer.print(format!("Folder {folder} successfully purged!"))
|
||||
printer.log(format!("Folder {folder} successfully purged!"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,9 @@ pub mod arg;
|
|||
pub mod command;
|
||||
pub mod config;
|
||||
|
||||
use color_eyre::Result;
|
||||
use comfy_table::{presets, Attribute, Cell, ContentArrangement, Row, Table};
|
||||
use serde::Serialize;
|
||||
use std::ops;
|
||||
|
||||
use crate::printer::{PrintTable, WriteColor};
|
||||
use comfy_table::{presets, Attribute, Cell, Row, Table};
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::{fmt, ops::Deref};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct Folder {
|
||||
|
@ -15,67 +12,46 @@ pub struct Folder {
|
|||
pub desc: String,
|
||||
}
|
||||
|
||||
impl From<&email::folder::Folder> for Folder {
|
||||
fn from(folder: &email::folder::Folder) -> Self {
|
||||
impl Folder {
|
||||
pub fn to_row(&self) -> Row {
|
||||
let mut row = Row::new();
|
||||
|
||||
row.add_cell(Cell::new(&self.name).fg(comfy_table::Color::Blue));
|
||||
row.add_cell(Cell::new(&self.desc).fg(comfy_table::Color::Green));
|
||||
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
impl From<email::folder::Folder> for Folder {
|
||||
fn from(folder: email::folder::Folder) -> Self {
|
||||
Folder {
|
||||
name: folder.name.clone(),
|
||||
desc: folder.desc.clone(),
|
||||
name: folder.name,
|
||||
desc: folder.desc,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&Folder> for Row {
|
||||
fn from(folder: &Folder) -> Self {
|
||||
let mut row = Row::new();
|
||||
row.add_cell(Cell::new(&folder.name).fg(comfy_table::Color::Blue));
|
||||
row.add_cell(Cell::new(&folder.desc).fg(comfy_table::Color::Green));
|
||||
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Folder> for Row {
|
||||
fn from(folder: Folder) -> Self {
|
||||
let mut row = Row::new();
|
||||
row.add_cell(Cell::new(folder.name).fg(comfy_table::Color::Blue));
|
||||
row.add_cell(Cell::new(folder.desc).fg(comfy_table::Color::Green));
|
||||
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct Folders(Vec<Folder>);
|
||||
|
||||
impl From<Folders> for Table {
|
||||
fn from(folders: Folders) -> Self {
|
||||
impl Folders {
|
||||
pub fn to_table(&self) -> Table {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_header(Row::from([
|
||||
Cell::new("NAME").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DESC").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(folders.0.into_iter().map(Row::from));
|
||||
.add_rows(self.iter().map(Folder::to_row));
|
||||
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Folders> for Table {
|
||||
fn from(folders: &Folders) -> Self {
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(presets::NOTHING)
|
||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
||||
.set_header(Row::from([
|
||||
Cell::new("NAME").add_attribute(Attribute::Reverse),
|
||||
Cell::new("DESC").add_attribute(Attribute::Reverse),
|
||||
]))
|
||||
.add_rows(folders.0.iter().map(Row::from));
|
||||
table
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Folders {
|
||||
impl Deref for Folders {
|
||||
type Target = Vec<Folder>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -85,19 +61,51 @@ impl ops::Deref for Folders {
|
|||
|
||||
impl From<email::folder::Folders> for Folders {
|
||||
fn from(folders: email::folder::Folders) -> Self {
|
||||
Folders(folders.iter().map(Folder::from).collect())
|
||||
Folders(folders.into_iter().map(Folder::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for Folders {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, table_max_width: Option<u16>) -> Result<()> {
|
||||
let mut table = Table::from(self);
|
||||
if let Some(width) = table_max_width {
|
||||
pub struct FoldersTable {
|
||||
folders: Folders,
|
||||
width: Option<u16>,
|
||||
}
|
||||
|
||||
impl FoldersTable {
|
||||
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Folders> for FoldersTable {
|
||||
fn from(folders: Folders) -> Self {
|
||||
Self {
|
||||
folders,
|
||||
width: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FoldersTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut table = self.folders.to_table();
|
||||
|
||||
if let Some(width) = self.width {
|
||||
table.set_width(width);
|
||||
}
|
||||
writeln!(writer)?;
|
||||
write!(writer, "{}", table)?;
|
||||
writeln!(writer)?;
|
||||
|
||||
writeln!(f)?;
|
||||
write!(f, "{table}")?;
|
||||
writeln!(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for FoldersTable {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.folders.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
let cli = Cli::parse();
|
||||
let mut printer = StdoutPrinter::new(cli.output, cli.color);
|
||||
let mut printer = StdoutPrinter::new(cli.output);
|
||||
let mut res = match cli.command {
|
||||
Some(cmd) => cmd.execute(&mut printer, cli.config_paths.as_ref()).await,
|
||||
None => {
|
||||
|
|
|
@ -33,7 +33,7 @@ impl ManualGenerateCommand {
|
|||
Man::new(cmd).render(&mut buffer)?;
|
||||
|
||||
fs::create_dir_all(&self.dir)?;
|
||||
printer.print_log(format!("Generating man page for command {cmd_name}…"))?;
|
||||
printer.log(format!("Generating man page for command {cmd_name}…"))?;
|
||||
fs::write(self.dir.join(format!("{}.1", cmd_name)), buffer)?;
|
||||
|
||||
for subcmd in subcmds {
|
||||
|
@ -42,14 +42,14 @@ impl ManualGenerateCommand {
|
|||
let mut buffer = Vec::new();
|
||||
Man::new(subcmd).render(&mut buffer)?;
|
||||
|
||||
printer.print_log(format!("Generating man page for subcommand {subcmd_name}…"))?;
|
||||
printer.log(format!("Generating man page for subcommand {subcmd_name}…"))?;
|
||||
fs::write(
|
||||
self.dir.join(format!("{}-{}.1", cmd_name, subcmd_name)),
|
||||
buffer,
|
||||
)?;
|
||||
}
|
||||
|
||||
printer.print(format!(
|
||||
printer.log(format!(
|
||||
"{subcmds_len} man page(s) successfully generated in {:?}!",
|
||||
self.dir
|
||||
))?;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pub mod args;
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod output;
|
||||
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
use clap::ValueEnum;
|
||||
use color_eyre::{eyre::eyre, eyre::Error, Result};
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, IsTerminal},
|
||||
str::FromStr,
|
||||
};
|
||||
use termcolor::ColorChoice;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
/// Represents the available output formats.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
||||
|
@ -49,59 +44,3 @@ impl<T: Serialize> OutputJson<T> {
|
|||
Self { response }
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent the available color configs.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum ColorFmt {
|
||||
Never,
|
||||
Always,
|
||||
Ansi,
|
||||
#[default]
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl FromStr for ColorFmt {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(fmt: &str) -> Result<Self, Self::Err> {
|
||||
match fmt {
|
||||
fmt if fmt.eq_ignore_ascii_case("never") => Ok(Self::Never),
|
||||
fmt if fmt.eq_ignore_ascii_case("always") => Ok(Self::Always),
|
||||
fmt if fmt.eq_ignore_ascii_case("ansi") => Ok(Self::Ansi),
|
||||
fmt if fmt.eq_ignore_ascii_case("auto") => Ok(Self::Auto),
|
||||
unknown => Err(eyre!("cannot parse color format {}", unknown)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorFmt> for ColorChoice {
|
||||
fn from(fmt: ColorFmt) -> Self {
|
||||
match fmt {
|
||||
ColorFmt::Never => Self::Never,
|
||||
ColorFmt::Always => Self::Always,
|
||||
ColorFmt::Ansi => Self::AlwaysAnsi,
|
||||
ColorFmt::Auto => {
|
||||
if io::stdout().is_terminal() {
|
||||
// Otherwise let's `termcolor` decide by
|
||||
// inspecting the environment. From the [doc]:
|
||||
//
|
||||
// * If `NO_COLOR` is set to any value, then
|
||||
// colors will be suppressed.
|
||||
//
|
||||
// * If `TERM` is set to dumb, then colors will be
|
||||
// suppressed.
|
||||
//
|
||||
// * In non-Windows environments, if `TERM` is not
|
||||
// set, then colors will be suppressed.
|
||||
//
|
||||
// [doc]: https://github.com/BurntSushi/termcolor#automatic-color-selection
|
||||
Self::Auto
|
||||
} else {
|
||||
// Colors should be deactivated if the terminal is
|
||||
// not a tty.
|
||||
Self::Never
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
73
src/printer.rs
Normal file
73
src/printer.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use color_eyre::{eyre::Context, Result};
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, Write},
|
||||
};
|
||||
|
||||
use crate::output::OutputFmt;
|
||||
|
||||
pub trait PrintTable {
|
||||
fn print(&self, writer: &mut dyn io::Write, table_max_width: Option<u16>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait Printer {
|
||||
fn out<T: fmt::Display + serde::Serialize>(&mut self, data: T) -> Result<()>;
|
||||
|
||||
fn log<T: fmt::Display + serde::Serialize>(&mut self, data: T) -> Result<()> {
|
||||
self.out(data)
|
||||
}
|
||||
|
||||
fn is_json(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StdoutPrinter {
|
||||
stdout: io::Stdout,
|
||||
stderr: io::Stderr,
|
||||
output: OutputFmt,
|
||||
}
|
||||
|
||||
impl StdoutPrinter {
|
||||
pub fn new(output: OutputFmt) -> Self {
|
||||
Self {
|
||||
stdout: io::stdout(),
|
||||
stderr: io::stderr(),
|
||||
output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StdoutPrinter {
|
||||
fn default() -> Self {
|
||||
Self::new(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Printer for StdoutPrinter {
|
||||
fn out<T: fmt::Display + serde::Serialize>(&mut self, data: T) -> Result<()> {
|
||||
match self.output {
|
||||
OutputFmt::Plain => {
|
||||
write!(self.stdout, "{data}")?;
|
||||
}
|
||||
OutputFmt::Json => {
|
||||
serde_json::to_writer(&mut self.stdout, &data)
|
||||
.context("cannot write json to writer")?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log<T: fmt::Display + serde::Serialize>(&mut self, data: T) -> Result<()> {
|
||||
if let OutputFmt::Plain = self.output {
|
||||
write!(&mut self.stderr, "{data}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_json(&self) -> bool {
|
||||
self.output == OutputFmt::Json
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
pub mod print;
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod printer;
|
||||
|
||||
use std::io;
|
||||
|
||||
pub use print::*;
|
||||
pub use printer::*;
|
||||
use termcolor::StandardStream;
|
||||
|
||||
pub trait WriteColor: io::Write + termcolor::WriteColor {}
|
||||
|
||||
impl WriteColor for StandardStream {}
|
|
@ -1,21 +0,0 @@
|
|||
use color_eyre::{eyre::Context, Result};
|
||||
|
||||
use crate::printer::WriteColor;
|
||||
|
||||
pub trait Print {
|
||||
fn print(&self, writer: &mut dyn WriteColor) -> Result<()>;
|
||||
}
|
||||
|
||||
impl Print for &str {
|
||||
fn print(&self, writer: &mut dyn WriteColor) -> Result<()> {
|
||||
writeln!(writer, "{}", self).context("cannot write string to writer")?;
|
||||
Ok(writer.reset()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Print for String {
|
||||
fn print(&self, writer: &mut dyn WriteColor) -> Result<()> {
|
||||
self.as_str().print(writer)?;
|
||||
Ok(writer.reset()?)
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
use clap::ArgMatches;
|
||||
use color_eyre::{eyre::Context, Report, Result};
|
||||
use std::fmt::Debug;
|
||||
use termcolor::StandardStream;
|
||||
|
||||
use crate::{
|
||||
output::{args, ColorFmt, OutputFmt},
|
||||
printer::{Print, WriteColor},
|
||||
};
|
||||
pub trait PrintTable {
|
||||
fn print_table(&self, writer: &mut dyn WriteColor, table_max_width: Option<u16>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait Printer {
|
||||
// TODO: rename end
|
||||
fn print<T: Debug + Print + serde::Serialize>(&mut self, data: T) -> Result<()>;
|
||||
// TODO: rename log
|
||||
fn print_log<T: Debug + Print>(&mut self, data: T) -> Result<()>;
|
||||
// TODO: rename table
|
||||
fn print_table<T: Debug + PrintTable>(
|
||||
&mut self,
|
||||
data: T,
|
||||
table_max_width: Option<u16>,
|
||||
) -> Result<()>;
|
||||
|
||||
fn is_json(&self) -> bool;
|
||||
}
|
||||
|
||||
pub struct StdoutPrinter {
|
||||
pub writer: Box<dyn WriteColor>,
|
||||
pub fmt: OutputFmt,
|
||||
}
|
||||
|
||||
impl Default for StdoutPrinter {
|
||||
fn default() -> Self {
|
||||
let fmt = OutputFmt::default();
|
||||
let writer = Box::new(StandardStream::stdout(ColorFmt::default().into()));
|
||||
Self { fmt, writer }
|
||||
}
|
||||
}
|
||||
|
||||
impl StdoutPrinter {
|
||||
pub fn new(fmt: OutputFmt, color: ColorFmt) -> Self {
|
||||
let writer = Box::new(StandardStream::stdout(color.into()));
|
||||
Self { fmt, writer }
|
||||
}
|
||||
}
|
||||
|
||||
impl Printer for StdoutPrinter {
|
||||
fn print_log<T: Debug + Print>(&mut self, data: T) -> Result<()> {
|
||||
match self.fmt {
|
||||
OutputFmt::Plain => data.print(self.writer.as_mut()),
|
||||
OutputFmt::Json => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn print<T: Debug + Print + serde::Serialize>(&mut self, data: T) -> Result<()> {
|
||||
match self.fmt {
|
||||
OutputFmt::Plain => data.print(self.writer.as_mut()),
|
||||
OutputFmt::Json => serde_json::to_writer(self.writer.as_mut(), &data)
|
||||
.context("cannot write json to writer"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_json(&self) -> bool {
|
||||
self.fmt == OutputFmt::Json
|
||||
}
|
||||
|
||||
fn print_table<T: Debug + PrintTable>(
|
||||
&mut self,
|
||||
data: T,
|
||||
table_max_width: Option<u16>,
|
||||
) -> Result<()> {
|
||||
data.print_table(self.writer.as_mut(), table_max_width)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutputFmt> for StdoutPrinter {
|
||||
fn from(fmt: OutputFmt) -> Self {
|
||||
Self::new(fmt, ColorFmt::Auto)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ArgMatches> for StdoutPrinter {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(m: &ArgMatches) -> Result<Self, Self::Error> {
|
||||
let fmt: OutputFmt = m
|
||||
.get_one::<String>(args::ARG_OUTPUT)
|
||||
.map(String::as_str)
|
||||
.unwrap()
|
||||
.parse()?;
|
||||
|
||||
let color: ColorFmt = m
|
||||
.get_one::<String>(args::ARG_COLOR)
|
||||
.map(String::as_str)
|
||||
.unwrap()
|
||||
.parse()?;
|
||||
|
||||
Ok(Self::new(fmt, color))
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ pub async fn edit_tpl_with_editor<P: Printer>(
|
|||
loop {
|
||||
match choice::post_edit() {
|
||||
Ok(PostEditChoice::Send) => {
|
||||
printer.print_log("Sending email…")?;
|
||||
printer.log("Sending email…")?;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut compiler = MmlCompilerBuilder::new();
|
||||
|
@ -93,7 +93,7 @@ pub async fn edit_tpl_with_editor<P: Printer>(
|
|||
backend.send_message_then_save_copy(&email).await?;
|
||||
|
||||
remove_local_draft()?;
|
||||
printer.print("Done!")?;
|
||||
printer.log("Done!")?;
|
||||
break;
|
||||
}
|
||||
Ok(PostEditChoice::Edit) => {
|
||||
|
@ -101,7 +101,7 @@ pub async fn edit_tpl_with_editor<P: Printer>(
|
|||
continue;
|
||||
}
|
||||
Ok(PostEditChoice::LocalDraft) => {
|
||||
printer.print("Email successfully saved locally")?;
|
||||
printer.log("Email successfully saved locally")?;
|
||||
break;
|
||||
}
|
||||
Ok(PostEditChoice::RemoteDraft) => {
|
||||
|
@ -121,7 +121,7 @@ pub async fn edit_tpl_with_editor<P: Printer>(
|
|||
)
|
||||
.await?;
|
||||
remove_local_draft()?;
|
||||
printer.print("Email successfully saved to drafts")?;
|
||||
printer.log("Email successfully saved to drafts")?;
|
||||
break;
|
||||
}
|
||||
Ok(PostEditChoice::Discard) => {
|
||||
|
|
Loading…
Reference in a new issue