diff options
author | Robin Jarry <robin@jarry.cc> | 2023-02-17 09:42:06 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-10-31 19:00:51 +0100 |
commit | 13cce987905bbe134dea4de35872302cccca271b (patch) | |
tree | 701b220e848a55458fdac7c63b2f1d373e63069c | |
parent | 202e8c9d06bab32217549f23125d6b274107afda (diff) | |
download | aerc-13cce987905bbe134dea4de35872302cccca271b.tar.gz aerc-13cce987905bbe134dea4de35872302cccca271b.zip |
wip
Signed-off-by: Robin Jarry <robin@jarry.cc>
-rw-r--r-- | Cargo.lock | 52 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | derive-macro/.gitignore | 1 | ||||
-rw-r--r-- | derive-macro/Cargo.lock | 78 | ||||
-rw-r--r-- | derive-macro/Cargo.toml | 13 | ||||
-rw-r--r-- | derive-macro/src/attrs.rs | 154 | ||||
-rw-r--r-- | derive-macro/src/derives.rs | 54 | ||||
-rw-r--r-- | derive-macro/src/doc_comments.rs | 124 | ||||
-rw-r--r-- | derive-macro/src/dummies.rs | 15 | ||||
-rw-r--r-- | derive-macro/src/item.rs | 26 | ||||
-rw-r--r-- | derive-macro/src/lib.rs | 22 | ||||
-rw-r--r-- | derive-macro/src/spanned.rs | 89 | ||||
-rw-r--r-- | src/app/mod.rs | 10 | ||||
-rw-r--r-- | src/config/mod.rs | 12 | ||||
-rw-r--r-- | src/config/ui.rs | 14 | ||||
-rw-r--r-- | src/main.rs | 37 | ||||
-rw-r--r-- | src/worker/mod.rs | 6 |
17 files changed, 670 insertions, 38 deletions
@@ -30,6 +30,7 @@ dependencies = [ "futures", "gtmpl", "log", + "rust-ini", "simple_logger", "time", "tokio", @@ -37,6 +38,17 @@ dependencies = [ ] [[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] name = "anyhow" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -191,6 +203,12 @@ dependencies = [ ] [[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -301,6 +319,17 @@ dependencies = [ ] [[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] name = "gimli" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -334,6 +363,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -502,6 +534,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown", +] + +[[package]] name = "os_str_bytes" version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -600,6 +642,16 @@ dependencies = [ ] [[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -22,6 +22,7 @@ derivative = "2.2.0" futures = "0.3.26" gtmpl = "0.7.1" log = "0.4.17" +rust-ini = { version = "0.18.0", features = ["brackets-in-section-names"] } simple_logger = { version = "4.0.0", default-features = false, features = ["time", "timestamps"] } time = "0.3.17" tokio = { version = "1.25.0", features = ["full"] } diff --git a/derive-macro/.gitignore b/derive-macro/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/derive-macro/.gitignore @@ -0,0 +1 @@ +/target diff --git a/derive-macro/Cargo.lock b/derive-macro/Cargo.lock new file mode 100644 index 00000000..0758d4aa --- /dev/null +++ b/derive-macro/Cargo.lock @@ -0,0 +1,78 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "derive-macro" +version = "0.1.0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/derive-macro/Cargo.toml b/derive-macro/Cargo.toml new file mode 100644 index 00000000..8dec225a --- /dev/null +++ b/derive-macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "derive-macro" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = "1.0.4" +proc-macro2 = "1.0.51" +quote = "1.0.23" +syn = "1.0.107" diff --git a/derive-macro/src/attrs.rs b/derive-macro/src/attrs.rs new file mode 100644 index 00000000..c95e6edf --- /dev/null +++ b/derive-macro/src/attrs.rs @@ -0,0 +1,154 @@ +use proc_macro2::TokenStream; +use proc_macro_error::{abort, ResultExt}; +use quote::{quote, ToTokens}; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Attribute, Expr, Ident, LitStr, Token, +}; + +use crate::spanned::Sp; + +#[derive(Clone)] +pub struct IniAttr { + pub kind: Sp<AttrKind>, + pub name: Ident, + pub magic: Option<MagicAttrName>, + pub value: Option<AttrValue>, +} + +impl IniAttr { + pub fn parse_all(all_attrs: &[Attribute]) -> Vec<Self> { + all_attrs + .iter() + .filter_map(|attr| { + let kind = if attr.path.is_ident("key") { + Some(Sp::new(AttrKind::Key, attr.path.span())) + } else { + None + }; + kind.map(|k| (k, attr)) + }) + .flat_map(|(k, attr)| { + attr.parse_args_with(Punctuated::<IniAttr, Token![,]>::parse_terminated) + .unwrap_or_abort() + .into_iter() + .map(move |mut a| { + a.kind = k; + a + }) + }) + .collect() + } + + pub fn value_or_abort(&self) -> &AttrValue { + self.value + .as_ref() + .unwrap_or_else(|| abort!(self.name, "attribute `{}` requires a value", self.name)) + } + + pub fn lit_str_or_abort(&self) -> &LitStr { + let value = self.value_or_abort(); + match value { + AttrValue::LitStr(tokens) => tokens, + AttrValue::Expr(_) | AttrValue::Call(_) => { + abort!( + self.name, + "attribute `{}` can only accept string literals", + self.name + ) + } + } + } +} + +impl Parse for IniAttr { + fn parse(input: ParseStream) -> syn::Result<Self> { + let name: Ident = input.parse()?; + let name_str = name.to_string(); + + let magic = match name_str.as_str() { + "name" => Some(MagicAttrName::Name), + "default" => Some(MagicAttrName::Default), + "parse_with" => Some(MagicAttrName::ParseWith), + _ => None, + }; + + let value = if input.peek(Token![=]) { + // `name = value` attributes. + let assign_token = input.parse::<Token![=]>()?; // skip '=' + if input.peek(LitStr) { + let lit: LitStr = input.parse()?; + Some(AttrValue::LitStr(lit)) + } else { + match input.parse::<Expr>() { + Ok(expr) => Some(AttrValue::Expr(expr)), + + Err(_) => abort! { + assign_token, + "expected `string literal` or `expression` after `=`" + }, + } + } + } else if input.peek(syn::token::Paren) { + // `name(...)` attributes. + let nested; + parenthesized!(nested in input); + + let method_args: Punctuated<_, Token![,]> = nested.parse_terminated(Expr::parse)?; + Some(AttrValue::Call(Vec::from_iter(method_args))) + } else { + None + }; + + Ok(Self { + kind: Sp::new(AttrKind::Ini, name.span()), + name, + magic, + value, + }) + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum MagicAttrName { + Name, + Default, + ParseWith, +} + +#[derive(Clone)] +#[allow(clippy::large_enum_variant)] +pub enum AttrValue { + LitStr(LitStr), + Expr(Expr), + Call(Vec<Expr>), +} + +impl ToTokens for AttrValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::LitStr(t) => t.to_tokens(tokens), + Self::Expr(t) => t.to_tokens(tokens), + Self::Call(t) => { + let t = quote!(#(#t),*); + t.to_tokens(tokens) + } + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum AttrKind { + Key, +} + +impl AttrKind { + pub fn as_str(&self) -> &'static str { + match self { + Self::Key => "key", + } + } +} diff --git a/derive-macro/src/derives.rs b/derive-macro/src/derives.rs new file mode 100644 index 00000000..fdb83a0d --- /dev/null +++ b/derive-macro/src/derives.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 Robin Jarry + +use proc_macro2::TokenStream; +use proc_macro_error::abort_call_site; +use quote::quote; +use syn::{self, Data, DataStruct, DeriveInput, Field, Fields, Generics, Ident}; + +use crate::dummies; +use crate::item::Item; + +pub fn derive_ini(input: &DeriveInput) -> TokenStream { + let ident = &input.ident; + + match input.data { + Data::Struct(DataStruct { + fields: Fields::Named(ref fields), + .. + }) => { + dummies::ini(ident); + + let item = Item::from_args_struct(input); + let fields = fields + .named + .iter() + .map(|field| { + let item = Item::from_args_field(field); + (field, item) + }) + .collect::<Vec<_>>(); + gen_for_struct(&item, ident, &input.generics, &fields) + } + _ => abort_call_site!("`#[derive(Ini)]` only supports non-tuple structs"), + } +} + +fn gen_for_struct( + item: &Item, + name: &Ident, + generics: &Generics, + fields: &[(&Field, Item)], +) -> TokenStream { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let into_app = into_app::gen_for_struct(item, name, generics); + let args = args::gen_for_struct(item, name, generics, fields); + + quote! { + impl #impl_generics clap::Parser for #name #ty_generics #where_clause {} + + #into_app + #args + } +} diff --git a/derive-macro/src/doc_comments.rs b/derive-macro/src/doc_comments.rs new file mode 100644 index 00000000..43b8a9cb --- /dev/null +++ b/derive-macro/src/doc_comments.rs @@ -0,0 +1,124 @@ +//! The preprocessing we apply to doc comments. +//! +//! #[derive(IniMap)] works in terms of "paragraphs". Paragraph is a sequence of +//! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines. + +use std::iter; + +pub fn extract_doc_comment(attrs: &[syn::Attribute]) -> Vec<String> { + use syn::Lit::*; + use syn::Meta::*; + use syn::MetaNameValue; + + // multiline comments (`/** ... */`) may have LFs (`\n`) in them, + // we need to split so we could handle the lines correctly + // + // we also need to remove leading and trailing blank lines + let mut lines: Vec<_> = attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .filter_map(|attr| { + if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { + Some(s.value()) + } else { + // non #[doc = "..."] attributes are not our concern + // we leave them for rustc to handle + None + } + }) + .skip_while(|s| is_blank(s)) + .flat_map(|s| { + let lines = s + .split('\n') + .map(|s| { + // remove one leading space no matter what + let s = s.strip_prefix(' ').unwrap_or(s); + s.to_owned() + }) + .collect::<Vec<_>>(); + lines + }) + .collect(); + + while let Some(true) = lines.last().map(|s| is_blank(s)) { + lines.pop(); + } + + lines +} + +pub fn format_doc_comment( + lines: &[String], + preprocess: bool, + force_long: bool, +) -> (Option<String>, Option<String>) { + if let Some(first_blank) = lines.iter().position(|s| is_blank(s)) { + let (short, long) = if preprocess { + let paragraphs = split_paragraphs(lines); + let short = paragraphs[0].clone(); + let long = paragraphs.join("\n\n"); + (remove_period(short), long) + } else { + let short = lines[..first_blank].join("\n"); + let long = lines.join("\n"); + (short, long) + }; + + (Some(short), Some(long)) + } else { + let (short, long) = if preprocess { + let short = merge_lines(lines); + let long = force_long.then(|| short.clone()); + let short = remove_period(short); + (short, long) + } else { + let short = lines.join("\n"); + let long = force_long.then(|| short.clone()); + (short, long) + }; + + (Some(short), long) + } +} + +fn split_paragraphs(lines: &[String]) -> Vec<String> { + let mut last_line = 0; + iter::from_fn(|| { + let slice = &lines[last_line..]; + let start = slice.iter().position(|s| !is_blank(s)).unwrap_or(0); + + let slice = &slice[start..]; + let len = slice + .iter() + .position(|s| is_blank(s)) + .unwrap_or(slice.len()); + + last_line += start + len; + + if len != 0 { + Some(merge_lines(&slice[..len])) + } else { + None + } + }) + .collect() +} + +fn remove_period(mut s: String) -> String { + if s.ends_with('.') && !s.ends_with("..") { + s.pop(); + } + s +} + +fn is_blank(s: &str) -> bool { + s.trim().is_empty() +} + +fn merge_lines(lines: impl IntoIterator<Item = impl AsRef<str>>) -> String { + lines + .into_iter() + .map(|s| s.as_ref().trim().to_owned()) + .collect::<Vec<_>>() + .join(" ") +} diff --git a/derive-macro/src/dummies.rs b/derive-macro/src/dummies.rs new file mode 100644 index 00000000..3abd39a4 --- /dev/null +++ b/derive-macro/src/dummies.rs @@ -0,0 +1,15 @@ +//! Dummy implementations that we emit along with an error. + +use proc_macro2::Ident; +use proc_macro_error::append_dummy; +use quote::quote; + +pub fn ini(name: &Ident) { + append_dummy(quote! { + impl #name { + pub fn parse<'a>(i: &'a ::ini::Ini) -> ::std::Result<Self, ::ini::Error> { + unimplemented!() + } + } + }); +} diff --git a/derive-macro/src/item.rs b/derive-macro/src/item.rs new file mode 100644 index 00000000..5d3f4cdb --- /dev/null +++ b/derive-macro/src/item.rs @@ -0,0 +1,26 @@ +use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Field, Ident, LitStr, Type, Variant}; + +#[derive(Clone)] +pub struct Item { + ident: Ident, + ty: Option<Type>, + doc_comment: Vec<Method>, + value_parser: Option<ValueParser>, +} + +impl Item { + pub fn from_key_field(field: &Field) -> Self { + let ident = field.ident.clone().unwrap(); + let span = field.span(); + let ty = Ty::from_syn_ty(&field.ty); + let kind = Sp::new(Kind::Arg(ty), span); + let mut res = Self::new(Name::Derived(name), ident, Some(field.ty.clone()), kind); + let parsed_attrs = ClapAttr::parse_all(&field.attrs); + res.push_attrs(&parsed_attrs); + if matches!(&*res.kind, Kind::Arg(_)) { + res.push_doc_comment(&field.attrs, "help", Some("long_help")); + } + + res + } +} diff --git a/derive-macro/src/lib.rs b/derive-macro/src/lib.rs new file mode 100644 index 00000000..513c2b20 --- /dev/null +++ b/derive-macro/src/lib.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 Robin Jarry + +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use syn::{parse_macro_input, DeriveInput}; + +mod attrs; +mod derives; +mod doc_comments; +mod dummies; +mod item; +mod spanned; + +#[proc_macro_derive(IniMap, attributes(key, default, parse_with))] +#[proc_macro_error] +pub fn ini(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + derives::derive_ini(&input).into() +} diff --git a/derive-macro/src/spanned.rs b/derive-macro/src/spanned.rs new file mode 100644 index 00000000..339a654e --- /dev/null +++ b/derive-macro/src/spanned.rs @@ -0,0 +1,89 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; +use syn::LitStr; + +use std::ops::{Deref, DerefMut}; + +/// An entity with a span attached. +#[derive(Debug, Copy, Clone)] +pub struct Sp<T> { + val: T, + span: Span, +} + +impl<T> Sp<T> { + pub fn new(val: T, span: Span) -> Self { + Sp { val, span } + } + + pub fn get(&self) -> &T { + &self.val + } + + pub fn span(&self) -> Span { + self.span + } +} + +impl<T> Deref for Sp<T> { + type Target = T; + + fn deref(&self) -> &T { + &self.val + } +} + +impl<T> DerefMut for Sp<T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.val + } +} + +impl From<Ident> for Sp<String> { + fn from(ident: Ident) -> Self { + Sp { + val: ident.to_string(), + span: ident.span(), + } + } +} + +impl From<LitStr> for Sp<String> { + fn from(lit: LitStr) -> Self { + Sp { + val: lit.value(), + span: lit.span(), + } + } +} + +impl<'a> From<Sp<&'a str>> for Sp<String> { + fn from(sp: Sp<&'a str>) -> Self { + Sp::new(sp.val.into(), sp.span) + } +} + +impl<U, T: PartialEq<U>> PartialEq<U> for Sp<T> { + fn eq(&self, other: &U) -> bool { + self.val == *other + } +} + +impl<T: AsRef<str>> AsRef<str> for Sp<T> { + fn as_ref(&self) -> &str { + self.val.as_ref() + } +} + +impl<T: ToTokens> ToTokens for Sp<T> { + fn to_tokens(&self, stream: &mut TokenStream) { + // this is the simplest way out of correct ones to change span on + // arbitrary token tree I could come up with + let tt = self.val.to_token_stream().into_iter().map(|mut tt| { + tt.set_span(self.span); + tt + }); + + stream.extend(tt); + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 27846065..1cb32499 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -4,16 +4,12 @@ use std::collections::HashMap; use std::sync::Arc; -use tokio::sync::mpsc::channel; -use tokio::sync::mpsc::Receiver; -use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use crate::config::AccountConfig; -use crate::config::Config; -use crate::worker::Worker; -use crate::worker::WorkerMessage; +use crate::config::{AccountConfig, Config}; +use crate::worker::{Worker, WorkerMessage}; pub struct App { config: Arc<Mutex<Config>>, diff --git a/src/config/mod.rs b/src/config/mod.rs index 5709180a..1ad8b7bc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -11,9 +11,9 @@ use std::collections::HashMap; use anyhow::Result; use derivative::Derivative; +use ini::Ini; -pub use crate::config::accounts::AccountConfig; -pub use crate::config::accounts::BackendType; +pub use crate::config::accounts::{AccountConfig, BackendType}; pub use crate::config::ui::UiConfig; #[derive(Derivative)] @@ -25,7 +25,13 @@ pub struct Config { impl Config { pub fn parse(accounts: Vec<String>) -> Result<Self> { - let c = Self::default(); + let ini = Ini::load_from_file("")?; + + let c = Config { + ui: UiConfig::parse(&ini)?, + accounts: HashMap::new(), + }; + Ok(c) } } diff --git a/src/config/ui.rs b/src/config/ui.rs index b7b004e2..bab96f48 100644 --- a/src/config/ui.rs +++ b/src/config/ui.rs @@ -3,14 +3,16 @@ use std::collections::HashMap; +use anyhow::{Error, Result}; use derivative::Derivative; use gtmpl::Template; +use ini::{Ini, Properties}; use crate::config::columns::ColumnDef; use crate::config::style::StyleSet; -#[derive(Derivative)] -#[derivative(Debug, Default)] +#[derive(Derivative, Default)] +#[derivative(Debug)] pub struct UiConfig { // message list pub index_columns: Vec<ColumnDef>, @@ -97,3 +99,11 @@ enum UiContextType {} #[derive(Debug)] struct UiContextKey {} + +impl UiConfig { + pub fn parse(ini: &Ini) -> Result<Self> { + let ui = ini.section(Some("ui")).unwrap_or(&Properties::new()); + + Ok(Self::default()) + } +} diff --git a/src/main.rs b/src/main.rs index 894b8853..d139daf6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,25 +13,18 @@ use std::sync::Arc; use anyhow::Result; use clap::Parser; use crossterm::cursor; -use crossterm::event::DisableMouseCapture; -use crossterm::event::EnableMouseCapture; -use crossterm::event::Event; -use crossterm::event::EventStream; -use crossterm::event::KeyCode; -use crossterm::event::KeyEventKind; -use crossterm::terminal::disable_raw_mode; -use crossterm::terminal::enable_raw_mode; -use crossterm::terminal::EnterAlternateScreen; -use crossterm::terminal::LeaveAlternateScreen; -use crossterm::terminal::SetTitle; +use crossterm::event::{ + DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode, KeyEventKind, +}; +use crossterm::terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle, +}; use crossterm::ExecutableCommand; use futures::future::FutureExt; use futures::StreamExt; -use tokio::sync::mpsc::channel; -use tokio::sync::mpsc::Receiver; +use tokio::sync::mpsc::{channel, Receiver}; use tokio::sync::Mutex; -use tui::backend::Backend; -use tui::backend::CrosstermBackend; +use tui::backend::{Backend, CrosstermBackend}; use tui::Terminal; use crate::app::App; @@ -39,10 +32,10 @@ use crate::config::Config; /// A pretty good email client #[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] +#[command(version, about, long_about = None)] struct Args { /// Enabled account (by default, all accounts are enabled). - #[arg(short, long = "account", env = "AERC_ACCOUNTS")] + #[arg(short, long = "account")] accounts: Vec<String>, } @@ -58,17 +51,17 @@ async fn main() -> Result<()> { let args = Args::parse(); let config = Arc::new(Mutex::new(Config::parse(args.accounts)?)); let (updates_tx, updates_rx) = channel(1); - let app = Arc::new(Mutex::new(App::new(config.clone(), updates_tx))); + let mut app = App::new(config.clone(), updates_tx); // Start email handling workers in the background. for account in config.lock().await.accounts.values() { - app.lock().await.start_worker(account.clone()); + app.start_worker(account.clone()); } // The UI must run in the "main" thread. - ui_loop(config, app.clone(), updates_rx).await?; + ui_loop(config, &app, updates_rx).await?; - app.lock().await.stop_workers().await; + app.stop_workers().await; restore_term()?; @@ -77,7 +70,7 @@ async fn main() -> Result<()> { async fn ui_loop( config: Arc<Mutex<Config>>, - app: Arc<Mutex<App>>, + app: &App, mut app_updates: Receiver<()>, ) -> Result<()> { // Terminal initialization. diff --git a/src/worker/mod.rs b/src/worker/mod.rs index 760ed82f..23dd76d3 100644 --- a/src/worker/mod.rs +++ b/src/worker/mod.rs @@ -6,11 +6,9 @@ mod maildir; mod mbox; mod notmuch; -use tokio::sync::mpsc::Receiver; -use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc::{Receiver, Sender}; -use crate::config::AccountConfig; -use crate::config::BackendType; +use crate::config::{AccountConfig, BackendType}; pub struct Worker { config: AccountConfig, |