move domain stuff to domain/, improve completion

This commit is contained in:
Clément DOUIN 2021-09-16 01:35:31 +02:00
parent 223537c4ca
commit 0ed1147f3d
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
26 changed files with 125 additions and 84 deletions

View file

@ -1,29 +0,0 @@
use anyhow::Result;
use clap::{self, App, Arg, ArgMatches, Shell, SubCommand};
use log::debug;
use std::io;
pub fn subcmds<'s>() -> Vec<App<'s, 's>> {
vec![SubCommand::with_name("completion")
.about("Generates the completion script for the given shell")
.args(&[Arg::with_name("shell")
.possible_values(&["bash", "zsh", "fish"])
.required(true)])]
}
pub fn matches<'a>(app: fn() -> App<'a, 'a>, matches: &ArgMatches) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("completion") {
debug!("completion command matched");
let shell = match matches.value_of("shell").unwrap() {
"fish" => Shell::Fish,
"zsh" => Shell::Zsh,
"bash" | _ => Shell::Bash,
};
debug!("shell: {}", shell);
app().gen_completions_to("himalaya", shell, &mut io::stdout());
return Ok(true);
};
debug!("nothing matched");
Ok(false)
}

View file

@ -1 +0,0 @@
pub mod cli;

34
src/compl/arg.rs Normal file
View file

@ -0,0 +1,34 @@
//! Module related to completion arguments.
//!
//! This module provides subcommands and an argument matcher for the completion command.
use anyhow::Result;
use clap::{self, App, Arg, ArgMatches, Shell, SubCommand};
use log::debug;
/// Subcommands related to the completion generation.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name("completion")
.about("Generates the completion script for the given shell")
.args(&[Arg::with_name("shell")
.possible_values(&Shell::variants()[..])
.required(true)])]
}
/// Enumeration of all possible completion matches.
pub enum Match<'a> {
/// Generate completion script for the given shell slice.
Generate(&'a str),
}
/// Completion arg matcher.
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Match<'a>>> {
if let Some(m) = m.subcommand_matches("completion") {
debug!("completion command matched");
let shell = m.value_of("shell").unwrap();
debug!("shell: {}", shell);
return Ok(Some(Match::Generate(shell)));
};
Ok(None)
}

16
src/compl/handler.rs Normal file
View file

@ -0,0 +1,16 @@
//! Module related to completion handling.
//!
//! This module gathers all completion actions triggered by the CLI.
use anyhow::{anyhow, Context, Result};
use clap::{App, Shell};
use std::{io, str::FromStr};
/// Generate completion script from the given [`clap::App`] for the given shell slice.
pub fn generate<'a>(shell: &'a str, mut app: App<'a, 'a>) -> Result<()> {
let shell = Shell::from_str(shell)
.map_err(|err| anyhow!(err))
.context("cannot parse shell")?;
app.gen_completions_to("himalaya", shell, &mut io::stdout());
Ok(())
}

9
src/compl/mod.rs Normal file
View file

@ -0,0 +1,9 @@
//! Module related to shell completion.
//!
//! This module allows users to generate autocompletion scripts for their shells. You can see the
//! list of available shells directly on the [clap's docs.rs website].
//!
//! [clap's docs.rs website]: https://docs.rs/clap/2.33.3/clap/enum.Shell.html
pub mod arg;
pub mod handler;

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use clap;
use log::debug;
use crate::domain::{config::entity::Config, imap::ImapServiceInterface};
use crate::domain::{config::entity::Config, imap::service::ImapServiceInterface};
pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![

4
src/domain/imap/mod.rs Normal file
View file

@ -0,0 +1,4 @@
//! Modules related to IMAP.
pub mod cli;
pub mod service;

View file

@ -4,9 +4,10 @@ use log::{debug, trace};
use native_tls::{self, TlsConnector, TlsStream};
use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, net::TcpStream};
use crate::{domain::account::entity::Account, flag::model::Flags, msg::model::Msg};
use super::config::entity::Config;
use crate::{
domain::{account::entity::Account, config::entity::Config, msg::entity::Msg},
flag::model::Flags,
};
type ImapSession = imap::Session<TlsStream<TcpStream>>;
type ImapMsgs = imap::types::ZeroCopy<Vec<imap::types::Fetch>>;

