refactor envelope with clap derive api

This commit is contained in:
Clément DOUIN 2023-12-06 23:12:06 +01:00
parent 4a77253c1d
commit 2c33dd2f9f
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
14 changed files with 304 additions and 642 deletions

View file

@ -6,6 +6,7 @@ use crate::{
account::command::AccountSubcommand,
completion::command::CompletionGenerateCommand,
config::{self, TomlConfig},
envelope::command::EnvelopeSubcommand,
folder::command::FolderSubcommand,
manual::command::ManualGenerateCommand,
output::{ColorFmt, OutputFmt},
@ -96,6 +97,10 @@ pub enum HimalayaCommand {
#[command(subcommand)]
Folder(FolderSubcommand),
/// Subcommand to manage envelopes
#[command(subcommand)]
Envelope(EnvelopeSubcommand),
/// Generate manual pages to a directory
#[command(arg_required_else_help = true)]
Manual(ManualGenerateCommand),
@ -110,6 +115,7 @@ impl HimalayaCommand {
match self {
Self::Account(cmd) => cmd.execute(printer, config).await,
Self::Folder(cmd) => cmd.execute(printer, config).await,
Self::Envelope(cmd) => cmd.execute(printer, config).await,
Self::Manual(cmd) => cmd.execute(printer).await,
Self::Completion(cmd) => cmd.execute(printer).await,
}

View file

@ -1,91 +0,0 @@
//! Email CLI module.
//!
//! This module contains the command matcher, the subcommands and the
//! arguments related to the email domain.
use anyhow::Result;
use clap::{Arg, ArgMatches, Command};
use crate::ui::table;
const ARG_PAGE: &str = "page";
const ARG_PAGE_SIZE: &str = "page-size";
const CMD_LIST: &str = "list";
const CMD_ENVELOPE: &str = "envelope";
pub type Page = usize;
pub type PageSize = usize;
/// Represents the email commands.
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd {
List(table::args::MaxTableWidth, Option<PageSize>, Page),
}
/// Email command matcher.
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
let cmd = if let Some(m) = m.subcommand_matches(CMD_ENVELOPE) {
if let Some(m) = m.subcommand_matches(CMD_LIST) {
let max_table_width = table::args::parse_max_width(m);
let page_size = parse_page_size_arg(m);
let page = parse_page_arg(m);
Some(Cmd::List(max_table_width, page_size, page))
} else {
Some(Cmd::List(None, None, 0))
}
} else {
None
};
Ok(cmd)
}
/// Represents the envelope subcommand.
pub fn subcmd() -> Command {
Command::new(CMD_ENVELOPE)
.about("Subcommand to manage envelopes")
.long_about("Subcommand to manage envelopes like list")
.subcommands([Command::new(CMD_LIST)
.alias("lst")
.about("List envelopes")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())])
}
/// Represents the page size argument.
fn page_size_arg() -> Arg {
Arg::new(ARG_PAGE_SIZE)
.help("Page size")
.long("page-size")
.short('s')
.value_name("INT")
}
/// Represents the page size argument parser.
fn parse_page_size_arg(matches: &ArgMatches) -> Option<usize> {
matches
.get_one::<String>(ARG_PAGE_SIZE)
.and_then(|s| s.parse().ok())
}
/// Represents the page argument.
fn page_arg() -> Arg {
Arg::new(ARG_PAGE)
.help("Page number")
.short('p')
.long("page")
.value_name("INT")
.default_value("1")
}
/// Represents the page argument parser.
fn parse_page_arg(matches: &ArgMatches) -> usize {
matches
.get_one::<String>(ARG_PAGE)
.unwrap()
.parse()
.ok()
.map(|page| 1.max(page) - 1)
.unwrap_or_default()
}

View file

@ -0,0 +1,68 @@
use anyhow::Result;
use clap::Parser;
use log::info;
use crate::{
account::arg::name::AccountNameFlag,
backend::Backend,
cache::arg::disable::DisableCacheFlag,
config::TomlConfig,
folder::arg::name::FolderNameOptionalArg,
printer::{PrintTableOpts, Printer},
ui::arg::max_width::MaxTableWidthFlag,
};
/// List all envelopes from a folder
#[derive(Debug, Parser)]
pub struct EnvelopeListCommand {
#[command(flatten)]
pub folder: FolderNameOptionalArg,
/// The page number
#[arg(long, short, value_name = "NUMBER", default_value = "1")]
pub page: usize,
/// The page size
#[arg(long, short = 's', value_name = "NUMBER")]
pub page_size: Option<usize>,
#[command(flatten)]
pub table: MaxTableWidthFlag,
#[command(flatten)]
pub account: AccountNameFlag,
#[command(flatten)]
pub cache: DisableCacheFlag,
}
impl EnvelopeListCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing envelope list command");
let folder = &self.folder.name;
let some_account_name = self.account.name.as_ref().map(String::as_str);
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(some_account_name, self.cache.disable)?;
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
let page_size = self
.page_size
.unwrap_or(account_config.email_listing_page_size());
let page = 1.max(self.page) - 1;
let envelopes = backend.list_envelopes(folder, page_size, page).await?;
printer.print_table(
Box::new(envelopes),
PrintTableOpts {
format: &account_config.email_reading_format,
max_width: self.table.max_width,
},
)?;
Ok(())
}
}

