mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-22 11:00:19 +00:00
improve config error management
This commit is contained in:
parent
d746a780ba
commit
01de392977
5 changed files with 124 additions and 104 deletions
148
src/config.rs
148
src/config.rs
|
@ -1,10 +1,59 @@
|
|||
use serde::Deserialize;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
env, fmt,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
path::PathBuf,
|
||||
result,
|
||||
};
|
||||
use toml;
|
||||
|
||||
// Error wrapper
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IoError(io::Error),
|
||||
ParseTomlError(toml::de::Error),
|
||||
GetEnvVarError(env::VarError),
|
||||
GetPathNotFoundError,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(config): ")?;
|
||||
match self {
|
||||
Error::IoError(err) => err.fmt(f),
|
||||
Error::ParseTomlError(err) => err.fmt(f),
|
||||
Error::GetEnvVarError(err) => err.fmt(f),
|
||||
Error::GetPathNotFoundError => write!(f, "path not found"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for Error {
|
||||
fn from(err: toml::de::Error) -> Error {
|
||||
Error::ParseTomlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<env::VarError> for Error {
|
||||
fn from(err: env::VarError) -> Error {
|
||||
Error::GetEnvVarError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Result wrapper
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Config
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ServerInfo {
|
||||
pub host: String,
|
||||
|
@ -28,61 +77,48 @@ pub struct Config {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new_from_file() -> Self {
|
||||
match read_file_content() {
|
||||
Err(err) => panic!(err),
|
||||
Ok(content) => toml::from_str(&content).unwrap(),
|
||||
}
|
||||
fn path_from_xdg() -> Result<PathBuf> {
|
||||
let path = env::var("XDG_CONFIG_HOME")?;
|
||||
let mut path = PathBuf::from(path);
|
||||
path.push("himalaya");
|
||||
path.push("config.toml");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn path_from_home(_err: Error) -> Result<PathBuf> {
|
||||
let path = env::var("HOME")?;
|
||||
let mut path = PathBuf::from(path);
|
||||
path.push(".config");
|
||||
path.push("himalaya");
|
||||
path.push("config.toml");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn path_from_tmp(_err: Error) -> Result<PathBuf> {
|
||||
let mut path = env::temp_dir();
|
||||
path.push("himalaya");
|
||||
path.push("config.toml");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn new_from_file() -> Result<Self> {
|
||||
let mut file = File::open(
|
||||
Self::path_from_xdg()
|
||||
.or_else(Self::path_from_home)
|
||||
.or_else(Self::path_from_tmp)
|
||||
.or_else(|_| Err(Error::GetPathNotFoundError))?,
|
||||
)?;
|
||||
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content)?;
|
||||
|
||||
Ok(toml::from_str(&content)?)
|
||||
}
|
||||
|
||||
pub fn email_full(&self) -> String {
|
||||
format!("{} <{}>", self.name, self.email)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_xdg() -> Option<PathBuf> {
|
||||
match env::var("XDG_CONFIG_HOME") {
|
||||
Err(_) => None,
|
||||
Ok(path_str) => {
|
||||
let mut path = PathBuf::from(path_str);
|
||||
path.push("himalaya");
|
||||
path.push("config.toml");
|
||||
Some(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_home() -> Option<PathBuf> {
|
||||
match env::var("HOME") {
|
||||
Err(_) => None,
|
||||
Ok(path_str) => {
|
||||
let mut path = PathBuf::from(path_str);
|
||||
path.push(".config");
|
||||
path.push("himalaya");
|
||||
path.push("config.toml");
|
||||
Some(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_tmp() -> Option<PathBuf> {
|
||||
let mut path = env::temp_dir();
|
||||
path.push("himalaya");
|
||||
path.push("config.toml");
|
||||
Some(path)
|
||||
}
|
||||
|
||||
pub fn file_path() -> PathBuf {
|
||||
match from_xdg().or_else(from_home).or_else(from_tmp) {
|
||||
None => panic!("Config file path not found."),
|
||||
Some(path) => path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_file_content() -> Result<String, io::Error> {
|
||||
let path = file_path();
|
||||
let mut file = File::open(path)?;
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content)?;
|
||||
Ok(content)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::env::temp_dir;
|
|||
use std::fs::{remove_file, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::Command;
|
||||
use std::{error, fmt, result};
|
||||
use std::{fmt, result};
|
||||
|
||||
// Error wrapper
|
||||
|
||||
|
@ -19,14 +19,6 @@ impl fmt::Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match *self {
|
||||
Error::IoError(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IoError(err)
|
||||
|
@ -37,7 +29,7 @@ impl From<io::Error> for Error {
|
|||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Utils
|
||||
// Editor utils
|
||||
|
||||
fn open_with_template(template: &[u8]) -> Result<String> {
|
||||
// Create temporary draft
|
||||
|
|
25
src/imap.rs
25
src/imap.rs
|
@ -1,7 +1,7 @@
|
|||
use imap;
|
||||
use mailparse::{self, MailHeaderMap};
|
||||
use native_tls::{self, TlsConnector, TlsStream};
|
||||
use std::{error, fmt, net::TcpStream, result};
|
||||
use std::{fmt, net::TcpStream, result};
|
||||
|
||||
use crate::config;
|
||||
use crate::email::Email;
|
||||
|
@ -19,30 +19,20 @@ pub enum Error {
|
|||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(imap): ")?;
|
||||
match self {
|
||||
Error::CreateTlsConnectorError(err) => err.fmt(f),
|
||||
Error::CreateImapSession(err) => err.fmt(f),
|
||||
Error::ReadEmailNotFoundError(uid) => {
|
||||
write!(f, "No email found for UID {}", uid)
|
||||
write!(f, "no email found for uid {}", uid)
|
||||
}
|
||||
Error::ReadEmailEmptyPartError(uid, mime) => {
|
||||
write!(f, "No {} content found for UID {}", mime, uid)
|
||||
write!(f, "no {} content found for uid {}", mime, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match *self {
|
||||
Error::CreateTlsConnectorError(ref err) => Some(err),
|
||||
Error::CreateImapSession(ref err) => Some(err),
|
||||
Error::ReadEmailNotFoundError(_) => None,
|
||||
Error::ReadEmailEmptyPartError(_, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<native_tls::Error> for Error {
|
||||
fn from(err: native_tls::Error) -> Error {
|
||||
Error::CreateTlsConnectorError(err)
|
||||
|
@ -77,6 +67,7 @@ impl ImapConnector {
|
|||
.and_then(|v| if v.starts_with(mime) { Some(()) } else { None })
|
||||
.is_some()
|
||||
{
|
||||
// TODO: push part instead of body str
|
||||
parts.push(part.get_body().unwrap_or(String::new()))
|
||||
}
|
||||
}
|
||||
|
@ -136,9 +127,9 @@ impl ImapConnector {
|
|||
self.sess.select(mbox)?;
|
||||
|
||||
match self.sess.uid_fetch(uid, "BODY[]")?.first() {
|
||||
None => return Err(Error::ReadEmailNotFoundError(uid.to_string())),
|
||||
Some(email_raw) => {
|
||||
let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap();
|
||||
None => Err(Error::ReadEmailNotFoundError(uid.to_string())),
|
||||
Some(fetch) => {
|
||||
let email = mailparse::parse_mail(fetch.body().unwrap_or(&[])).unwrap();
|
||||
let mut parts = vec![];
|
||||
Self::extract_subparts_by_mime(mime, &email, &mut parts);
|
||||
|
||||
|
|
42
src/main.rs
42
src/main.rs
|
@ -6,8 +6,8 @@ mod mailbox;
|
|||
mod smtp;
|
||||
mod table;
|
||||
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use std::{error, fmt, process::exit, result};
|
||||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
use std::{fmt, process::exit, result};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::imap::ImapConnector;
|
||||
|
@ -15,31 +15,24 @@ use crate::table::DisplayTable;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
EditorError(editor::Error),
|
||||
ConfigError(config::Error),
|
||||
ImapError(imap::Error),
|
||||
EditorError(editor::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::EditorError(err) => err.fmt(f),
|
||||
Error::ConfigError(err) => err.fmt(f),
|
||||
Error::ImapError(err) => err.fmt(f),
|
||||
Error::EditorError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match *self {
|
||||
Error::EditorError(ref err) => Some(err),
|
||||
Error::ImapError(ref err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<editor::Error> for Error {
|
||||
fn from(err: editor::Error) -> Error {
|
||||
Error::EditorError(err)
|
||||
impl From<config::Error> for Error {
|
||||
fn from(err: config::Error) -> Error {
|
||||
Error::ConfigError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,6 +42,12 @@ impl From<imap::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<editor::Error> for Error {
|
||||
fn from(err: editor::Error) -> Error {
|
||||
Error::EditorError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Result wrapper
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
@ -76,6 +75,7 @@ fn run() -> Result<()> {
|
|||
.version("0.1.0")
|
||||
.about("📫 Minimalist CLI email client")
|
||||
.author("soywod <clement.douin@posteo.net>")
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.subcommand(SubCommand::with_name("list").about("Lists all available mailboxes"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("search")
|
||||
|
@ -126,7 +126,7 @@ fn run() -> Result<()> {
|
|||
.get_matches();
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("list") {
|
||||
let config = Config::new_from_file();
|
||||
let config = Config::new_from_file()?;
|
||||
let mboxes = ImapConnector::new(config.imap)?
|
||||
.list_mailboxes()?
|
||||
.to_table();
|
||||
|
@ -135,7 +135,7 @@ fn run() -> Result<()> {
|
|||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("search") {
|
||||
let config = Config::new_from_file();
|
||||
let config = Config::new_from_file()?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
|
||||
if let Some(matches) = matches.values_of("query") {
|
||||
|
@ -171,7 +171,7 @@ fn run() -> Result<()> {
|
|||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("read") {
|
||||
let config = Config::new_from_file();
|
||||
let config = Config::new_from_file()?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
let mime = matches.value_of("mime-type").unwrap();
|
||||
|
@ -181,7 +181,7 @@ fn run() -> Result<()> {
|
|||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("write") {
|
||||
let config = Config::new_from_file();
|
||||
let config = Config::new_from_file()?;
|
||||
let draft = editor::open_with_new_template()?;
|
||||
|
||||
smtp::send(&config, draft.as_bytes());
|
||||
|
@ -194,7 +194,7 @@ fn run() -> Result<()> {
|
|||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("Error: {}", err);
|
||||
eprintln!("Error {}", err);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use mailparse;
|
|||
|
||||
use crate::config;
|
||||
|
||||
// TODO: improve error management
|
||||
pub fn send(config: &config::Config, bytes: &[u8]) {
|
||||
let email_origin = mailparse::parse_mail(bytes).unwrap();
|
||||
let email = email_origin
|
||||
|
|
Loading…
Reference in a new issue