add date_time parser

This commit is contained in:
Michael Zhang 2021-03-09 03:12:07 -06:00
parent 880ef94db5
commit 0162d39deb
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
11 changed files with 172 additions and 110 deletions

72
Cargo.lock generated
View file

@ -70,13 +70,13 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.42"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -527,7 +527,7 @@ dependencies = [
"proc-macro2",
"quote 1.0.9",
"strsim 0.9.3",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -538,7 +538,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -549,7 +549,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -562,7 +562,7 @@ dependencies = [
"derive_builder_core",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -574,7 +574,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -615,7 +615,7 @@ checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -708,9 +708,9 @@ dependencies = [
[[package]]
name = "format-bytes"
version = "0.2.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc35f5e45d6b31053cea13078ffc6fa52fa8617aa54b7ac2011720d9c009e04f"
checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762"
dependencies = [
"format-bytes-macros",
"proc-macro-hack",
@ -725,7 +725,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -800,7 +800,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -1086,9 +1086,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.86"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a"
[[package]]
name = "libgit2-sys"
@ -1336,9 +1336,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.7.0"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
@ -1426,6 +1426,7 @@ dependencies = [
"assert_matches",
"async-trait",
"bytes",
"chrono",
"derive_builder",
"futures",
"log",
@ -1434,7 +1435,6 @@ dependencies = [
"pest_derive",
"tokio",
"tokio-rustls",
"tokio-stream",
"tokio-util",
"webpki-roots",
]
@ -1507,7 +1507,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -1523,9 +1523,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.4"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
[[package]]
name = "pin-utils"
@ -1576,7 +1576,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
"version_check",
]
@ -1858,22 +1858,22 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.123"
version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.123"
version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -1904,7 +1904,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -2027,7 +2027,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -2059,9 +2059,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.60"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512"
dependencies = [
"proc-macro2",
"quote 1.0.9",
@ -2182,7 +2182,7 @@ checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -2413,7 +2413,7 @@ dependencies = [
"log",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
"wasm-bindgen-shared",
]
@ -2435,7 +2435,7 @@ checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -2584,7 +2584,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]
[[package]]
@ -2608,5 +2608,5 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.60",
"syn 1.0.62",
]

View file

@ -14,18 +14,18 @@ members = ["imap", "smtp"]
[dependencies]
crossterm = "0.19.0"
anyhow = "1.0.38"
async-trait = "0.1.42"
async-trait = "0.1.48"
cfg-if = "1.0.0"
chrono = "0.4.19"
fern = { version = "0.6.0", features = ["colored"] }
format-bytes = "0.2.0"
format-bytes = "0.2.2"
futures = "0.3.13"
inotify = { version = "0.9.2", features = ["stream"] }
log = "0.4.14"
notify-rust = { version = "4.2.2", default-features = false, features = ["z"] }
notify-rust = { version = "4.3.0", default-features = false, features = ["z"] }
parking_lot = "0.11.1"
# pgp = "0.7.1"
serde = { version = "1.0.123", features = ["derive"] }
serde = { version = "1.0.124", features = ["derive"] }
structopt = "0.3.21"
tokio = { version = "1.2.0", features = ["full"] }
tokio-rustls = "0.22.0"

View file

@ -8,13 +8,11 @@ categories = ["email", "network-programming", "parser-implementations"]
license = "MIT OR Apache-2.0"
edition = "2018"
[badges]
maintenance = { status = "passively-maintained" }
[dependencies]
anyhow = "1.0.38"
async-trait = "0.1.42"
bytes = { version = "1.0.1" }
chrono = "0.4.19"
derive_builder = "0.9.0"
futures = "0.3.12"
log = "0.4.14"
@ -25,7 +23,6 @@ pest = { git = "https://github.com/iptq/pest", rev = "6a4d3a3d10e42a3ee605ca979d
pest_derive = { git = "https://github.com/iptq/pest", rev = "6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99" }
tokio = { version = "1.1.1", features = ["full"] }
tokio-rustls = "0.22.0"
tokio-stream = "0.1.3"
tokio-util = { version = "0.6.3" }
webpki-roots = "0.21.0"

View file

@ -184,7 +184,7 @@ async fn listen<C>(
conn: ReadHalf<C>,
mut cmd_rx: mpsc::UnboundedReceiver<Command2>,
greeting_tx: oneshot::Sender<()>,
mut exit_rx: oneshot::Receiver<()>,
exit_rx: oneshot::Receiver<()>,
) -> Result<ReadHalf<C>>
where
C: AsyncRead + Unpin,
@ -197,7 +197,7 @@ where
let mut exit_rx = exit_rx.map_err(|_| ()).shared();
// let mut exit_fut = Some(exit_rx.fuse());
// let mut fut1 = None;
let mut cache = String::new();
let cache = String::new();
loop {
// let mut next_line = String::new();
@ -227,19 +227,6 @@ where
}
resp = read_fut => {
// trace!("read line {:?}", next_line);
// res should not be None here
// cache += &next_line;
// let resp = match parse_response(&cache) {
// Ok(v) => {
// cache.clear();
// v
// }
// Err(e) => {
// error!("parse error: {}", e);
// continue;
// }
// };
let resp = match resp {
Some(Ok(v)) => v,
a => { error!("failed: {:?}", a); bail!("fuck"); },

View file

@ -13,16 +13,12 @@ impl Decoder for ImapCodec {
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
let s = std::str::from_utf8(src)?;
debug!("-------------------------");
debug!("s: {:?}", s);
match parse_streamed_response(s) {
Ok((resp, len)) => {
src.advance(len);
return Ok(Some(resp));
}
Err(e) => {
warn!("failed to parse: {:?} {}", e, e);
}
Err(e) => {}
};
Ok(None)

View file

@ -5,8 +5,7 @@ use super::Rule;
type PSR<'a> = Box<ParserState<'a, Rule>>;
/// This is a hack around the literal syntax to allow us to parse characters statefully.
pub(crate) fn literal_internal(mut state: PSR) -> PestResult<PSR> {
// debug!("STATE: {:?}", state);
pub(crate) fn literal_internal(state: PSR) -> PestResult<PSR> {
use pest::Atomicity;
// yoinked from the generated code
@ -19,10 +18,7 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult<PSR> {
#[allow(non_snake_case, unused_variables)]
pub fn number(state: PSR) -> PestResult<PSR> {
state.rule(Rule::number, |state| {
state.sequence(|state| {
debug!("number::atomic::sequence");
digit(state).and_then(|state| state.repeat(digit))
})
state.sequence(|state| digit(state).and_then(|state| state.repeat(digit)))
})
}
#[inline]
@ -37,10 +33,6 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult<PSR> {
#[inline]
#[allow(non_snake_case, unused_variables)]
pub fn crlf(state: PSR) -> PestResult<PSR> {
debug!(
"running rule 'crlf' {:?}",
state.queue().iter().rev().take(10).collect::<Vec<_>>()
);
state.sequence(|state| state.match_string("\r")?.match_string("\n"))
}
@ -63,15 +55,11 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult<PSR> {
QueueableToken::Start { input_pos: pos, .. } => pos,
_ => unreachable!(),
};
debug!("start_pos: {}, end_pos: {}", start_pos, end_pos);
let inp = state.position().get_str();
let seg = &inp[start_pos..end_pos];
match seg.parse::<usize>() {
Ok(v) => {
debug!("got length: {}", v);
v
}
Ok(v) => v,
Err(e) => {
error!(
"failed to parse int from {}..{} {:?}: {}",
@ -95,7 +83,6 @@ pub(crate) fn literal_internal(mut state: PSR) -> PestResult<PSR> {
state
})
})
// todo!("hit internal state: {:?}", state,);
}
pub(crate) fn noop(state: PSR) -> PestResult<PSR> {

View file

@ -8,6 +8,7 @@ mod tests;
use std::fmt::Debug;
use std::str::FromStr;
use chrono::{DateTime, FixedOffset, TimeZone};
use pest::{error::Error, iterators::Pair, Parser};
use crate::response::*;
@ -153,7 +154,7 @@ fn build_msg_att_static(pair: Pair<Rule>) -> AttributeValue {
match pair.as_rule() {
Rule::msg_att_static_internaldate => {
AttributeValue::InternalDate(build_string(unwrap1(unwrap1(pair))))
AttributeValue::InternalDate(build_date_time(unwrap1(pair)))
}
Rule::msg_att_static_rfc822_size => AttributeValue::Rfc822Size(build_number(unwrap1(pair))),
Rule::msg_att_static_envelope => AttributeValue::Envelope(build_envelope(unwrap1(pair))),
@ -272,7 +273,6 @@ fn build_capabilities(pair: Pair<Rule>) -> Vec<Capability> {
if !matches!(pair.as_rule(), Rule::capability_data) {
unreachable!("{:#?}", pair);
}
pair.into_inner().map(build_capability).collect()
}
@ -323,9 +323,7 @@ fn build_flag(mut pair: Pair<Rule>) -> MailboxFlag {
}
fn build_mailbox_data(pair: Pair<Rule>) -> MailboxData {
if !matches!(pair.as_rule(), Rule::mailbox_data) {
unreachable!("{:#?}", pair);
}
assert!(matches!(pair.as_rule(), Rule::mailbox_data));
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
@ -357,9 +355,7 @@ fn build_mailbox_data(pair: Pair<Rule>) -> MailboxData {
}
fn build_mailbox_list(pair: Pair<Rule>) -> (Vec<String>, Option<String>, String) {
if !matches!(pair.as_rule(), Rule::mailbox_list) {
unreachable!("{:#?}", pair);
}
assert!(matches!(pair.as_rule(), Rule::mailbox_list));
let mut pairs = pair.into_inner();
let mut pair = pairs.next().unwrap();
@ -411,9 +407,7 @@ where
T: FromStr,
T::Err: Debug,
{
if !matches!(pair.as_rule(), Rule::nz_number | Rule::number) {
unreachable!("not a number {:#?}", pair);
}
assert!(matches!(pair.as_rule(), Rule::nz_number | Rule::number));
pair.as_str().parse::<T>().unwrap()
}
@ -430,9 +424,6 @@ fn build_astring(pair: Pair<Rule>) -> String {
}
}
/// Wrapper around [build_string][1], except return None for the `nil` case
///
/// [1]: self::build_string
fn build_nstring(pair: Pair<Rule>) -> Option<String> {
assert!(matches!(pair.as_rule(), Rule::nstring));
let pair = unwrap1(pair);
@ -475,3 +466,60 @@ fn build_literal(pair: Pair<Rule>) -> String {
let literal_str = pairs.next().unwrap();
literal_str.as_str().to_owned()
}
fn parse_zone(s: impl AsRef<str>) -> ParseResult<FixedOffset> {
let mut pairs = Rfc3501::parse(Rule::zone, s.as_ref())?;
let pair = pairs.next().unwrap();
Ok(build_zone(pair))
}
fn build_zone(pair: Pair<Rule>) -> FixedOffset {
assert!(matches!(pair.as_rule(), Rule::zone));
let n = pair.as_str().parse::<i32>().unwrap();
let sign = if n != 0 { n / n.abs() } else { 1 };
let h = n.abs() / 100;
let m = n.abs() % 100;
FixedOffset::east(sign * (h * 60 + m) * 60)
}
fn build_date_time(pair: Pair<Rule>) -> DateTime<FixedOffset> {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
assert!(matches!(pair.as_rule(), Rule::date_day_fixed));
let day = pair.as_str().trim().parse::<u32>().unwrap();
let pair = pairs.next().unwrap();
assert!(matches!(pair.as_rule(), Rule::date_month));
let month = match pair.as_str() {
"Jan" => 1,
"Feb" => 2,
"Mar" => 3,
"Apr" => 4,
"May" => 5,
"Jun" => 6,
"Jul" => 7,
"Aug" => 8,
"Sep" => 9,
"Oct" => 10,
"Nov" => 11,
"Dec" => 12,
_ => unreachable!(),
};
let pair = pairs.next().unwrap();
assert!(matches!(pair.as_rule(), Rule::date_year));
let year = pair.as_str().trim().parse::<i32>().unwrap();
let pair = pairs.next().unwrap();
assert!(matches!(pair.as_rule(), Rule::time));
let mut parts = pair.as_str().split(':');
let hour = parts.next().unwrap().parse::<u32>().unwrap();
let minute = parts.next().unwrap().parse::<u32>().unwrap();
let second = parts.next().unwrap().parse::<u32>().unwrap();
let pair = pairs.next().unwrap();
assert!(matches!(pair.as_rule(), Rule::zone));
let zone = build_zone(pair);
zone.ymd(year, month, day).and_hms(hour, minute, second)
}

View file

@ -42,8 +42,8 @@ continue_req = { "+" ~ sp ~ (resp_text | base64) ~ crlf }
date_day_fixed = { (sp ~ digit) | digit{2} }
date_month = { "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" }
// TODO: date_time is a real date time
// date_time = { dquote ~ date_day_fixed ~ "-" ~ date_month ~ "-" ~ date_year ~ sp ~ time ~ sp ~ zone ~ dquote }
date_time = ${ string }
// date_time = ${ string }
date_time = { dquote_ ~ date_day_fixed ~ "-" ~ date_month ~ "-" ~ date_year ~ sp ~ time ~ sp ~ zone ~ dquote_ }
date_year = @{ digit{4} }
digit_nz = @{ '\x31'..'\x39' }
env_bcc = { "(" ~ address{1,} ~ ")" | nil }
@ -151,6 +151,7 @@ crlf = _{ cr ~ lf }
ctl = @{ '\x00'..'\x1f' | "\x7f" }
digit = @{ '\x30'..'\x39' }
dquote = @{ "\"" }
dquote_ = _{ "\"" }
lf = _{ "\x0a" }
sp = _{ " " }

View file

@ -1,22 +1,56 @@
use anyhow::Result;
use chrono::FixedOffset;
use pest::Parser;
use super::*;
use crate::response::*;
use pest::Parser;
fn parse<F, R>(r: Rule, f: F) -> impl Fn(&str) -> ParseResult<R>
where
F: Fn(Pair<Rule>) -> R,
{
move |s: &str| {
let mut pairs = Rfc3501::parse(r, s.as_ref())?;
let pair = pairs.next().unwrap();
Ok(f(pair))
}
}
#[test]
fn test_literal() {
assert_eq!(parse_literal("{7}\r\nhellosu"), Ok("hellosu".to_owned()));
let p = parse(Rule::literal, build_literal);
assert_eq!(p("{7}\r\nhellosu"), Ok("hellosu".to_owned()));
}
#[test]
fn test_zone() {
let p = parse(Rule::zone, build_zone);
assert_eq!(p("+0000"), Ok(FixedOffset::east(0)));
assert_eq!(p("-0200"), Ok(FixedOffset::west(7200)));
assert_eq!(p("+0330"), Ok(FixedOffset::east(12600)));
}
#[test]
fn test_date_time() -> Result<()> {
let p = parse(Rule::date_time, build_date_time);
assert_eq!(
p("\"17-Jul-1996 02:44:25 -0700\"")?,
DateTime::parse_from_rfc3339("1996-07-17T02:44:25-07:00")?
);
Ok(())
}
#[test]
#[rustfmt::skip]
fn test_capability() {
assert_eq!(parse_capability("IMAP4rev1"), Ok(Capability::Imap4rev1));
assert_eq!(parse_capability("LOGINDISABLED"), Ok(Capability::Atom("LOGINDISABLED".to_owned())));
assert_eq!(parse_capability("AUTH=PLAIN"), Ok(Capability::Auth("PLAIN".to_owned())));
assert_eq!(parse_capability("auth=plain"), Ok(Capability::Auth("PLAIN".to_owned())));
let p = parse(Rule::capability, build_capability);
assert_eq!(p("IMAP4rev1"), Ok(Capability::Imap4rev1));
assert_eq!(p("LOGINDISABLED"), Ok(Capability::Atom("LOGINDISABLED".to_owned())));
assert_eq!(p("AUTH=PLAIN"), Ok(Capability::Auth("PLAIN".to_owned())));
assert_eq!(p("auth=plain"), Ok(Capability::Auth("PLAIN".to_owned())));
assert!(parse_capability("(OSU)").is_err());
assert!(parse_capability("\x01HELLO").is_err());
assert!(p("(OSU)").is_err());
assert!(p("\x01HELLO").is_err());
}
#[test]
@ -108,7 +142,9 @@ fn test_section_8() {
12,
vec![
AttributeValue::Flags(vec![MailboxFlag::Seen]),
AttributeValue::InternalDate("17-Jul-1996 02:44:25 -0700".to_owned()),
AttributeValue::InternalDate(
DateTime::parse_from_rfc3339("1996-07-17T02:44:25-07:00").unwrap()
),
AttributeValue::Rfc822Size(4286),
AttributeValue::Envelope(Envelope {
date: Some("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)".to_owned()),

View file

@ -2,6 +2,8 @@
use std::ops::RangeInclusive;
use chrono::{DateTime, FixedOffset};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Response {
Capabilities(Vec<Capability>),
@ -78,7 +80,7 @@ pub enum AttributeValue {
BodyStructure(BodyStructure),
Envelope(Envelope),
Flags(Vec<MailboxFlag>),
InternalDate(String),
InternalDate(DateTime<FixedOffset>),
ModSeq(u64), // RFC 4551, section 3.3.2
Rfc822(Option<String>),
Rfc822Header(Option<String>),

View file

@ -50,16 +50,22 @@ pub async fn run_ui(
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Length(1), Constraint::Max(5000)])
.constraints([
Constraint::Length(1),
Constraint::Max(5000),
Constraint::Length(1),
])
.split(f.size());
// this is the title bar
let titles = vec!["hellosu"].into_iter().map(Spans::from).collect();
let titles = vec!["OSU mail"].into_iter().map(Spans::from).collect();
let tabs = Tabs::new(titles);
f.render_widget(tabs, chunks[0]);
mail_tab.render(f, chunks[1]);
// render_mail_tab(f, chunks[1], &folders, &messages);
let status = Paragraph::new("hellosu");
f.render_widget(status, chunks[2]);
})?;
let event = if event::poll(FRAME_DURATION)? {
@ -69,7 +75,9 @@ pub async fn run_ui(
if let Event::Key(KeyEvent { code, .. }) = event {
let selected = mail_tab.message_list.selected();
let len = mail_tab.messages.len();
let seln = selected.map(|x| if x < len - 1 { x + 1 } else { x }).unwrap_or(0);
let seln = selected
.map(|x| if x < len - 1 { x + 1 } else { x })
.unwrap_or(0);
let selp = selected.map(|x| if x > 0 { x - 1 } else { 0 }).unwrap_or(0);
match code {
KeyCode::Char('q') => break,