View file

@ -0,0 +1,24 @@
pub mod list;
use anyhow::Result;
use clap::Subcommand;
use crate::{config::TomlConfig, printer::Printer};
use self::list::EnvelopeListCommand;
/// Subcommand to manage envelopes
#[derive(Debug, Subcommand)]
pub enum EnvelopeSubcommand {
/// List all envelopes from a folder
#[command(alias = "lst")]
List(EnvelopeListCommand),
}
impl EnvelopeSubcommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
match self {
Self::List(cmd) => cmd.execute(printer, config).await,
}
}
}

View file

@ -1,32 +0,0 @@
use anyhow::Result;
use email::account::config::AccountConfig;
use log::{debug, trace};
use crate::{
backend::Backend,
printer::{PrintTableOpts, Printer},
};
pub async fn list<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &Backend,
folder: &str,
max_width: Option<usize>,
page_size: Option<usize>,
page: usize,
) -> Result<()> {
let page_size = page_size.unwrap_or(config.email_listing_page_size());
debug!("page size: {}", page_size);
let envelopes = backend.list_envelopes(&folder, page_size, page).await?;
trace!("envelopes: {:?}", envelopes);
printer.print_table(
Box::new(envelopes),
PrintTableOpts {
format: &config.email_reading_format,
max_width,
},
)
}

View file

@ -1,7 +1,6 @@
pub mod args;
pub mod command;
pub mod config;
pub mod flag;
pub mod handlers;
use anyhow::Result;
use email::account::config::AccountConfig;

View file

