aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2023-02-17 09:42:06 +0100
committerRobin Jarry <robin@jarry.cc>2023-10-31 19:00:51 +0100
commit13cce987905bbe134dea4de35872302cccca271b (patch)
tree701b220e848a55458fdac7c63b2f1d373e63069c
parent202e8c9d06bab32217549f23125d6b274107afda (diff)
downloadaerc-13cce987905bbe134dea4de35872302cccca271b.tar.gz
aerc-13cce987905bbe134dea4de35872302cccca271b.zip
wip
Signed-off-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--Cargo.lock52
-rw-r--r--Cargo.toml1
-rw-r--r--derive-macro/.gitignore1
-rw-r--r--derive-macro/Cargo.lock78
-rw-r--r--derive-macro/Cargo.toml13
-rw-r--r--derive-macro/src/attrs.rs154
-rw-r--r--derive-macro/src/derives.rs54
-rw-r--r--derive-macro/src/doc_comments.rs124
-rw-r--r--derive-macro/src/dummies.rs15
-rw-r--r--derive-macro/src/item.rs26
-rw-r--r--derive-macro/src/lib.rs22
-rw-r--r--derive-macro/src/spanned.rs89
-rw-r--r--src/app/mod.rs10
-rw-r--r--src/config/mod.rs12
-rw-r--r--src/config/ui.rs14
-rw-r--r--src/main.rs37
-rw-r--r--src/worker/mod.rs6
17 files changed, 670 insertions, 38 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7d5fbfe6..dbc58dce 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 996816d3..dc047f93 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,