crabmail

Static HTML email archive viewer in Rust
git clone git://git.alexwennerberg.com/crabmail
Log | Files | Refs | README | LICENSE

commit 0d96fb29b138be22dc87b16a9e2d2123a72bdc5b
parent 7055def95dddae93b63bae88c30b4c31c220659a
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Fri, 31 Dec 2021 22:50:16 -0800

Continue restructuring

Diffstat:
Mcrabmail.conf | 7+++----
Msrc/config.rs | 38+++++++++++---------------------------
Msrc/main.rs | 67+++++++++++++++++++++++++++++++------------------------------------
3 files changed, 45 insertions(+), 67 deletions(-)

diff --git a/crabmail.conf b/crabmail.conf @@ -1,4 +1,3 @@ -list_email=crabmail@flounder.online -list_name=Crabmail Mailing List -url=https://lists.flounder.online/crabmail -homepage=https://crabmail.flounder.online/ +# Use %s to represent list name +email_fmt=lists+%s@flounder.online +base_url=https://lists.flounder.online diff --git a/src/config.rs b/src/config.rs @@ -7,18 +7,14 @@ use std::path::{Path, PathBuf}; // key=value\n #[derive(Debug)] pub struct Config { - pub list_name: String, - pub list_email: String, - pub url: String, - pub homepage: String, - // mimes that will be preserved as raw attachment files - // wildcards allowed as * - // WIP - pub ok_attachments: Vec<String>, + pub email_fmt: String, + pub base_url: String, pub out_dir: PathBuf, pub relative_times: bool, } +// TODO list-specific config + pub static INSTANCE: OnceCell<Config> = OnceCell::new(); impl Config { @@ -28,11 +24,8 @@ impl Config { pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, std::io::Error> { let file = File::open(path)?; - let mut list_name = "Crabmail Mailing List".to_string(); - let mut list_email = "setme@foo.local".to_string(); - let mut url = "flounder.online".to_string(); - let mut ok_attachments = vec!["text/python".to_string()]; - let mut homepage = String::new(); + let mut email_fmt = "lists+%s@example.com".to_string(); + let mut base_url = "https://example.com".to_string(); for l in io::BufReader::new(file).lines() { let line = l?; @@ -43,27 +36,18 @@ impl Config { let key = &line[..i]; let value = &line[i + 1..]; match key { - "list_name" => list_name = value.to_string(), - "list_email" => list_email = value.to_string(), - "url" => url = value.to_string(), - "homepage" => homepage = value.to_string(), - "ok_attachments" => { - ok_attachments = value.split(",").map(|s| s.to_owned()).collect() - } + "email_fmt" => email_fmt = value.to_string(), + "base_url" => base_url = value.to_string(), _ => {} } } else { - // Replace with whatever you want to do on malformed config lines - panic!("Invalid config") + // panic!("Invalid config") } } Ok(Config { - list_name, - list_email, - url, - homepage, + email_fmt, + base_url, out_dir: PathBuf::from(""), - ok_attachments, relative_times: false, }) } diff --git a/src/main.rs b/src/main.rs @@ -98,7 +98,9 @@ fn layout(page_title: impl Render, content: impl Render) -> impl Render { struct ThreadList<'a> { threads: Vec<MailThread<'a>>, - list_name: String, + name: String, + email: String, + url: String, // URL? } // Get short name from an address name like "alex wennerberg <alex@asdfasdfafd>" @@ -110,7 +112,14 @@ fn short_name(s: &SingleInfo) -> &str { } impl<'a> ThreadList<'a> { - // fn new() {} set + fn new(threads: Vec<MailThread<'a>>, list_name: &str) -> Self { + ThreadList { + threads, + name: list_name.to_owned(), + email: Config::global().email_fmt.replace("%s", &list_name), + url: format!("{}/{}", Config::global().base_url, &list_name), + } + } fn write_atom_feed(&self) -> Result<()> { // TODO dry // not sure how well this feed works... it just tracks thread updates. @@ -155,18 +164,15 @@ impl<'a> ThreadList<'a> { <id>{feed_id}</id> {entry_list} </feed>"#, - feed_title = &self.list_name, - feed_link = Config::global().url, + feed_title = &self.name, + feed_link = &self.url, last_updated = time::secs_to_date(last_updated).rfc3339(), - author_name = Config::global().list_email, - author_email = Config::global().list_email, - feed_id = Config::global().url, + author_name = &self.email, + author_email = &self.email, + feed_id = &self.url, entry_list = entries, ); - let path = Config::global() - .out_dir - .join(&self.list_name) - .join("atom.xml"); + let path = Config::global().out_dir.join(&self.name).join("atom.xml"); let mut file = File::create(&path)?; file.write(atom.as_bytes())?; Ok(()) @@ -178,21 +184,15 @@ impl<'a> ThreadList<'a> { }; let tmp = html! { h1(class="page-title") { - : &Config::global().list_name; + : format!("{} Mailing List", &self.name); : Raw(" "); a(href="atom.xml") { img(alt="Atom feed", src=utils::rss_svg); } } - a(href=format!("mailto:{}", &Config::global().list_email)) { - : &Config::global().list_email - } - span { // Hack - : " | " - } - a(href=&Config::global().homepage) { - : "about" + a(href=format!("mailto:{}", &self.email)) { + : &self.email } hr; @ for thread in &self.threads { @@ -214,14 +214,9 @@ impl<'a> ThreadList<'a> { } }; - let file = File::create( - &Config::global() - .out_dir - .join(&self.list_name) - .join("index.html"), - )?; + let file = File::create(&Config::global().out_dir.join(&self.name).join("index.html"))?; let mut br = BufWriter::new(file); - layout(Config::global().list_name.as_str(), tmp).write_to_io(&mut br)?; + layout(self.name.clone(), tmp).write_to_io(&mut br)?; Ok(()) } } @@ -232,7 +227,7 @@ impl<'a> MailThread<'a> { } fn url(&self) -> String { - format!("{}/threads/{}.html", Config::global().url, self.hash) + format!("{}/threads/{}.html", Config::global().base_url, self.hash) } fn write_atom_feed(&self) -> Result<()> { @@ -307,7 +302,7 @@ impl<'a> MailThread<'a> { } div { a(href="../") { - : &Config::global().list_name + : "Back"; } } div { @ for (n, message) in self.messages.iter().enumerate() { @@ -338,7 +333,7 @@ impl<'a> MailThread<'a> { } br; div(class="bold"){ - a (href=message.mailto(&root.subject)) { + a (href=message.mailto(&root.subject, &self.list_name)) { :"✉️ Reply" } } @@ -361,9 +356,12 @@ impl<'a> MailThread<'a> { impl Email { // mailto:... populated with everything you need - pub fn mailto(&self, thread_subject: &str) -> String { + pub fn mailto(&self, thread_subject: &str, list_name: &str) -> String { // TODO configurable - let mut url = format!("mailto:{}?", Config::global().list_email); + let mut url = format!( + "mailto:{}?", + Config::global().email_fmt.replace("%s", list_name) // not ideal + ); let from = self.from.to_string(); // make sure k is already urlencoded @@ -650,10 +648,7 @@ fn main() -> Result<()> { threads.sort_by_key(|a| a.last_reply); threads.reverse(); - let list = ThreadList { - threads, - list_name: list_name.to_string(), - }; + let list = ThreadList::new(threads, &list_name); list.write_to_file()?; list.write_atom_feed()?; // kinda clunky