323 lines
8.9 KiB
Rust
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");
|
|
}
|