use build_html::{self, Container, ContainerType, Html, HtmlContainer, HtmlPage}; use chrono::prelude::*; use rocket::form::Form; use serde_derive::{Deserialize, Serialize}; use std::fmt; use uuid::Uuid; 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, } impl fmt::Display for TimeUnits { // This trait requires `fmt` with this exact signature. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Write strictly the first element into the supplied output // stream: `f`. Returns `fmt::Result` which indicates whether the // operation succeeded or failed. Note that `write!` uses syntax which // is very similar to `println!`. match &self { TimeUnits::Hours => write!(f, "hr."), TimeUnits::Minutes => write!(f, "min."), TimeUnits::Seconds => write!(f, "sec."), } } } #[derive(Debug, Serialize, Deserialize)] pub enum MeasurementUnits { Grams, Kilograms, Pounds, Litres, Millilitres, Gallons, Ounces, Pinches, Drops, Cups, Tablespoons, Teaspoons, } type mu = MeasurementUnits; impl fmt::Display for MeasurementUnits { // This trait requires `fmt` with this exact signature. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Write strictly the first element into the supplied output // stream: `f`. Returns `fmt::Result` which indicates whether the // operation succeeded or failed. Note that `write!` uses syntax which // is very similar to `println!`. match &self { mu::Grams => write!(f, "g"), mu::Kilograms => write!(f, "kg"), mu::Pounds => write!(f, "lbs"), mu::Litres => write!(f, "L"), mu::Millilitres => write!(f, "mL"), mu::Gallons => write!(f, "gal"), mu::Ounces => write!(f, "oz"), mu::Pinches => write!(f, "pinch(es)"), mu::Drops => write!(f, "drop(s)"), mu::Cups => write!(f, "cup(s)"), mu::Tablespoons => write!(f, "tablespoon(s)"), mu::Teaspoons => write!(f, "teaspoon(s)"), } } } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Tag(pub char, pub String); impl Tag { pub fn new(s: String) -> Tag { let mut fuck = s; fuck.make_ascii_lowercase(); Tag('#', fuck) } 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(FromForm)] pub struct RecipeForm { pub recipe_name: String, pub author_name: String, pub tags: String, pub prep_time: String, pub cooking_time: String, pub servings: String, pub ingredients: String, pub directions: String, } #[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, // Date the recipe was first posted pub edited_date: Option>, // Date the recipe was last edited pub tags: Option>, // 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, attribution: String, tags: Option>, ) -> 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) { // Create the footer let footer = Container::new(ContainerType::Footer) .with_raw(r#"
"#) // Line seperator .with_link("/", "home") // Link to the root page .with_link("/submit.html", "submit recipe") // Link to submission form .with_link("/rss.xml", "rss") // Link the the Atom feed // License info .with_paragraph("Software licensed under the CNPLv7+") .with_paragraph("Recipes under Public Domain"); // 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)); // 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); } // 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); } // Tags let mut tags_html = String::new(); tags_html.push_str(r#"

Tags: "#); match &self.tags { Some(list) => { for x in 0..list.len() { if x != list.len() - 1 { tags_html.push_str( format!("#{},", list[x].1, list[x].1).as_str(), ); } else { tags_html.push_str( format!("#{}", list[x].1, list[x].1).as_str(), ); } } } None => { tags_html.push_str(r#"no tags!! :o"#); } } tags_html.push_str(r#"

"#); // Date let edit_date = match &self.edited_date { Some(date) => { format!( "

Last edited on: {}-{}-{}

", date.year(), date.month(), date.day() ) } None => "

No edits

".to_string(), }; // Construct Main Page let 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#"
"#) .with_container(meta) .with_header(2, r#"Ingredients:"#) .with_container(ingredients_container) .with_header(2, r#"Directions:"#) .with_container(directions_container) .with_header(2, r#"Misc."#) .with_raw(format!("

Author: {}

", &self.attribution).as_str()) .with_raw(tags_html) .with_raw(format!( "

Recipe added on: {}-{}-{}

", &self.posted_date.year(), &self.posted_date.month(), &self.posted_date.day() )) .with_raw(edit_date) .with_container(footer); 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(); } // Struct for the users answer to a captcha #[derive(Clone, Debug, FromForm)] pub struct CaptchaAnswer { pub text: String, }