@ -6,7 +6,7 @@
use anyhow::Result;
use clap::{Arg, ArgAction, ArgMatches, Command};
use crate::{folder, template};
use crate::template;
const ARG_CRITERIA: &str = "criterion";
const ARG_HEADERS: &str = "headers";
@ -71,7 +71,7 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
Some(Cmd::Attachments(ids))
} else if let Some(m) = m.subcommand_matches(CMD_COPY) {
let ids = parse_ids_arg(m);
let folder = folder::args::parse_target_arg(m);
let folder = "INBOX";
Some(Cmd::Copy(ids, folder))
} else if let Some(m) = m.subcommand_matches(CMD_DELETE) {
let ids = parse_ids_arg(m);
@ -83,7 +83,7 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
Some(Cmd::Forward(id, headers, body))
} else if let Some(m) = m.subcommand_matches(CMD_MOVE) {
let ids = parse_ids_arg(m);
let folder = folder::args::parse_target_arg(m);
let folder = "INBOX";
Some(Cmd::Move(ids, folder))
} else if let Some(m) = m.subcommand_matches(CMD_READ) {
let ids = parse_ids_arg(m);
@ -158,12 +158,12 @@ pub fn subcmd() -> Command {
Command::new(CMD_COPY)
.alias("cp")
.about("Copy emails to the given folder")
.arg(folder::args::target_arg())
// .arg(folder::args::target_arg())
.arg(ids_arg()),
Command::new(CMD_MOVE)
.alias("mv")
.about("Move emails to the given folder")
.arg(folder::args::target_arg())
// .arg(folder::args::target_arg())
.arg(ids_arg()),
Command::new(CMD_DELETE)
.aliases(["remove", "rm"])

View file

@ -1,4 +1,5 @@
use clap::Parser;
use email::account::config::DEFAULT_INBOX_FOLDER;
/// The folder name argument parser
#[derive(Debug, Parser)]
@ -7,3 +8,11 @@ pub struct FolderNameArg {
#[arg(name = "folder-name", value_name = "FOLDER")]
pub name: String,
}
/// The optional folder name argument parser
#[derive(Debug, Parser)]
pub struct FolderNameOptionalArg {
/// The name of the folder
#[arg(name = "folder-name", value_name = "FOLDER", default_value = DEFAULT_INBOX_FOLDER)]
pub name: String,
}

View file

@ -1,241 +0,0 @@
//! Folder CLI module.
//!
//! This module provides subcommands, arguments and a command matcher
//! related to the folder domain.
use std::collections::HashSet;
use anyhow::Result;
use clap::{self, Arg, ArgAction, ArgMatches, Command};
use log::{debug, info};
use crate::ui::table;
const ARG_ALL: &str = "all";
const ARG_EXCLUDE: &str = "exclude";
const ARG_INCLUDE: &str = "include";
const ARG_GLOBAL_SOURCE: &str = "global-source";
const ARG_SOURCE: &str = "source";
const ARG_TARGET: &str = "target";
const CMD_CREATE: &str = "create";
const CMD_DELETE: &str = "delete";
const CMD_EXPUNGE: &str = "expunge";
const CMD_FOLDER: &str = "folder";
const CMD_LIST: &str = "list";
/// Represents the folder commands.
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd {
Create,
List(table::args::MaxTableWidth),
Expunge,
Delete,
}
/// Represents the folder command matcher.
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
let cmd = if let Some(m) = m.subcommand_matches(CMD_FOLDER) {
if let Some(_) = m.subcommand_matches(CMD_EXPUNGE) {
info!("expunge folder subcommand matched");
Some(Cmd::Expunge)
} else if let Some(_) = m.subcommand_matches(CMD_CREATE) {
debug!("create folder command matched");
Some(Cmd::Create)
} else if let Some(m) = m.subcommand_matches(CMD_LIST) {
debug!("list folders command matched");
let max_table_width = table::args::parse_max_width(m);
Some(Cmd::List(max_table_width))
} else if let Some(_) = m.subcommand_matches(CMD_DELETE) {
debug!("delete folder command matched");
Some(Cmd::Delete)
} else {
info!("no folder subcommand matched, falling back to subcommand list");
Some(Cmd::List(None))
}
} else {
None
};
Ok(cmd)
}
/// Represents the folder subcommand.
pub fn subcmd() -> Command {
Command::new(CMD_FOLDER)
.about("Subcommand to manage folders")
.long_about("Subcommand to manage folders like list, expunge or delete")
.subcommands([
Command::new(CMD_EXPUNGE).about("Delete emails marked for deletion"),
Command::new(CMD_CREATE)
.aliases(["add", "new"])
.about("Create a new folder"),
Command::new(CMD_LIST)
.about("List folders")
.arg(table::args::max_width()),
Command::new(CMD_DELETE)
.aliases(["remove", "rm"])
.about("Delete a folder with all its emails"),
])
}
/// Represents the source folder argument.
pub fn global_args() -> impl IntoIterator<Item = Arg> {
[Arg::new(ARG_GLOBAL_SOURCE)
.help("Override the default INBOX folder")
.long_help(
"Override the default INBOX folder.
The given folder will be used by default for all other commands (when
applicable).",
)
.long("folder")
.short('f')
.global(true)
.value_name("name")]
}
pub fn parse_global_source_arg(matches: &ArgMatches) -> Option<&str> {
matches
.get_one::<String>(ARG_GLOBAL_SOURCE)
.map(String::as_str)
}
pub fn source_arg(help: &'static str) -> Arg {
Arg::new(ARG_SOURCE).help(help).value_name("name")
}
pub fn parse_source_arg(matches: &ArgMatches) -> Option<&str> {
matches.get_one::<String>(ARG_SOURCE).map(String::as_str)
}
/// Represents the all folders argument.
pub fn all_arg(help: &'static str) -> Arg {
Arg::new(ARG_ALL)
.help(help)
.long("all-folders")
.alias("all")
.short('A')
.action(ArgAction::SetTrue)
.conflicts_with(ARG_SOURCE)
.conflicts_with(ARG_INCLUDE)
.conflicts_with(ARG_EXCLUDE)
}
/// Represents the folders to include argument.
pub fn include_arg(help: &'static str) -> Arg {
Arg::new(ARG_INCLUDE)
.help(help)
.long("include-folder")
.alias("only")
.short('F')
.value_name("FOLDER")
.num_args(1..)
.action(ArgAction::Append)
.conflicts_with(ARG_SOURCE)
.conflicts_with(ARG_ALL)
.conflicts_with(ARG_EXCLUDE)
}
/// Represents the folders to exclude argument.
pub fn exclude_arg(help: &'static str) -> Arg {
Arg::new(ARG_EXCLUDE)
.help(help)
.long("exclude-folder")
.alias("except")
.short('x')
.value_name("FOLDER")
.num_args(1..)
.action(ArgAction::Append)
.conflicts_with(ARG_SOURCE)
.conflicts_with(ARG_ALL)
.conflicts_with(ARG_INCLUDE)
}
/// Represents the folders to exclude argument parser.
pub fn parse_exclude_arg(m: &ArgMatches) -> HashSet<String> {
m.get_many::<String>(ARG_EXCLUDE)
.unwrap_or_default()
.map(ToOwned::to_owned)
.collect()
}
/// Represents the target folder argument.
pub fn target_arg() -> Arg {
Arg::new(ARG_TARGET)
.help("Specifies the target folder")
.value_name("TARGET")
.required(true)
}
/// Represents the target folder argument parser.
pub fn parse_target_arg(matches: &ArgMatches) -> &str {
matches.get_one::<String>(ARG_TARGET).unwrap().as_str()
}
#[cfg(test)]
mod tests {
use clap::{error::ErrorKind, Command};
use super::*;
#[test]
fn it_should_match_cmds() {
let arg = Command::new("himalaya")
.subcommand(subcmd())
.get_matches_from(&["himalaya", "folders"]);
assert_eq!(Some(Cmd::List(None)), matches(&arg).unwrap());
let arg = Command::new("himalaya")
.subcommand(subcmd())
.get_matches_from(&["himalaya", "folders", "list", "--max-width", "20"]);
assert_eq!(Some(Cmd::List(Some(20))), matches(&arg).unwrap());
}
#[test]
fn it_should_match_source_arg() {
macro_rules! get_matches_from {
($($arg:expr),*) => {
Command::new("himalaya")
.arg(source_arg())
.get_matches_from(&["himalaya", $($arg,)*])
};
}
let app = get_matches_from![];
assert_eq!(None, app.get_one::<String>(ARG_SOURCE).map(String::as_str));
let app = get_matches_from!["-f", "SOURCE"];
assert_eq!(
Some("SOURCE"),
app.get_one::<String>(ARG_SOURCE).map(String::as_str)
);
let app = get_matches_from!["--folder", "SOURCE"];
assert_eq!(
Some("SOURCE"),
app.get_one::<String>(ARG_SOURCE).map(String::as_str)
);
}
#[test]
fn it_should_match_target_arg() {
macro_rules! get_matches_from {
($($arg:expr),*) => {
Command::new("himalaya")
.arg(target_arg())
.try_get_matches_from_mut(&["himalaya", $($arg,)*])
};
}
let app = get_matches_from![];
assert_eq!(ErrorKind::MissingRequiredArgument, app.unwrap_err().kind());
let app = get_matches_from!["TARGET"];
assert_eq!(
Some("TARGET"),
app.unwrap()
.get_one::<String>(ARG_TARGET)
.map(String::as_str)
);
}
}

View file

@ -49,3 +49,194 @@ impl FolderSubcommand {
}
}
}
#[cfg(test)]
mod tests {
use async_trait::async_trait;
use email::{
account::config::AccountConfig,
backend::Backend,
envelope::{Envelope, Envelopes},
flag::Flags,
folder::{Folder, Folders},
message::Messages,
};
use std::{any::Any, fmt::Debug, io};
use termcolor::ColorSpec;
use crate::printer::{Print, PrintTable, WriteColor};
use super::*;
#[tokio::test]
async fn it_should_list_mboxes() {
#[derive(Debug, Default, Clone)]
struct StringWriter {
content: String,
}
impl io::Write for StringWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.content
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.content = String::default();
Ok(())
}
}
impl termcolor::WriteColor for StringWriter {
fn supports_color(&self) -> bool {
false
}
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
io::Result::Ok(())
}
fn reset(&mut self) -> io::Result<()> {
io::Result::Ok(())
}
}
impl WriteColor for StringWriter {}
#[derive(Debug, Default)]
struct PrinterServiceTest {
pub writer: StringWriter,
}
impl Printer for PrinterServiceTest {
fn print_table<T: Debug + PrintTable + erased_serde::Serialize + ?Sized>(
&mut self,
data: Box<T>,
opts: PrintTableOpts,
) -> anyhow::Result<()> {
data.print_table(&mut self.writer, opts)?;
Ok(())
}
fn print_log<T: Debug + Print>(&mut self, _data: T) -> anyhow::Result<()> {
unimplemented!()
}
fn print<T: Debug + Print + serde::Serialize>(
&mut self,
_data: T,
) -> anyhow::Result<()> {
unimplemented!()
}
fn is_json(&self) -> bool {
unimplemented!()
}
}
struct TestBackend;
#[async_trait]
impl Backend for TestBackend {
fn name(&self) -> String {
unimplemented!();
}
async fn add_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn list_folders(&mut self) -> email::Result<Folders> {
Ok(Folders::from_iter([
Folder {
name: "INBOX".into(),
desc: "desc".into(),
},
Folder {
name: "Sent".into(),
desc: "desc".into(),
},
]))
}
async fn expunge_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn purge_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn delete_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn get_envelope(&mut self, _: &str, _: &str) -> email::Result<Envelope> {
unimplemented!();
}
async fn list_envelopes(
&mut self,
_: &str,
_: usize,
_: usize,
) -> email::Result<Envelopes> {
unimplemented!()
}
async fn search_envelopes(
&mut self,
_: &str,
_: &str,
_: &str,
_: usize,
_: usize,
) -> email::Result<Envelopes> {
unimplemented!()
}
async fn add_email(&mut self, _: &str, _: &[u8], _: &Flags) -> email::Result<String> {
unimplemented!()
}
async fn get_emails(&mut self, _: &str, _: Vec<&str>) -> email::Result<Messages> {
unimplemented!()
}
async fn preview_emails(&mut self, _: &str, _: Vec<&str>) -> email::Result<Messages> {
unimplemented!()
}
async fn copy_emails(&mut self, _: &str, _: &str, _: Vec<&str>) -> email::Result<()> {
unimplemented!()
}
async fn move_emails(&mut self, _: &str, _: &str, _: Vec<&str>) -> email::Result<()> {
unimplemented!()
}
async fn delete_emails(&mut self, _: &str, _: Vec<&str>) -> email::Result<()> {
unimplemented!()
}
async fn add_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> email::Result<()> {
unimplemented!()
}
async fn set_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> email::Result<()> {
unimplemented!()
}
async fn remove_flags(
&mut self,
_: &str,
_: Vec<&str>,
_: &Flags,
) -> email::Result<()> {
unimplemented!()
}
fn as_any(&self) -> &dyn Any {
unimplemented!()
}
}
let account_config = AccountConfig::default();
let mut printer = PrinterServiceTest::default();
let mut backend = TestBackend {};
assert!(list(&account_config, &mut printer, &mut backend, None)
.await
.is_ok());
assert_eq!(
concat![
"\n",
"NAME │DESC \n",
"INBOX │desc \n",
"Sent │desc \n",
"\n"
],
printer.writer.content
);
}
}

