From 7a6ebc0cd03499b1020bd7a91a313dee3c3d9ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 15 Jun 2023 15:45:36 +0200 Subject: [PATCH] add new datetime options --- CHANGELOG.md | 6 ++ Cargo.lock | 10 +-- Cargo.toml | 4 +- src/config/config.rs | 2 + src/domain/account/config.rs | 15 +++++ src/domain/email/handlers.rs | 25 +++++--- src/domain/envelope/envelope.rs | 27 +------- src/domain/envelope/envelopes.rs | 106 +++++++++++++++++++++++++++---- 8 files changed, 142 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e077a6f..488ecfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implemented OAuth 2.0 refresh token flow for IMAP and SMTP, which means that access tokens are now automatically refreshed and is transparent for users. - Added `imap-oauth2-redirect-host` and `smtp-oauth2-redirect-host` options to customize the redirect server host name (default: `localhost`). - Added `imap-oauth2-redirect-port` and `smtp-oauth2-redirect-port` options to customize the redirect server port (default: `9999`). +- Added `email-listing-datetime-fmt` to customize envelopes datetime format. See format spec at . +- Added `email-listing-local-datetime` to transform envelopes datetime's timezone to the user's local one. For example, if the user's local is set to `UTC`, the envelope date `2023-06-15T09:00:00+02:00` becomes `2023-06-15T07:00:00-00:00`. + +### Fixed + +- Fixed missing `<` and `>` around `Message-ID` and `In-Reply-To` headers. ## [0.8.0] - 2023-06-03 diff --git a/Cargo.lock b/Cargo.lock index f08a486..f6bde53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1215,7 +1215,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "himalaya" -version = "0.8.1-beta" +version = "0.8.1" dependencies = [ "anyhow", "atty", @@ -2199,9 +2199,9 @@ dependencies = [ [[package]] name = "pimalaya-email" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b881982050e29d8f9141a57f0be3f87471394462a2f1695471d5a81f44b4b3a" +checksum = "8375dc804686e9e03a8b6f87d80b9038b5a734efb4b77f81b90b07b8ac1d4499" dependencies = [ "advisory-lock", "ammonia", @@ -2244,9 +2244,9 @@ dependencies = [ [[package]] name = "pimalaya-email-tpl" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f640b701926112e28b025cea9c9d50a2bfe329862eeb4acbf2d62edeb53fb19b" +checksum = "6a838dd91468bf79997ead555d6a5b93f066e363259925ec931d49591b1ebb79" dependencies = [ "chumsky 0.9.0", "log", diff --git a/Cargo.toml b/Cargo.toml index 9377951..66c021c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "himalaya" description = "CLI to manage your emails." -version = "0.8.1-beta" +version = "0.8.1" authors = ["soywod "] edition = "2021" license = "MIT" @@ -44,7 +44,7 @@ indicatif = "0.17" log = "0.4" md5 = "0.7.0" once_cell = "1.16.0" -pimalaya-email = { version = "=0.10.0", default-features = false } +pimalaya-email = { version = "=0.11.0", default-features = false } pimalaya-keyring = "=0.0.4" pimalaya-oauth2 = "=0.0.3" pimalaya-process = "=0.0.2" diff --git a/src/config/config.rs b/src/config/config.rs index 439c562..660aa24 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -33,6 +33,8 @@ pub struct DeserializedConfig { pub folder_aliases: Option>, pub email_listing_page_size: Option, + pub email_listing_datetime_fmt: Option, + pub email_listing_datetime_local_tz: Option, pub email_reading_headers: Option>, #[serde( default, diff --git a/src/domain/account/config.rs b/src/domain/account/config.rs index 350a1d5..3ebfd1d 100644 --- a/src/domain/account/config.rs +++ b/src/domain/account/config.rs @@ -32,6 +32,8 @@ pub struct DeserializedAccountConfig { pub folder_aliases: Option>, pub email_listing_page_size: Option, + pub email_listing_datetime_fmt: Option, + pub email_listing_datetime_local_tz: Option, pub email_reading_headers: Option>, #[serde( default, @@ -131,6 +133,19 @@ impl DeserializedAccountConfig { email_listing_page_size: self .email_listing_page_size .or_else(|| config.email_listing_page_size), + email_listing_datetime_fmt: self + .email_listing_datetime_fmt + .as_ref() + .map(ToOwned::to_owned) + .or_else(|| { + config + .email_listing_datetime_fmt + .as_ref() + .map(ToOwned::to_owned) + }), + email_listing_datetime_local_tz: self + .email_listing_datetime_local_tz + .or_else(|| config.email_listing_datetime_local_tz), email_reading_headers: self .email_reading_headers .as_ref() diff --git a/src/domain/email/handlers.rs b/src/domain/email/handlers.rs index 2a73871..11cdef2 100644 --- a/src/domain/email/handlers.rs +++ b/src/domain/email/handlers.rs @@ -149,8 +149,11 @@ pub fn list( let page_size = page_size.unwrap_or(config.email_listing_page_size()); debug!("page size: {}", page_size); - let mut envelopes: Envelopes = backend.list_envelopes(&folder, page_size, page)?.into(); - envelopes.remap_ids(id_mapper)?; + let envelopes = Envelopes::from_backend( + config, + id_mapper, + backend.list_envelopes(&folder, page_size, page)?, + )?; trace!("envelopes: {:?}", envelopes); printer.print_table( @@ -326,10 +329,11 @@ pub fn search( ) -> Result<()> { let folder = config.folder_alias(folder)?; let page_size = page_size.unwrap_or(config.email_listing_page_size()); - let mut envelopes: Envelopes = backend - .search_envelopes(&folder, &query, "", page_size, page)? - .into(); - envelopes.remap_ids(id_mapper)?; + let envelopes = Envelopes::from_backend( + config, + id_mapper, + backend.search_envelopes(&folder, &query, "", page_size, page)?, + )?; let opts = PrintTableOpts { format: &config.email_reading_format, max_width, @@ -352,10 +356,11 @@ pub fn sort( ) -> Result<()> { let folder = config.folder_alias(folder)?; let page_size = page_size.unwrap_or(config.email_listing_page_size()); - let mut envelopes: Envelopes = backend - .search_envelopes(&folder, &query, &sort, page_size, page)? - .into(); - envelopes.remap_ids(id_mapper)?; + let envelopes = Envelopes::from_backend( + config, + id_mapper, + backend.search_envelopes(&folder, &query, &sort, page_size, page)?, + )?; let opts = PrintTableOpts { format: &config.email_reading_format, max_width, diff --git a/src/domain/envelope/envelope.rs b/src/domain/envelope/envelope.rs index d58c499..c683de2 100644 --- a/src/domain/envelope/envelope.rs +++ b/src/domain/envelope/envelope.rs @@ -1,15 +1,10 @@ -use chrono::{DateTime, Local}; -use serde::{Serialize, Serializer}; +use serde::Serialize; use crate::{ ui::{Cell, Row, Table}, Flag, Flags, }; -fn date(date: &DateTime, s: S) -> Result { - s.serialize_str(&date.to_rfc3339()) -} - #[derive(Clone, Debug, Default, Serialize)] pub struct Mailbox { pub name: Option, @@ -22,23 +17,7 @@ pub struct Envelope { pub flags: Flags, pub subject: String, pub from: Mailbox, - #[serde(serialize_with = "date")] - pub date: DateTime, -} - -impl From<&pimalaya_email::Envelope> for Envelope { - fn from(envelope: &pimalaya_email::Envelope) -> Self { - Envelope { - id: envelope.id.clone(), - flags: envelope.flags.clone().into(), - subject: envelope.subject.clone(), - from: Mailbox { - name: envelope.from.name.clone(), - addr: envelope.from.addr.clone(), - }, - date: envelope.date.clone(), - } - } + pub date: String, } impl Table for Envelope { @@ -75,7 +54,7 @@ impl Table for Envelope { } else { &self.from.addr }; - let date = self.date.to_rfc3339(); + let date = &self.date; Row::new() .cell(Cell::new(id).bold_if(unseen).red()) diff --git a/src/domain/envelope/envelopes.rs b/src/domain/envelope/envelopes.rs index f3352e0..dc98a23 100644 --- a/src/domain/envelope/envelopes.rs +++ b/src/domain/envelope/envelopes.rs @@ -1,11 +1,12 @@ use anyhow::Result; +use pimalaya_email::AccountConfig; use serde::Serialize; use std::ops; use crate::{ printer::{PrintTable, PrintTableOpts, WriteColor}, ui::Table, - Envelope, IdMapper, + Envelope, IdMapper, Mailbox, }; /// Represents the list of envelopes. @@ -13,11 +14,28 @@ use crate::{ pub struct Envelopes(Vec); impl Envelopes { - pub fn remap_ids(&mut self, id_mapper: &IdMapper) -> Result<()> { - for envelope in &mut self.0 { - envelope.id = id_mapper.get_or_create_alias(&envelope.id)?; - } - Ok(()) + pub fn from_backend( + config: &AccountConfig, + id_mapper: &IdMapper, + envelopes: pimalaya_email::Envelopes, + ) -> Result { + let envelopes = envelopes + .iter() + .map(|envelope| { + Ok(Envelope { + id: id_mapper.get_or_create_alias(&envelope.id)?, + flags: envelope.flags.clone().into(), + subject: envelope.subject.clone(), + from: Mailbox { + name: envelope.from.name.clone(), + addr: envelope.from.addr.clone(), + }, + date: envelope.format_date(config), + }) + }) + .collect::>>()?; + + Ok(Envelopes(envelopes)) } } @@ -29,12 +47,6 @@ impl ops::Deref for Envelopes { } } -impl From for Envelopes { - fn from(envelopes: pimalaya_email::Envelopes) -> Self { - Envelopes(envelopes.iter().map(Envelope::from).collect()) - } -} - impl PrintTable for Envelopes { fn print_table(&self, writer: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> { writeln!(writer)?; @@ -43,3 +55,73 @@ impl PrintTable for Envelopes { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::env; + + use chrono::DateTime; + use pimalaya_email::AccountConfig; + + use crate::{Envelopes, IdMapper}; + + #[test] + fn default_datetime_fmt() { + let config = AccountConfig::default(); + let id_mapper = IdMapper::Dummy; + + let envelopes = pimalaya_email::Envelopes::from_iter([pimalaya_email::Envelope { + date: DateTime::parse_from_rfc3339("2023-06-15T09:42:00+04:00").unwrap(), + ..Default::default() + }]); + let envelopes = Envelopes::from_backend(&config, &id_mapper, envelopes).unwrap(); + + let expected_date = "2023-06-15 09:42+04:00"; + let date = &envelopes.first().unwrap().date; + + assert_eq!(date, expected_date); + } + + #[test] + fn custom_datetime_fmt() { + let id_mapper = IdMapper::Dummy; + let config = AccountConfig { + email_listing_datetime_fmt: Some("%d/%m/%Y %Hh%M".into()), + ..AccountConfig::default() + }; + + let envelopes = pimalaya_email::Envelopes::from_iter([pimalaya_email::Envelope { + date: DateTime::parse_from_rfc3339("2023-06-15T09:42:00+04:00").unwrap(), + ..Default::default() + }]); + let envelopes = Envelopes::from_backend(&config, &id_mapper, envelopes).unwrap(); + + let expected_date = "15/06/2023 09h42"; + let date = &envelopes.first().unwrap().date; + + assert_eq!(date, expected_date); + } + + #[test] + fn custom_datetime_fmt_with_local_tz() { + env::set_var("TZ", "UTC"); + + let id_mapper = IdMapper::Dummy; + let config = AccountConfig { + email_listing_datetime_fmt: Some("%d/%m/%Y %Hh%M".into()), + email_listing_datetime_local_tz: Some(true), + ..AccountConfig::default() + }; + + let envelopes = pimalaya_email::Envelopes::from_iter([pimalaya_email::Envelope { + date: DateTime::parse_from_rfc3339("2023-06-15T09:42:00+04:00").unwrap(), + ..Default::default() + }]); + let envelopes = Envelopes::from_backend(&config, &id_mapper, envelopes).unwrap(); + + let expected_date = "15/06/2023 05h42"; + let date = &envelopes.first().unwrap().date; + + assert_eq!(date, expected_date); + } +}