bread gallery data and generator script
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
bread-gallery/src/main.rs

252 lines
7.0 KiB

// #[global_allocator]
// static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
#[macro_use]
extern crate smart_default;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use std::fs::{File, DirEntry, OpenOptions};
use std::io::{Read, Write};
use std::{env, fs, io};
use failure::Fallible;
use clap;
mod hash_dict;
mod logging;
mod bread;
use bread::Bread;
use crate::hash_dict::HashDict;
use chrono::Datelike;
use itertools::Itertools;
#[derive(Serialize, Deserialize, Debug, SmartDefault, Clone)]
#[serde(default)]
pub struct AppConfig {
#[default = "info"]
logging: String,
#[default = "https://www.ondrovo.com/bread"]
web_url : String,
}
pub struct GalleryInfo {
web_path: PathBuf,
data_path: PathBuf,
tpl_path: PathBuf,
thumbs_path: PathBuf,
base_url: String,
templates: HashMap<String, String>,
image_hashes: hash_dict::HashDict,
latest_year: i32,
oldest_year: i32,
}
impl GalleryInfo {
/// Read a named template from file, reusing from cache if possible
fn template(&mut self, name: &str) -> Fallible<String> {
if let Some(text) = self.templates.get(name) {
return Ok(text.clone());
}
let mut tpl = String::new();
File::open(self.tpl_path.join(name))?.read_to_string(&mut tpl)?;
self.templates.insert(name.to_string(), tpl.clone());
Ok(tpl)
}
}
const CONFIG_FILE: &str = "breadgen.json";
fn main() -> Fallible<()> {
let version = clap::crate_version!();
let clap =
clap::App::new("Flowbox RT")
.version(version)
.arg(
clap::Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Sets a custom config file (default: breadgen.json)")
.takes_value(true),
);
let clap = logging::add_clap_args(clap);
let argv = clap.get_matches();
/* Load config */
let appcfg = {
let confile = argv.value_of("config")
.unwrap_or(CONFIG_FILE);
println!("Bread gallery builder {}\nrun with -h for help", version);
println!("config file: {}", confile);
let buf = read_file(confile)
.unwrap_or("{}".to_string());
let config: AppConfig = serde_json::from_str(&buf)?;
let _ = logging::init(&config.logging, &argv, None);
config
};
let mut ginfo = {
let cwd = env::current_dir()?;
let web_path = cwd.join("web");
let data_path = web_path.join("data");
let tpl_path = web_path.join("templates");
let thumbs_path = web_path.join("thumbs");
GalleryInfo {
web_path,
data_path,
tpl_path,
thumbs_path,
base_url: appcfg.web_url.clone(),
templates: HashMap::new(),
image_hashes: HashDict::load(cwd.join(".hashes.txt"))?,
latest_year: 0,
oldest_year: 0,
}
};
let mut bread_dirs: Vec<DirEntry> = fs::read_dir(&ginfo.data_path)?
.filter_map(Result::ok)
.collect();
bread_dirs.sort_by(|x, y| {
x.file_name().cmp(&y.file_name())
});
let mut breads: Vec<Bread> = bread_dirs
.iter()
.filter_map(|p| Bread::parse(&ginfo.web_path, &p).ok())
.collect();
ginfo.latest_year = breads.last().unwrap().date.year();
ginfo.oldest_year = breads.first().unwrap().date.year();
for i in 0..breads.len() {
let preceding = if i <= 0 { None } else {
match breads.get(i - 1) {
Some(b) => Some(b.to_link()),
None => None
}
};
let following = match breads.get(i + 1) {
Some(b) => Some(b.to_link()),
None => None
};
let cur = breads.get_mut(i).unwrap();
cur.compile(&mut ginfo, preceding, following)?;
}
let mut channel: rss::Channel = rss::ChannelBuilder::default()
.title("Piggo's Bread Gallery")
.link("https://www.ondrovo.com/bread")
.description("Sourdough feed")
.build()
.unwrap();
ginfo.image_hashes.save();
// rss
{
let start = (breads.len() as i32 - 10).max(0) as usize;
let mut channel_items = vec![];
for b in &mut breads[start..] {
channel_items.push(b.rendered.rss_item.take().unwrap());
}
info!("Generating feed...");
let f = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(ginfo.web_path.join("feed.xml"))
.unwrap();
channel.set_items(channel_items);
channel.pretty_write_to(f, b' ', 2).unwrap();
}
// main page
{
// make thumbs go from the newest to the oldest
//breads.reverse(); // actually don't do that
let oldest = ginfo.oldest_year;
let latest = ginfo.latest_year;
let base_url = ginfo.base_url.clone();
let year_pager = |year| {
let mut buf = String::new();
for y in oldest..=latest {
if year == y {
buf.push_str(&format!(" <li class=\"active\"><a href=\"#\">{y}</a>\n", y=y));
} else {
if y == latest {
buf.push_str(&format!(" <li><a href=\"{u}/index.html\">{y}</a>\n", u=base_url, y=y));
} else {
buf.push_str(&format!(" <li><a href=\"{u}/{y}.html\">{y}</a>\n", u=base_url, y=y));
}
}
}
buf
};
for (year, year_breads) in &breads.iter().group_by(|b| b.date.year()) {
let mut thumbs_buf = String::new();
for b in year_breads {
thumbs_buf.push_str(&b.rendered.thumb);
}
info!("Building the gallery page");
let head = ginfo.template("_head.html")?
.replace("{title}", "Piggo's breads");
let main = ginfo.template("index.html")?
.replace("{breads}", thumbs_buf.trim())
.replace("{year_pager}", year_pager(year).trim())
.replace("{head}", head.trim());
let fname = if year == ginfo.latest_year {
"index.html".to_string()
} else {
format!("{}.html", year)
};
let mut f = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(ginfo.web_path.join(fname))
.unwrap();
f.write(main.as_bytes()).unwrap();
}
}
Ok(())
}
pub fn read_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
let path = path.as_ref();
let mut file = File::open(path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
Ok(buf)
}