View file

@ -3,8 +3,7 @@ use clap;
use log::{debug, trace};
use crate::{
domain::imap::ImapServiceInterface,
mbox::model::Mboxes,
domain::{imap::service::ImapServiceInterface, mbox::entity::Mboxes},
output::service::{OutputService, OutputServiceInterface},
};

2
src/domain/mbox/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod cli;
pub mod entity;

View file

@ -3,4 +3,6 @@
pub mod account;
pub mod config;
pub mod imap;
pub mod mbox;
pub mod msg;
pub mod smtp;

View file

@ -15,14 +15,16 @@ use url::Url;
use super::{
body::Body,
entity::{Msg, MsgSerialized, Msgs},
headers::Headers,
model::{Msg, MsgSerialized, Msgs},
};
use crate::{
domain::{account::entity::Account, imap::ImapServiceInterface, smtp::*},
domain::{
account::entity::Account, imap::service::ImapServiceInterface, mbox::cli::mbox_target_arg,
smtp::service::SmtpServiceInterface,
},
flag::model::Flags,
input,
mbox::cli::mbox_target_arg,
output::service::{OutputService, OutputServiceInterface},
};

View file

@ -22,7 +22,7 @@ pub mod cli;
/// Here are the two **main structs** of this module: `Msg` and `Msgs` which
/// represent a *Mail* or *multiple Mails* in this crate.
pub mod model;
pub mod entity;
/// This module is used in the `Msg` struct, which should represent an
/// attachment of a msg.

3
src/domain/smtp/mod.rs Normal file
View file

@ -0,0 +1,3 @@
//! Modules related to SMTP.
pub mod service;

View file

@ -2,7 +2,10 @@ use anyhow::Result;
use clap;
use log::debug;
use crate::{domain::imap::ImapServiceInterface, flag::model::Flags, msg::cli::uid_arg};
use crate::{
domain::{imap::service::ImapServiceInterface, msg::cli::uid_arg},
flag::model::Flags,
};
fn flags_arg<'a>() -> clap::Arg<'a, 'a> {
clap::Arg::with_name("flags")

View file

@ -1 +0,0 @@
pub mod cli;

View file

@ -12,9 +12,7 @@
//!
//! [here]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
/// `comp` stands for `completion`. This module makes it possible to create autocompletion-settings
/// for himalaya for your shell :)
pub mod comp;
pub mod compl;
/// Everything which is related to the config files. For example the structure of your config file.
pub mod config;
@ -23,19 +21,10 @@ pub mod config;
/// read-flag.
pub mod flag;
/// A wrapper for creating connections easier to the IMAP-Servers.
pub mod imap;
/// Handles the input-interaction with the user. For example if you want to edit the body of your
/// message, his module takes care of the draft and calls your ~(neo)vim~ your favourite editor.
pub mod input;
/// Everything which is related to mboxes, for example creating or deleting some.
pub mod mbox;
/// Includes everything related to a message. This means: Body, Headers, Attachments, etc.
pub mod msg;
/// Handles the output. For example the JSON and HTML output.
pub mod output;

View file

