commit 0e2595d26a0dc686b6eeea1c3a718393e163f53b
parent d358780018a0e9956b269e1885d9b2b56687407a
Author: alex wennerberg <alex@alexwennerberg.com>
Date: Fri, 31 Dec 2021 15:17:59 -0800
WIP
Diffstat:
M | Cargo.lock | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | Cargo.toml | | | 6 | ++++++ |
M | src/arg.rs | | | 12 | ++++++------ |
M | src/main.rs | | | 282 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
4 files changed, 220 insertions(+), 136 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -46,7 +46,9 @@ dependencies = [
"anyhow",
"horrorshow",
"linkify",
+ "maildir",
"mailparse",
+ "nanotemplate",
"once_cell",
"sha3",
"urlencoding",
@@ -92,6 +94,16 @@ dependencies = [
]
[[package]]
+name = "gethostname"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "horrorshow"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -104,6 +116,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
name = "linkify"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -113,6 +131,16 @@ dependencies = [
]
[[package]]
+name = "maildir"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6adcf4693a3c725b9c69ccf24f8ed9c6d3e7168c1a45632570d65529adc13b5e"
+dependencies = [
+ "gethostname",
+ "mailparse",
+]
+
+[[package]]
name = "mailparse"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,6 +158,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
+name = "nanotemplate"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcde141f0a9acabd38860369eeb0d69f1756d19c5948672c211e82e0519edd61"
+
+[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -168,3 +202,25 @@ name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
@@ -7,11 +7,17 @@ edition = "2018"
[features]
default = []
+
+[lib]
+proc-macro = true
+
[dependencies]
anyhow = "1.0"
horrorshow = "0.8"
linkify = "0.8"
mailparse = "0.13"
+maildir = "0.5.0"
once_cell = "1.9"
sha3 = "0.10"
urlencoding = "2.1"
+nanotemplate = "0.2"
diff --git a/src/arg.rs b/src/arg.rs
@@ -18,8 +18,8 @@ use std::process::exit;
fn usage() -> ! {
let name = env::args().next().unwrap();
eprintln!(
- "usage: {} [mbox-file]
-FLAGS:
+ "usage: {} [maildir]
+FLAGS
-r use relative timestamps
ARGS:
@@ -31,7 +31,7 @@ ARGS:
}
pub struct Args {
- pub mbox: PathBuf,
+ pub maildir: String,
pub config: PathBuf,
pub out_dir: PathBuf,
pub flags: String,
@@ -41,7 +41,7 @@ impl Args {
pub fn from_env() -> Self {
// Modify as needed
let mut flags = String::new();
- let mut mbox: Option<String> = None;
+ let mut maildir: Option<String> = None;
let mut out_dir = "site".into();
let mut config = "crabmail.conf".into();
@@ -55,7 +55,7 @@ impl Args {
while let Some(arg) = args.next() {
let mut chars = arg.chars();
if chars.next() != Some('-') {
- mbox = Some(arg);
+ maildir = Some(arg);
continue;
}
chars.for_each(|m| match m {
@@ -69,7 +69,7 @@ impl Args {
}
Self {
config,
- mbox: match mbox {
+ maildir: match maildir {
Some(m) => m.into(),
None => usage(),
},
diff --git a/src/main.rs b/src/main.rs
@@ -3,6 +3,8 @@ use horrorshow::helper::doctype;
use horrorshow::owned_html;
use horrorshow::prelude::*;
use horrorshow::Template;
+use maildir::Maildir;
+use nanotemplate::template; // TODO write htmlescaper
use std::io::BufWriter;
use std::path::Path;
use std::str;
@@ -25,6 +27,7 @@ use utils::xml_safe;
mod arg;
mod config;
mod mbox;
+mod threading;
mod time;
mod utils;
@@ -64,6 +67,7 @@ struct MailThread<'a> {
}
impl<'a> MailThread<'a> {}
+
fn layout(page_title: impl Render, content: impl Render) -> impl Render {
// owned_html _moves_ the arguments into the template. Useful for returning
// owned (movable) templates.
@@ -471,6 +475,19 @@ fn local_parse_email(data: &[u8]) -> Result<Email> {
});
}
+// if [arg] has cur,new,tmp -> that is the index
+// else, do each subfolder
+
+// Used when we have more than 1 maildir folder
+struct Index {}
+
+impl Index {
+ fn write_file() -> Result<()> {
+ template("Hello, my name is {name}!", &[("name", "nanotemplate")]);
+ Ok(())
+ }
+}
+
fn main() -> Result<()> {
let args = arg::Args::from_env();
@@ -480,137 +497,142 @@ fn main() -> Result<()> {
INSTANCE.set(config).unwrap();
let out_dir = &Config::global().out_dir;
- let mbox = mbox::from_file(&args.mbox)?;
-
- let mut thread_index: HashMap<String, Vec<String>> = HashMap::new();
-
- // index email ID -> email
- let mut email_index: HashMap<String, Email> = HashMap::new();
- for entry in mbox {
- let buffer = entry.unwrap();
- let email = match local_parse_email(&buffer) {
- Ok(e) => e,
- Err(e) => {
- println!("{:?}", e);
- continue;
- }
- };
- // TODO fix borrow checker
- if let Some(reply) = email.in_reply_to.clone() {
- match thread_index.get(&reply) {
- Some(_) => {
- let d = thread_index.get_mut(&reply).unwrap();
- d.push(email.id.clone());
- }
- None => {
- thread_index.insert(reply, vec![email.id.clone()]);
- }
- }
- }
- email_index.insert(email.id.clone(), email);
- }
-
- // Add index by subject lines
- // atrocious
- let mut todo = vec![]; // im bad at borrow checker
- for (_, em) in &email_index {
- if em.in_reply_to.is_none()
- && (em.subject.starts_with("Re: ") || em.subject.starts_with("RE: "))
- {
- // TODO O(n^2)
- for (_, em2) in &email_index {
- if em2.subject == em.subject[4..] {
- match thread_index.get(&em2.id) {
- Some(_) => {
- let d = thread_index.get_mut(&em2.id).unwrap();
- d.push(em.id.clone());
- }
- None => {
- thread_index.insert(em2.id.clone(), vec![em.id.clone()]);
- }
- }
- todo.push((em.id.clone(), em2.id.clone()));
- break;
- }
- }
- }
- }
- for (id, reply) in todo {
- let em = email_index.get_mut(&id).unwrap();
- em.in_reply_to = Some(reply)
- }
-
- let mut thread_roots: Vec<Email> = email_index
- .iter()
- .filter_map(|(_, v)| {
- if v.in_reply_to.is_none() {
- // or can't find root based on Re: subject
- return Some(v.clone());
- }
- return None;
- })
- .collect();
- std::fs::create_dir(&out_dir).ok();
- let thread_dir = Config::global().out_dir.join("threads");
- std::fs::create_dir(&thread_dir).ok();
- let mut threads = vec![];
- let mut curr_threads = get_current_threads(&out_dir);
-
- for root in &mut thread_roots {
- let mut thread_ids = vec![];
- let mut current: Vec<String> = vec![root.id.clone()];
- while current.len() > 0 {
- let top = current.pop().unwrap().clone();
- thread_ids.push(top.clone());
- if let Some(ids) = thread_index.get(&top.clone()) {
- for item in ids {
- current.push(item.to_string());
- }
- }
- }
-
- let mut messages: Vec<&Email> = thread_ids
- .iter()
- .map(|id| email_index.get(id).unwrap())
- .collect();
-
- messages.sort_by_key(|a| a.date);
-
- let mut thread = MailThread {
- messages: messages,
- hash: root.hash(),
- last_reply: 0, // TODO
- };
-
- thread.last_reply = thread.last_reply();
-
- thread.write_to_file()?;
- thread.write_atom_feed()?;
- curr_threads.remove(&thread.hash);
- threads.push(thread);
+ let maildir = Maildir::from(args.maildir.as_str());
+ let mut threader = threading::Arena::default();
+ // Loads whole file into memory for threading
+ for item in maildir.list_cur().chain(maildir.list_new()) {
+ threader.add_message(item?);
}
-
- for leftover in curr_threads {
- let file_to_remove = out_dir.join("threads").join(format!("{}.html", leftover));
- std::fs::remove_file(&file_to_remove).ok();
- let file_to_remove = out_dir.join("threads").join(format!("{}.xml", leftover));
- std::fs::remove_file(&file_to_remove).ok();
- }
-
- // Remove any threads left over
-
- threads.sort_by_key(|a| a.last_reply);
- threads.reverse();
- let list = ThreadList { threads };
- list.write_to_file()?;
- list.write_atom_feed()?;
- // kinda clunky
- let css = include_bytes!("style.css");
- let mut css_root = File::create(out_dir.join("style.css"))?;
- css_root.write(css)?;
- let mut css_sub = File::create(out_dir.join("threads").join("style.css"))?;
- css_sub.write(css)?;
- Ok(())
+ threader.finalize();
+ return Ok(());
+
+ // // index email ID -> email
+ // let mut email_index: HashMap<String, Email> = HashMap::new();
+ // for entry in mbox {
+ // let buffer = entry.unwrap();
+ // let email = match local_parse_email(&buffer) {
+ // Ok(e) => e,
+ // Err(e) => {
+ // println!("{:?}", e);
+ // continue;
+ // }
+ // };
+ // // TODO fix borrow checker
+ // if let Some(reply) = email.in_reply_to.clone() {
+ // match thread_index.get(&reply) {
+ // Some(_) => {
+ // let d = thread_index.get_mut(&reply).unwrap();
+ // d.push(email.id.clone());
+ // }
+ // None => {
+ // thread_index.insert(reply, vec![email.id.clone()]);
+ // }
+ // }
+ // }
+ // email_index.insert(email.id.clone(), email);
+ // }
+
+ // // Add index by subject lines
+ // // atrocious
+ // let mut todo = vec![]; // im bad at borrow checker
+ // for (_, em) in &email_index {
+ // if em.in_reply_to.is_none()
+ // && (em.subject.starts_with("Re: ") || em.subject.starts_with("RE: "))
+ // {
+ // // TODO O(n^2)
+ // for (_, em2) in &email_index {
+ // if em2.subject == em.subject[4..] {
+ // match thread_index.get(&em2.id) {
+ // Some(_) => {
+ // let d = thread_index.get_mut(&em2.id).unwrap();
+ // d.push(em.id.clone());
+ // }
+ // None => {
+ // thread_index.insert(em2.id.clone(), vec![em.id.clone()]);
+ // }
+ // }
+ // todo.push((em.id.clone(), em2.id.clone()));
+ // break;
+ // }
+ // }
+ // }
+ // }
+ // for (id, reply) in todo {
+ // let em = email_index.get_mut(&id).unwrap();
+ // em.in_reply_to = Some(reply)
+ // }
+
+ // let mut thread_roots: Vec<Email> = email_index
+ // .iter()
+ // .filter_map(|(_, v)| {
+ // if v.in_reply_to.is_none() {
+ // // or can't find root based on Re: subject
+ // return Some(v.clone());
+ // }
+ // return None;
+ // })
+ // .collect();
+ // std::fs::create_dir(&out_dir).ok();
+ // let thread_dir = Config::global().out_dir.join("threads");
+ // std::fs::create_dir(&thread_dir).ok();
+ // let mut threads = vec![];
+ // let mut curr_threads = get_current_threads(&out_dir);
+
+ // for root in &mut thread_roots {
+ // let mut thread_ids = vec![];
+ // let mut current: Vec<String> = vec![root.id.clone()];
+ // while current.len() > 0 {
+ // let top = current.pop().unwrap().clone();
+ // thread_ids.push(top.clone());
+ // if let Some(ids) = thread_index.get(&top.clone()) {
+ // for item in ids {
+ // current.push(item.to_string());
+ // }
+ // }
+ // }
+
+ // let mut messages: Vec<&Email> = thread_ids
+ // .iter()
+ // .map(|id| email_index.get(id).unwrap())
+ // .collect();
+
+ // messages.sort_by_key(|a| a.date);
+
+ // let mut thread = MailThread {
+ // messages: messages,
+ // hash: root.hash(),
+ // last_reply: 0, // TODO
+ // };
+
+ // thread.last_reply = thread.last_reply();
+
+ // thread.write_to_file()?;
+ // thread.write_atom_feed()?;
+ // curr_threads.remove(&thread.hash);
+ // threads.push(thread);
+ // }
+
+ // for leftover in curr_threads {
+ // let file_to_remove = out_dir.join("threads").join(format!("{}.html", leftover));
+ // std::fs::remove_file(&file_to_remove).ok();
+ // let file_to_remove = out_dir.join("threads").join(format!("{}.xml", leftover));
+ // std::fs::remove_file(&file_to_remove).ok();
+ // }
+
+ // // Remove any threads left over
+
+ // threads.sort_by_key(|a| a.last_reply);
+ // threads.reverse();
+ // let list = ThreadList { threads };
+ // list.write_to_file()?;
+ // list.write_atom_feed()?;
+ // // kinda clunky
+ // let css = include_bytes!("style.css");
+ // let mut css_root = File::create(out_dir.join("style.css"))?;
+ // css_root.write(css)?;
+ // let mut css_sub = File::create(out_dir.join("threads").join("style.css"))?;
+ // css_sub.write(css)?;
+ // Ok(())
}
// Use the sha3 hash of the ID. It is what it is.