gmi.rs (4838B) - raw
1 // WIP 2 // 3 use crate::models::*; 4 use crate::templates::PAGE_SIZE; 5 use crate::time::Date; 6 use crate::util::*; 7 use nanotemplate::template; 8 9 impl Lists { 10 pub fn to_gmi(&self) -> String { 11 let mut lists = String::new(); 12 for list in &self.lists { 13 lists.push_str(&format!("=> ./{0}/ {0}\n", &h(&list.config.name))); 14 } 15 // this looks stupid ok I know 16 template( 17 r#"# Mail Archives 18 {lists} 19 "#, 20 &[("lists", &lists)], 21 ) 22 .unwrap() 23 } 24 } 25 26 impl List { 27 pub fn to_gmi(&self) -> Vec<String> { 28 // TODO paginate 29 let page_count = self.thread_topics.len() / PAGE_SIZE + 1; 30 self.thread_topics 31 .chunks(PAGE_SIZE) 32 .enumerate() 33 .map(|(n, thread_topics)| { 34 let mut threads = format!( 35 "## {0}\n{1}\n=>mailto:{2} {2}\n", 36 self.config.name, self.config.description, self.config.email 37 ); 38 for thread in thread_topics { 39 threads.push_str( 40 // TODO reuse with html templates? 41 &template( 42 r#" 43 => threads/{path_id}.gmi {subject} 44 {preview} 45 {from} | {replies} replies | {date} 46 "#, 47 &[ 48 ( 49 "path_id", 50 &h(thread.message.pathescape_msg_id().to_str().unwrap()), 51 ), 52 ("subject", &h(&thread.message.subject)), 53 ("replies", &thread.reply_count.to_string()), 54 ("preview", &h(&thread.message.preview)), 55 ("date", &h(&Date::from(thread.last_reply).ymd())), 56 ( 57 "from", 58 &h(&thread. // awkawrd 59 message.from 60 .name 61 .clone() 62 .unwrap_or(thread.message.from.address.clone()) 63 .clone()), 64 ), 65 ], 66 ) 67 .unwrap(), 68 ); 69 } 70 if n + 1 <= page_count { 71 threads.push_str(&format!("\n=> index-{}.gmi next", n + 1)); 72 } 73 threads 74 }) 75 .collect() 76 } 77 } 78 79 impl Thread { 80 pub fn to_gmi(&self) -> String { 81 let mut out = format!( 82 r#"# {} 83 "#, 84 self.messages[0].subject.replace("\n", " ") 85 ); 86 for msg in &self.messages { 87 let mut optional_headers = String::new(); 88 if let Some(irt) = &msg.in_reply_to { 89 optional_headers.push_str(&format!("\nIn-Reply-To: {}", &h(&irt))); 90 } 91 // TODO no copy pasta 92 let cc_string = &h(&msg 93 .cc 94 .iter() 95 .map(|t| t.to_string()) 96 .collect::<Vec<String>>() 97 .join(", ")); 98 if msg.cc.len() > 0 { 99 optional_headers.push_str(&format!("\nCc: {}", cc_string)); 100 } 101 let body = match msg.flowed { 102 true => unformat_flowed(&msg.body), 103 false => msg.body.clone(), 104 }; 105 let msg = template( 106 r#" 107 ## {subject} 108 From: {from} 109 Date: {date} 110 Message-Id: {msg_id} 111 To: {to}{optional_headers} 112 => {mailto} Reply 113 => ../messages/{msg_path}.mbox Export 114 -------------------------------------- 115 {body} 116 "#, 117 &[ 118 ("subject", &h(&msg.subject)), 119 ("date", &h(&msg.date)), 120 ("msg_id", &h(&msg.id)), 121 ( 122 "to", 123 &h(&msg 124 .to 125 .iter() 126 .map(|t| t.to_string()) 127 .collect::<Vec<String>>() 128 .join(", ")), 129 ), 130 ("optional_headers", &optional_headers), 131 ("from", &h(&msg.from.address)), 132 ("mailto", &h(&msg.mailto)), 133 ("msg_path", &h(msg.pathescape_msg_id().to_str().unwrap())), 134 // TODO escape # in body? 135 // TODO only unformat flowed if flowed is true 136 ("body", &body), 137 ], 138 ) 139 .unwrap(); 140 out.push_str(&msg); 141 } 142 out 143 } 144 } 145 146 // escape header 147 fn h(s: &str) -> String { 148 s.replace("\n", " ") 149 }