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