View file

@ -1,247 +0,0 @@
//! Folder handling module.
//!
//! This module gathers all folder actions triggered by the CLI.
use anyhow::Result;
use dialoguer::Confirm;
use email::account::config::AccountConfig;
use std::process;
use crate::{
backend::Backend,
printer::{PrintTableOpts, Printer},
};
use super::Folders;
pub async fn expunge<P: Printer>(printer: &mut P, backend: &Backend, folder: &str) -> Result<()> {
backend.expunge_folder(folder).await?;
printer.print(format!("Folder {folder} successfully expunged!"))
}
pub async fn list<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &Backend,
max_width: Option<usize>,
) -> Result<()> {
let folders: Folders = backend.list_folders().await?.into();
printer.print_table(
// TODO: remove Box
Box::new(folders),
PrintTableOpts {
format: &config.email_reading_format,
max_width,
},
)
}
pub async fn create<P: Printer>(printer: &mut P, backend: &Backend, folder: &str) -> Result<()> {
backend.add_folder(folder).await?;
printer.print("Folder successfully created!")
}
pub async fn delete<P: Printer>(printer: &mut P, backend: &Backend, folder: &str) -> Result<()> {
if let Some(false) | None = Confirm::new()
.with_prompt(format!("Confirm deletion of folder {folder}?"))
.default(false)
.report(false)
.interact_opt()?
{
process::exit(0);
};
backend.delete_folder(folder).await?;
printer.print("Folder successfully deleted!")
}
#[cfg(test)]
mod tests {
use async_trait::async_trait;
use email::{
account::config::AccountConfig,
backend::Backend,
envelope::{Envelope, Envelopes},
flag::Flags,
folder::{Folder, Folders},
message::Messages,
};
use std::{any::Any, fmt::Debug, io};
use termcolor::ColorSpec;
use crate::printer::{Print, PrintTable, WriteColor};
use super::*;
#[tokio::test]
async fn it_should_list_mboxes() {
#[derive(Debug, Default, Clone)]
struct StringWriter {
content: String,
}
impl io::Write for StringWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.content
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.content = String::default();
Ok(())
}
}
impl termcolor::WriteColor for StringWriter {
fn supports_color(&self) -> bool {
false
}
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
io::Result::Ok(())
}
fn reset(&mut self) -> io::Result<()> {
io::Result::Ok(())
}
}
impl WriteColor for StringWriter {}
#[derive(Debug, Default)]
struct PrinterServiceTest {
pub writer: StringWriter,
}
impl Printer for PrinterServiceTest {
fn print_table<T: Debug + PrintTable + erased_serde::Serialize + ?Sized>(
&mut self,
data: Box<T>,
opts: PrintTableOpts,
) -> anyhow::Result<()> {
data.print_table(&mut self.writer, opts)?;
Ok(())
}
fn print_log<T: Debug + Print>(&mut self, _data: T) -> anyhow::Result<()> {
unimplemented!()
}
fn print<T: Debug + Print + serde::Serialize>(
&mut self,
_data: T,
) -> anyhow::Result<()> {
unimplemented!()
}
fn is_json(&self) -> bool {
unimplemented!()
}
}
struct TestBackend;
#[async_trait]
impl Backend for TestBackend {
fn name(&self) -> String {
unimplemented!();
}
async fn add_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn list_folders(&mut self) -> email::Result<Folders> {
Ok(Folders::from_iter([
Folder {
name: "INBOX".into(),
desc: "desc".into(),
},
Folder {
name: "Sent".into(),
desc: "desc".into(),
},
]))
}
async fn expunge_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn purge_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn delete_folder(&mut self, _: &str) -> email::Result<()> {
unimplemented!();
}
async fn get_envelope(&mut self, _: &str, _: &str) -> email::Result<Envelope> {
unimplemented!();
}
async fn list_envelopes(
&mut self,
_: &str,
_: usize,
_: usize,
) -> email::Result<Envelopes> {
unimplemented!()
}
async fn search_envelopes(
&mut self,
_: &str,
_: &str,
_: &str,
_: usize,
_: usize,
) -> email::Result<Envelopes> {
unimplemented!()
}
async fn add_email(&mut self, _: &str, _: &[u8], _: &Flags) -> email::Result<String> {
unimplemented!()
}
async fn get_emails(&mut self, _: &str, _: Vec<&str>) -> email::Result<Messages> {
unimplemented!()
}
async fn preview_emails(&mut self, _: &str, _: Vec<&str>) -> email::Result<Messages> {
unimplemented!()
}
async fn copy_emails(&mut self, _: &str, _: &str, _: Vec<&str>) -> email::Result<()> {
unimplemented!()
}
async fn move_emails(&mut self, _: &str, _: &str, _: Vec<&str>) -> email::Result<()> {
unimplemented!()
}
async fn delete_emails(&mut self, _: &str, _: Vec<&str>) -> email::Result<()> {
unimplemented!()
}
async fn add_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> email::Result<()> {
unimplemented!()
}
async fn set_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> email::Result<()> {
unimplemented!()
}
async fn remove_flags(
&mut self,
_: &str,
_: Vec<&str>,
_: &Flags,
) -> email::Result<()> {
unimplemented!()
}
fn as_any(&self) -> &dyn Any {
unimplemented!()
}
}
let account_config = AccountConfig::default();
let mut printer = PrinterServiceTest::default();
let mut backend = TestBackend {};
assert!(list(&account_config, &mut printer, &mut backend, None)
.await
.is_ok());
assert_eq!(
concat![
"\n",
"NAME │DESC \n",
"INBOX │desc \n",
"Sent │desc \n",
"\n"
],
printer.writer.content
);
}
}

View file

@ -1,8 +1,6 @@
pub mod arg;
pub mod args;
pub mod command;
pub mod config;
pub mod handlers;
use anyhow::Result;
use serde::Serialize;

View file

@ -1,21 +0,0 @@
use clap::{Arg, ArgMatches};
const ARG_MAX_TABLE_WIDTH: &str = "max-table-width";
pub(crate) type MaxTableWidth = Option<usize>;
/// Represents the max table width argument.
pub fn max_width() -> Arg {
Arg::new(ARG_MAX_TABLE_WIDTH)
.help("Defines a maximum width for the table")
.long("max-width")
.short('w')
.value_name("INT")
}
/// Represents the max table width argument parser.
pub fn parse_max_width(matches: &ArgMatches) -> Option<usize> {
matches
.get_one::<String>(ARG_MAX_TABLE_WIDTH)
.and_then(|s| s.parse().ok())
}

View file

@ -1,5 +1,4 @@
pub mod arg;
pub mod args;
pub mod table;
pub use table::*;