diff --git a/src/comp/cli.rs b/src/comp/cli.rs deleted file mode 100644 index a64a331..0000000 --- a/src/comp/cli.rs +++ /dev/null @@ -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> { - 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 { - 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) -} diff --git a/src/comp/mod.rs b/src/comp/mod.rs deleted file mode 100644 index 4f77372..0000000 --- a/src/comp/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod cli; diff --git a/src/compl/arg.rs b/src/compl/arg.rs new file mode 100644 index 0000000..d123f93 --- /dev/null +++ b/src/compl/arg.rs @@ -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> { + 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>> { + 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) +} diff --git a/src/compl/handler.rs b/src/compl/handler.rs new file mode 100644 index 0000000..5fb0efc --- /dev/null +++ b/src/compl/handler.rs @@ -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(()) +} diff --git a/src/compl/mod.rs b/src/compl/mod.rs new file mode 100644 index 0000000..a333e6c --- /dev/null +++ b/src/compl/mod.rs @@ -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; diff --git a/src/imap/cli.rs b/src/domain/imap/cli.rs similarity index 95% rename from src/imap/cli.rs rename to src/domain/imap/cli.rs index e05d538..dce1092 100644 --- a/src/imap/cli.rs +++ b/src/domain/imap/cli.rs @@ -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> { vec![ diff --git a/src/domain/imap/mod.rs b/src/domain/imap/mod.rs new file mode 100644 index 0000000..f41d052 --- /dev/null +++ b/src/domain/imap/mod.rs @@ -0,0 +1,4 @@ +//! Modules related to IMAP. + +pub mod cli; +pub mod service; diff --git a/src/domain/imap.rs b/src/domain/imap/service.rs similarity index 99% rename from src/domain/imap.rs rename to src/domain/imap/service.rs index 093fc69..0b12de7 100644 --- a/src/domain/imap.rs +++ b/src/domain/imap/service.rs @@ -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>; type ImapMsgs = imap::types::ZeroCopy>; diff --git a/src/mbox/cli.rs b/src/domain/mbox/cli.rs similarity index 94% rename from src/mbox/cli.rs rename to src/domain/mbox/cli.rs index 30355fd..90cd0a3 100644 --- a/src/mbox/cli.rs +++ b/src/domain/mbox/cli.rs @@ -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}, }; diff --git a/src/mbox/model.rs b/src/domain/mbox/entity.rs similarity index 100% rename from src/mbox/model.rs rename to src/domain/mbox/entity.rs diff --git a/src/domain/mbox/mod.rs b/src/domain/mbox/mod.rs new file mode 100644 index 0000000..e85c41a --- /dev/null +++ b/src/domain/mbox/mod.rs @@ -0,0 +1,2 @@ +pub mod cli; +pub mod entity; diff --git a/src/domain/mod.rs b/src/domain/mod.rs index d690368..c3446e0 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -3,4 +3,6 @@ pub mod account; pub mod config; pub mod imap; +pub mod mbox; +pub mod msg; pub mod smtp; diff --git a/src/msg/attachment.rs b/src/domain/msg/attachment.rs similarity index 100% rename from src/msg/attachment.rs rename to src/domain/msg/attachment.rs diff --git a/src/msg/body.rs b/src/domain/msg/body.rs similarity index 100% rename from src/msg/body.rs rename to src/domain/msg/body.rs diff --git a/src/msg/cli.rs b/src/domain/msg/cli.rs similarity index 99% rename from src/msg/cli.rs rename to src/domain/msg/cli.rs index aa93092..4d4b714 100644 --- a/src/msg/cli.rs +++ b/src/domain/msg/cli.rs @@ -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}, }; diff --git a/src/msg/model.rs b/src/domain/msg/entity.rs similarity index 100% rename from src/msg/model.rs rename to src/domain/msg/entity.rs diff --git a/src/msg/headers.rs b/src/domain/msg/headers.rs similarity index 100% rename from src/msg/headers.rs rename to src/domain/msg/headers.rs diff --git a/src/msg/mod.rs b/src/domain/msg/mod.rs similarity index 98% rename from src/msg/mod.rs rename to src/domain/msg/mod.rs index 7c77f6f..ac0abf9 100644 --- a/src/msg/mod.rs +++ b/src/domain/msg/mod.rs @@ -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. diff --git a/src/domain/smtp/mod.rs b/src/domain/smtp/mod.rs new file mode 100644 index 0000000..81d6738 --- /dev/null +++ b/src/domain/smtp/mod.rs @@ -0,0 +1,3 @@ +//! Modules related to SMTP. + +pub mod service; diff --git a/src/domain/smtp.rs b/src/domain/smtp/service.rs similarity index 100% rename from src/domain/smtp.rs rename to src/domain/smtp/service.rs diff --git a/src/flag/cli.rs b/src/flag/cli.rs index 2573d34..c475d94 100644 --- a/src/flag/cli.rs +++ b/src/flag/cli.rs @@ -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") diff --git a/src/imap/mod.rs b/src/imap/mod.rs deleted file mode 100644 index 4f77372..0000000 --- a/src/imap/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod cli; diff --git a/src/lib.rs b/src/lib.rs index 92033d2..43da26b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/main.rs b/src/main.rs index 918395f..c8b23e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(()) } diff --git a/src/mbox/mod.rs b/src/mbox/mod.rs deleted file mode 100644 index 9225677..0000000 --- a/src/mbox/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod cli; -pub mod model; diff --git a/src/output/service.rs b/src/output/service.rs index 786f811..21c88b0 100644 --- a/src/output/service.rs +++ b/src/output/service.rs @@ -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 { 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 { + debug!("init output service"); + debug!("output: {}", slice); + let fmt = OutputFmt::from_str(slice)?; + Ok(Self { fmt }) } /// Returns true, if the formatting should be plaintext.