use std::env; use std::fs; use std::fs::DirEntry; use std::fs::File; use std::io::prelude::*; use std::path::{Path, PathBuf}; use chrono; use chrono::NaiveDate; use markdown; use std::fs::OpenOptions; use rss::{Channel, ChannelBuilder, Item, ItemBuilder, Guid}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use image_utils; use chrono::offset::TimeZone; use chrono::Date; use chrono::Utc; #[derive(Debug)] struct Bread { path: PathBuf, rel_path: PathBuf, date: chrono::NaiveDate, note: String, images: Vec, } impl Bread { fn thumb_photo(&self) -> (&str, &str) { let mut first_img : &PathBuf = self.images.get(0).unwrap(); for im in &self.images { if im.file_name().unwrap().to_str().unwrap().contains("cover") { first_img = im; break; } } let img_path = first_img.to_str().unwrap(); let img_alt = first_img.file_name().unwrap().to_str().unwrap(); (img_path, img_alt) } fn parse(base_dir : &PathBuf, bread_dir : &DirEntry) -> Result { let bpath = bread_dir.path(); let mut note = String::new(); let mut note_path = bpath.join("note.txt"); // try a md one as a fallback if !note_path.exists() { note_path = bpath.join("note.md"); } if note_path.exists() { let mut note_file = File::open(note_path)?; note_file.read_to_string(&mut note)?; note = markdown::to_html(¬e); } let mut bread_files: Vec = fs::read_dir(&bpath)?.map(|e| e.unwrap()).collect(); bread_files.sort_by(|x, y| x.file_name().cmp(&y.file_name())); let images = bread_files.iter().filter(|&f| { let fname = f.file_name(); let name = fname.to_str().unwrap(); return name.ends_with(".png") || name.ends_with(".jpg") || name.ends_with(".jpeg") || name.ends_with(".gif"); }).map(|x| x.path().strip_prefix(base_dir).unwrap().to_path_buf()).collect(); return Ok(Bread { date: NaiveDate::parse_from_str(bpath.file_name().unwrap().to_str().unwrap(), "%Y-%m-%d").unwrap(), rel_path: bpath.strip_prefix(base_dir).unwrap().to_path_buf(), path: bpath, note, images }); } } fn main() { let cwd = env::current_dir().unwrap(); let web_path = Path::new(&cwd).join("web"); let data_path = web_path.join("data"); let tpl_path = web_path.join("templates"); let thumbs_path = web_path.join("thumbs"); let mut bread_dirs: Vec = fs::read_dir(&data_path).unwrap().map(|e| e.unwrap()).collect(); bread_dirs.sort_by(|x, y| x.file_name().cmp(&y.file_name())); let mut breads : Vec = Vec::new(); for bread_dir in bread_dirs { if let Ok(b) = Bread::parse(&web_path, &bread_dir) { breads.push(b); } } let mut main_tpl = String::new(); let mut thumb_tpl = String::new(); let mut detail_tpl = String::new(); let mut head_tpl = String::new(); File::open(tpl_path.join("index.html")).unwrap().read_to_string(&mut main_tpl).unwrap(); File::open(tpl_path.join("_thumb.html")).unwrap().read_to_string(&mut thumb_tpl).unwrap(); File::open(tpl_path.join("_head.html")).unwrap().read_to_string(&mut head_tpl).unwrap(); File::open(tpl_path.join("detail.html")).unwrap().read_to_string(&mut detail_tpl).unwrap(); let mut thumbs = String::new(); let mut channel : Channel = ChannelBuilder::default() .title("Piggo's Bread Gallery") .link("https://www.ondrovo.com/bread") .description("Sourdough feed") .build() .unwrap(); let mut channel_items = Vec::::new(); for bread in &breads { let date = bread.date.format("%Y/%m/%d").to_string(); let date_slug = bread.date.format("%Y-%m-%d").to_string(); let detail_file = date_slug.clone() + ".html"; let (img_path, img_alt) = bread.thumb_photo(); let note = if bread.note.is_empty() { "There's no note about this bread." } else { &bread.note }; let thumb_fname = date_slug.clone() + "." + Path::new(&img_path).extension().unwrap().to_str().unwrap(); let thumb_path = thumbs_path.join(&thumb_fname); let thumb_relpath = thumb_path.strip_prefix(&web_path).unwrap(); let image_path_encoded = utf8_percent_encode(thumb_relpath.to_str().unwrap(), DEFAULT_ENCODE_SET).to_string(); let im = image::open(&web_path.join(img_path)).unwrap(); let im = im.thumbnail(500, 500); //let mut output = File::create(&thumb_path); im.save(&thumb_path).unwrap(); //image_utils::resize(&web_path.join(img_path), 500, 500, &thumb_path).unwrap(); // bread pic for the thumbnails page { let thumb = thumb_tpl .replace("{detail_url}", &detail_file) .replace("{img_src}", &image_path_encoded) .replace("{img_alt}", &img_alt) .replace("{title}", &date); thumbs.push_str(&thumb); } // add to rss { let image_url : String = channel.link().to_string() + "/" + &image_path_encoded; let link : String = channel.link().to_string() + "/" + &detail_file; let mut guid = Guid::default(); guid.set_value(link.clone()); guid.set_permalink(true); let date_formatted : Date = chrono::Utc.from_local_date(&bread.date).unwrap(); let dt = date_formatted.and_hms(12,0,0); channel_items.push(ItemBuilder::default() .title(date.clone()) .link(link.clone()) .description(note.to_string() + &format!("\"{}\"

Open the link for more...

", image_url, img_alt)) .guid(guid) .pub_date(dt.to_rfc2822()) .build().unwrap()); } // generate the detail page { let detail = detail_tpl .replace("{head}", &head_tpl.replace("{title}", &format!("Bread from {}", date)).trim()) .replace("{title}", &date) .replace("{note}", note.trim()); let mut pics = String::new(); for img in &bread.images { pics.push_str(&format!(" \n", src=&utf8_percent_encode(img.to_str().unwrap(), DEFAULT_ENCODE_SET).to_string())) } let detail = detail.replace("{images}", &pics.trim()); let mut f = OpenOptions::new().write(true).truncate(true).create(true).open(web_path.join(detail_file)).unwrap(); f.write(detail.as_bytes()).unwrap(); } } let main = main_tpl.replace("{breads}", &thumbs.trim()) .replace("{head}", &head_tpl.replace("{title}", "Piggo's breads").trim()); { let mut f = OpenOptions::new().write(true).truncate(true).create(true).open(web_path.join("index.html")).unwrap(); f.write(main.as_bytes()).unwrap(); } { let f = OpenOptions::new().write(true).truncate(true).create(true).open(web_path.join("feed.xml")).unwrap(); channel.set_items(channel_items); channel.pretty_write_to(f, b' ', 2).unwrap(); } }