@ -5,16 +5,20 @@ use log::{debug, trace};
use std::{convert::TryFrom, env, path::PathBuf};
use himalaya::{
comp,
compl,
config::cli::config_args,
domain::{
account::entity::Account, config::entity::Config, imap::ImapService, smtp::SmtpService,
account::entity::Account,
config::entity::Config,
imap::{self, service::ImapService},
mbox, msg,
smtp::service::SmtpService,
},
flag, imap, mbox, msg,
flag,
output::{cli::output_args, service::OutputService},
};
fn parse_args<'a>() -> clap::App<'a, 'a> {
fn create_app<'a>() -> clap::App<'a, 'a> {
clap::App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"))
@ -27,7 +31,7 @@ fn parse_args<'a>() -> clap::App<'a, 'a> {
.subcommands(imap::cli::subcmds())
.subcommands(mbox::cli::subcmds())
.subcommands(msg::cli::subcmds())
.subcommands(comp::cli::subcmds())
.subcommands(compl::arg::subcmds())
}
fn main() -> Result<()> {
@ -50,23 +54,23 @@ fn main() -> Result<()> {
// return Ok(msg_matches_mailto(&app, &url, smtp)?);
// }
let args = parse_args();
let arg_matches = args.get_matches();
let app = create_app();
let m = app.get_matches();
// Check completion before init config
if comp::cli::matches(parse_args, &arg_matches)? {
// Check shell completion BEFORE any entity or service initialization.
if let Some(compl::arg::Match::Generate(shell)) = compl::arg::matches(&m)? {
let app = create_app();
compl::handler::generate(shell, app)?;
return Ok(());
}
debug!("init output service");
let output = OutputService::new(arg_matches.value_of("output").unwrap());
debug!("output service: {:?}", output);
let output = OutputService::new(m.value_of("output").unwrap())?;
debug!("init mbox");
let mbox = arg_matches.value_of("mailbox").unwrap();
let mbox = m.value_of("mailbox").unwrap();
debug!("mbox: {}", mbox);
let config_path: PathBuf = arg_matches
let config_path: PathBuf = m
.value_of("config")
.map(|s| s.into())
.unwrap_or(Config::path()?);
@ -74,7 +78,7 @@ fn main() -> Result<()> {
let config = Config::try_from(config_path.clone())?;
trace!("{:#?}", config);
let account_name = arg_matches.value_of("account");
let account_name = m.value_of("account");
debug!("init account `{}`", account_name.unwrap_or("default"));
let account = Account::try_from((&config, account_name))?;
trace!("{:#?}", account);
@ -86,10 +90,10 @@ fn main() -> Result<()> {
let mut smtp = SmtpService::new(&account)?;
debug!("begin matching");
let _matched = mbox::cli::matches(&arg_matches, &output, &mut imap)?
|| flag::cli::matches(&arg_matches, &mut imap)?
|| imap::cli::matches(&arg_matches, &config, &mut imap)?
|| msg::cli::matches(&arg_matches, mbox, &account, &output, &mut imap, &mut smtp)?;
let _matched = mbox::cli::matches(&m, &output, &mut imap)?
|| flag::cli::matches(&m, &mut imap)?
|| imap::cli::matches(&m, &config, &mut imap)?
|| msg::cli::matches(&m, mbox, &account, &output, &mut imap, &mut smtp)?;
Ok(())
}

View file

@ -1,2 +0,0 @@
pub mod cli;
pub mod model;

View file

@ -1,6 +1,7 @@
use anyhow::Result;
use anyhow::{anyhow, Error, Result};
use log::debug;
use serde::Serialize;
use std::fmt;
use std::{fmt, str::FromStr};
#[derive(Debug, PartialEq)]
pub enum OutputFmt {
@ -8,11 +9,13 @@ pub enum OutputFmt {
Json,
}
impl From<&str> for OutputFmt {
fn from(slice: &str) -> Self {
impl FromStr for OutputFmt {
type Err = Error;
fn from_str(slice: &str) -> Result<Self, Self::Err> {
match slice {
"json" => Self::Json,
"plain" | _ => Self::Plain,
"JSON" | _ if slice.eq_ignore_ascii_case("json") => Ok(Self::Json),
"PLAIN" | _ if slice.eq_ignore_ascii_case("plain") => Ok(Self::Plain),
_ => Err(anyhow!("cannot parse output `{}`", slice)),
}
}
}
@ -21,7 +24,7 @@ impl fmt::Display for OutputFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let slice = match self {
&OutputFmt::Json => "JSON",
&OutputFmt::Plain => "Plain",
&OutputFmt::Plain => "PLAIN",
};
write!(f, "{}", slice)
}
@ -51,8 +54,11 @@ pub struct OutputService {
impl OutputService {
/// Create a new output-handler by setting the given formatting style.
pub fn new(fmt: &str) -> Self {
Self { fmt: fmt.into() }
pub fn new(slice: &str) -> Result<Self> {
debug!("init output service");
debug!("output: {}", slice);
let fmt = OutputFmt::from_str(slice)?;
Ok(Self { fmt })
}
/// Returns true, if the formatting should be plaintext.