pharmacy/src/html.rs

323 lines
8.9 KiB
Rust

use crate::database::read_all_products;
use crate::structures::{Product, ProductClass};
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use tera::{Context, Tera};
use toml::Value;
// Struct for data needed for rendering a product
struct ProductRenderObject {
url: String,
tera: Tera,
product: Product,
}
// Struct for data needed for rendering
struct RenderObject<'a> {
url: String,
tera: Tera,
subtitle: &'a str,
active_page: &'a str,
title: &'a str,
description: &'a str,
products: Option<Vec<Product>>,
}
// Navigation bar info
#[derive(Serialize)]
struct NavLink {
href: String,
text: String,
active: AtomicBool,
}
// TOML config file
#[derive(Serialize, Deserialize)]
struct Config {
title: String,
description: String,
index: Page,
about: Page,
contact: Page,
products: Products,
}
// Products field in TOML
#[derive(Serialize, Deserialize)]
struct Products {
label: String,
href: String,
title: String,
estrogens: Page,
anti_androgens: Page,
progestogens: Page,
}
// TOML page info
#[derive(Serialize, Deserialize)]
struct Page {
label: String,
href: String,
title: String,
}
// Base info for rendering a page
struct PageBase {
config: Config,
tera: Tera,
nav_elements: Vec<NavLink>,
product_classes: Vec<NavLink>,
}
// Build the base info
fn build_base() -> PageBase {
// Read in TOML
let mut config_file = std::fs::File::open("config.toml").unwrap();
let mut contents = String::new();
config_file.read_to_string(&mut contents).unwrap();
let config: Config = toml::from_str(&contents).unwrap();
println!("{}", config.index.href);
// Use globbing
let tera = match Tera::new("templates/**/*.html") {
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {}", e);
::std::process::exit(1);
}
};
// Setup basic elements
let nav_home = NavLink {
href: config.index.href.clone(),
text: config.index.label.clone(),
active: AtomicBool::new(true),
};
let nav_about = NavLink {
href: config.about.href.clone(),
text: config.about.label.clone(),
active: AtomicBool::new(false),
};
let nav_products = NavLink {
href: config.products.href.clone(),
text: config.products.label.clone(),
active: AtomicBool::new(false),
};
let nav_contact = NavLink {
href: config.contact.href.clone(),
text: config.contact.label.clone(),
active: AtomicBool::new(false),
};
let nav_elements = vec![nav_home, nav_about, nav_products, nav_contact];
// Construct the product classes
let estrogens = NavLink {
href: config.products.estrogens.href.clone(),
text: config.products.estrogens.label.clone(),
active: AtomicBool::new(false),
};
let aa = NavLink {
href: config.products.anti_androgens.href.clone(),
text: config.products.anti_androgens.label.clone(),
active: AtomicBool::new(false),
};
let progestogens = NavLink {
href: config.products.progestogens.href.clone(),
text: config.products.progestogens.label.clone(),
active: AtomicBool::new(false),
};
let product_classes = vec![estrogens, aa, progestogens];
return PageBase {
config,
tera,
nav_elements,
product_classes,
};
}
// Render all the pages
pub fn render_pages() {
let base = build_base();
// Render pages
let index_render = RenderObject {
url: "index.html".to_string(),
tera: base.tera.clone(),
subtitle: &base.config.index.title,
active_page: &base.config.index.label,
title: &base.config.title,
description: &base.config.description,
products: None,
};
render_page(
// Home
index_render,
&base.nav_elements,
&base.product_classes,
);
let about_render = RenderObject {
url: "about/index.html".to_string(),
tera: base.tera.clone(),
subtitle: &base.config.about.title,
active_page: &base.config.about.label,
title: &base.config.title,
description: &base.config.description,
products: None,
};
render_page(
// About
about_render,
&base.nav_elements,
&base.product_classes,
);
let contact_render = RenderObject {
url: "contact/index.html".to_string(),
tera: base.tera.clone(),
subtitle: &base.config.contact.title,
active_page: &base.config.contact.label,
title: &base.config.title,
description: &base.config.description,
products: None,
};
render_page(
// About
contact_render,
&base.nav_elements,
&base.product_classes,
);
render_products();
}
// Render the products page
pub fn render_products() {
let base = build_base();
let products_vec: Option<Vec<Product>>;
match read_all_products() {
Ok(prods) => products_vec = Some(prods),
Err(err) => products_vec = None,
}
let products_render = RenderObject {
url: "products/index.html".to_string(),
tera: base.tera.clone(),
subtitle: &base.config.products.title,
active_page: &base.config.products.label,
title: &base.config.title,
description: &base.config.description,
products: products_vec,
};
render_page(
// Products
products_render,
&base.nav_elements,
&base.product_classes,
);
}
// Render a specific product page
pub fn render_product(product: Product) {
let base = build_base();
let obj = ProductRenderObject {
url: format!("products/{}/index.html", product.short_name),
tera: base.tera.clone(),
product,
};
let url = format!("static/{}", &obj.url);
let url_str = url.as_str();
let prefix = std::path::Path::new(url_str).parent().unwrap();
std::fs::create_dir_all(prefix).unwrap();
let mut output_file = std::fs::File::create(url).expect("create failed");
// Change the active NavLink
for i in &base.nav_elements {
if i.text == "Products" {
i.active.store(true, Ordering::Relaxed);
} else {
i.active.store(false, Ordering::Relaxed);
}
}
// Create the context
let mut context = Context::new();
context.insert("title", &base.config.title);
context.insert("description", &base.config.description);
// Fill out product information
context.insert("product", &obj.product);
// Insert navigation elements
for i in &base.nav_elements {
context.insert(&i.text.to_ascii_lowercase(), &i);
}
context.insert("product_classes", &base.product_classes);
// Write it to the file
output_file
.write_all(
base.tera
.render("products/product.html", &context)
.unwrap()
.as_bytes(),
)
.expect("write failed");
}
fn render_page(data: RenderObject, nav_elements: &[NavLink], product_classes: &[NavLink]) {
let mut output_file =
std::fs::File::create(format!("static/{}", data.url)).expect("create failed");
// Change the active NavLink
for i in nav_elements {
if i.text == data.active_page {
i.active.store(true, Ordering::Relaxed);
} else {
i.active.store(false, Ordering::Relaxed);
}
}
// Create the context
let mut context = Context::new();
context.insert("title", data.title);
context.insert("description", data.description);
context.insert("subtitle", &data.subtitle);
// Insert navigation elements
for i in nav_elements {
context.insert(&i.text.to_ascii_lowercase(), &i);
}
context.insert("product_classes", &product_classes);
if data.active_page == "Contact" {
context.insert("is_contact", &true);
} else {
context.insert("is_contact", &false);
}
// Fill out products page
match data.products {
None => info!("no product"),
Some(p) => {
let mut estrogens = Vec::new();
let mut anti_androgens = Vec::new();
let mut progestogens = Vec::new();
for i in p {
match i.class {
ProductClass::Estrogens => estrogens.push(i),
ProductClass::AntiAndrogens => anti_androgens.push(i),
ProductClass::Progestogens => progestogens.push(i),
}
}
context.insert("estrogens", &estrogens);
context.insert("anti_androgens", &anti_androgens);
context.insert("progestogens", &progestogens);
}
}
// Write it to the file
output_file
.write_all(data.tera.render(&data.url, &context).unwrap().as_bytes())
.expect("write failed");
}