202 lines
6.8 KiB
Rust
202 lines
6.8 KiB
Rust
use uuid::Uuid;
|
|
use chrono::prelude::*;
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use build_html::{self, Html, HtmlContainer, ContainerType, Container, HtmlPage};
|
|
|
|
use crate::io::write_html;
|
|
|
|
// Content strings to add to html
|
|
pub const TITLE: &str = r#"Catgirl Cooking"#;
|
|
pub const HEADER: &str = r#"🥘 Catgirl Cooking"#;
|
|
pub const DESCRIPTION: &str = r#"The cutest cooking site on the net :3"#;
|
|
pub const DETAILS: &str = r#"Absolutely no ads, tracking, or nazis, ever."#;
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum TimeUnits {
|
|
Hours,
|
|
Minutes,
|
|
Seconds,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum MeasurementUnits {
|
|
Grams,
|
|
Kilograms,
|
|
Pounds,
|
|
Litres,
|
|
Millilitres,
|
|
Gallons,
|
|
Ounces,
|
|
Pinches,
|
|
Drops,
|
|
Cups,
|
|
Tablespoons,
|
|
Teaspoons,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
pub struct Tag(pub char, pub String);
|
|
|
|
impl Tag {
|
|
pub fn new(s: String) -> Tag {
|
|
Tag('#', s)
|
|
}
|
|
|
|
pub fn to_string(&self) -> String {
|
|
let mut tag = String::new();
|
|
tag.push_str(&self.0.to_string());
|
|
tag.push_str(&self.1);
|
|
return tag;
|
|
}
|
|
|
|
pub fn from_string(s: String) -> Tag {
|
|
let hash = s.chars().nth(0).unwrap();
|
|
let mut text = s;
|
|
text.remove(0);
|
|
text.make_ascii_lowercase();
|
|
return Tag(hash, text);
|
|
}
|
|
|
|
pub fn display(&self) {
|
|
println!("{}{}", self.0, self.1);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct Recipe {
|
|
pub id: Uuid, // Unique recipe ID
|
|
pub name: String, // Full recipe name
|
|
pub shortcode: String,
|
|
pub prep_time: (i32, TimeUnits), // Preparation time: (value, units)
|
|
pub cooking_time: (i32, TimeUnits), // Cooking time: (value, units)
|
|
pub servings: u8, // How many servinge the recipe makes
|
|
pub ingredients: Vec<(i32, MeasurementUnits, String)>, // Vector of ingredients: (value, units, name)
|
|
pub directions: Vec<(u16, String)>, // List of instructions: (step #, details)
|
|
pub attribution: String, // Author name
|
|
pub posted_date: DateTime<Utc>, // Date the recipe was first posted
|
|
pub edited_date: Option<DateTime<Utc>>, // Date the recipe was last edited
|
|
pub tags: Option<Vec<Tag>>, // List of tags
|
|
}
|
|
|
|
impl Recipe {
|
|
pub fn example(name: &str, time: i32, directions: Vec<&str>, ingredients: Vec<&str>) -> Recipe {
|
|
let mut ingredients_list: Vec<(i32, MeasurementUnits, String)> = Vec::new();
|
|
for i in 0..ingredients.len() {
|
|
ingredients_list.push((i.try_into().unwrap(), MeasurementUnits::Litres, ingredients[i].to_string()));
|
|
}
|
|
|
|
let mut directions_list: Vec<(u16, String)> = Vec::new();
|
|
for n in 0..directions.len() {
|
|
directions_list.push((n.try_into().unwrap(), directions[n].to_string()));
|
|
}
|
|
|
|
Recipe {
|
|
id: Uuid::new_v4(),
|
|
name: name.to_string(),
|
|
shortcode: construct_shortcode(name.to_string()),
|
|
prep_time: (time, TimeUnits::Minutes),
|
|
cooking_time: (time, TimeUnits::Hours),
|
|
servings: 4,
|
|
ingredients: ingredients_list,
|
|
directions: directions_list,
|
|
attribution: "Erin".to_string(),
|
|
posted_date: Utc::now(),
|
|
edited_date: None,
|
|
tags: None,
|
|
}
|
|
}
|
|
|
|
pub fn new(name: String, prep_time: (i32, TimeUnits), cooking_time: (i32, TimeUnits), servings: u8, ingredients: Vec<(i32, MeasurementUnits, String)>, directions: Vec<String>, attribution: String, tags: Option<Vec<Tag>>) -> Recipe {
|
|
let mut directions_list: Vec<(u16, String)> = Vec::new();
|
|
for i in 0..directions.len() {
|
|
directions_list.push((i.try_into().unwrap(), directions[i].to_string()));
|
|
}
|
|
|
|
Recipe {
|
|
id: Uuid::new_v4(),
|
|
name: name.clone(),
|
|
shortcode: construct_shortcode(name.clone()),
|
|
prep_time,
|
|
cooking_time,
|
|
servings,
|
|
ingredients,
|
|
directions: directions_list,
|
|
attribution,
|
|
posted_date: Utc::now(),
|
|
edited_date: None,
|
|
tags,
|
|
}
|
|
}
|
|
|
|
pub fn construct_page(&self) {
|
|
let mut recipe_page = build_html::HtmlPage::new()
|
|
.with_head_link("/favicon.ico", "icon") // Favicon
|
|
.with_stylesheet("/style.css") // Link stylesheet
|
|
.with_meta(vec![("charset", "UTF-8")])
|
|
.with_meta(vec![("name", "viewport"), ("content", "width=device-width, initial-scale=1")]) // Display stuff
|
|
.with_meta(vec![("name", "description"), ("content", DESCRIPTION)]) // Add the description
|
|
.with_title(TITLE)
|
|
.with_header(1, &self.name)
|
|
.with_raw(r#"<hr>"#);
|
|
|
|
// Metadata
|
|
let meta = Container::new(ContainerType::UnorderedList)
|
|
.with_attributes(vec![("class", "recipe")])
|
|
.with_paragraph(format!("⏲️ Preparation time: {} {:?}", &self.prep_time.0, &self.prep_time.1))
|
|
.with_paragraph(format!("🍳 Cooking time: {} {:?}", &self.cooking_time.0, &self.cooking_time.1))
|
|
.with_paragraph(format!("🍽️ Servings: {}", &self.servings));
|
|
recipe_page.add_container(meta);
|
|
|
|
// Append ingredients
|
|
recipe_page.add_header(2, r#"Ingredients:"#);
|
|
let mut ingredients_container = Container::new(ContainerType::UnorderedList)
|
|
.with_attributes(vec![("class", "recipe")]);
|
|
for i in &self.ingredients {
|
|
let ingredient = format!("{:?} {:?} {}", &i.0, &i.1, &i.2);
|
|
ingredients_container.add_paragraph(ingredient);
|
|
}
|
|
recipe_page.add_container(ingredients_container);
|
|
|
|
// Append directions
|
|
recipe_page.add_header(2, r#"Directions:"#);
|
|
let mut directions_container = Container::new(ContainerType::OrderedList)
|
|
.with_attributes(vec![("class", "recipe")]);
|
|
for n in &self.directions {
|
|
directions_container.add_paragraph(&n.1);
|
|
}
|
|
recipe_page.add_container(directions_container);
|
|
|
|
recipe_page.add_header(2, r#"Author:"#);
|
|
recipe_page.add_paragraph(&self.attribution);
|
|
|
|
// Append tags
|
|
let mut tags_html = String::new();
|
|
tags_html.push_str(r#"<i><p>Tags: "#);
|
|
match &self.tags {
|
|
Some(list) => {
|
|
for x in list {
|
|
tags_html.push_str(format!("<a href='/tags/{}'>#{}</a>,", x.1, x.1).as_str());
|
|
}
|
|
},
|
|
None => {
|
|
tags_html.push_str(r#"no tags!! :o"#);
|
|
},
|
|
}
|
|
|
|
|
|
write_html(recipe_page.to_html_string(), "en", Some(&self.shortcode));
|
|
}
|
|
}
|
|
|
|
pub fn construct_shortcode(full_name: String) -> String {
|
|
let mut prev_name = full_name;
|
|
prev_name.make_ascii_lowercase();
|
|
return prev_name
|
|
.chars()
|
|
.map(|x| match x {
|
|
' ' => '-',
|
|
_ => x,
|
|
})
|
|
.collect();
|
|
}
|