Add database functionality, recipe pages

This commit is contained in:
~erin 2022-01-23 16:38:44 -05:00
parent b098b6f708
commit db1dcc0704
No known key found for this signature in database
GPG Key ID: DA70E064A8C70F44
12 changed files with 611 additions and 31 deletions

277
Cargo.lock generated
View File

@ -8,12 +8,42 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "build_html"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e121f1569e70b069135a0768a6b62a16c9c0eeaba92815a4baa6fdb93a97ec79"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -38,11 +68,66 @@ dependencies = [
name = "cooking"
version = "0.1.0"
dependencies = [
"bincode",
"build_html",
"chrono",
"serde",
"serde_derive",
"sled",
"uuid",
]
[[package]]
name = "crc32fast"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "getrandom"
version = "0.2.4"
@ -54,12 +139,63 @@ dependencies = [
"wasi",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
[[package]]
name = "lock_api"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -79,12 +215,55 @@ dependencies = [
"autocfg",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.4"
@ -125,11 +304,74 @@ dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot",
"zstd",
]
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "time"
@ -141,6 +383,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "uuid"
version = "1.0.0-alpha.1"
@ -179,3 +427,32 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zstd"
version = "0.9.2+zstd.1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "4.1.3+zstd.1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "1.6.2+zstd.1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f"
dependencies = [
"cc",
"libc",
]

View File

@ -19,3 +19,7 @@ panic = "abort"
build_html = "2.0.0"
uuid = { version = "1.0.0-alpha.1", features = ["v4", "serde", "fast-rng"] }
chrono = { version = "0.4", features = ["serde"] }
sled = { version = "0.34.7", features = ["compression"] }
bincode = "1.3.3"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0.132"

View File

@ -1 +0,0 @@
<!DOCTYPE html><html><head><link href="favicon.ico" rel="icon"><link href="style.css" rel="stylesheet"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="The cutest cooking site on the net :3"><title>Catgirl Cooking</title></head><body><h1>🥘 Catgirl Cooking</h1><hr><p>The cutest cooking site on the net :3</p><p>Absolutely no ads, tracking, or nazis, ever.</p><i><p>Tags:</p><p><a href='breakfast'>breakfast</a>,<a href='apples'>apples</a>,<a href='cheese'>cheese</a>,<a href='vegan'>vegan</a>,<a href='vegetarian'>vegetarian</a>,</p></i><h2>Recipes:</h2><ul><li><a href="Macaroni & Cheese">Macaroni & Cheese</a></li><li><a href="Soup">Soup</a></li><li><a href="Apple Pie">Apple Pie</a></li><li><a href="More Soup">More Soup</a></li><li><a href="idk">idk</a></li><li><a href="cum">cum</a></li></ul><footer><hr><a href="/">home</a><a href="/atom.xml">atom</a><p>Software licensed under the CNPLv7+</p><p>Recipes under Public Domain</p></footer></body></html>

139
src/io.rs
View File

@ -1,19 +1,148 @@
use std::fs::File;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::Path;
use std::path::{PathBuf};
use std::env;
use uuid::Uuid;
pub fn write_html(html: String) {
let path = Path::new("index.html");
use crate::structures::*;
pub fn write_html(html: String, lang: &str, shortcode: Option<&str>) {
// Create a path to write the file to
let mut path = PathBuf::new();
path.push("static"); // base path for web content
path.push(lang); // not really that useful right now
match shortcode { // If there's a shortcode (for a recipe), use that path
Some(short) => {
path.push(short);
path.push("index.html");
},
None => path.push("index.html"), // Otherwise just write to the main index
}
// If the directory doesn't exist, create it
let prefix = path.parent().unwrap();
std::fs::create_dir_all(prefix).unwrap();
// Open file in write-only mode
let mut file = match File::create(&path) {
let mut file = match OpenOptions::new()
.write(true)
.create(true)
.open(&path) {
Err(why) => panic!("couldn't create file: {}", why),
Ok(file) => file,
};
// Write the HTML to fil
// Write the HTML to file
match file.write_all(html.as_bytes()) {
Err(why) => panic!("couldn't write to file: {}", why),
Ok(_) => println!("successfully wrote to file"),
}
}
pub fn open_database(keyspace: &str) -> Option<sled::Tree> {
let db: sled::Db;
let key = "HOME";
let database_path = match env::var(key) {
Ok(val) => {
let mut tmp_path = PathBuf::new();
tmp_path.push(val);
tmp_path.push(".local/share/catgirl-cooking");
tmp_path.push("database");
tmp_path
},
Err(e) => {
panic!("failed to get key: {}", e);
}
};
match sled::open(database_path) {
Ok(database) => db = database,
Err(error) => {
println!("error in opening database: {}", error);
return None;
}
}
let database: Option<sled::Tree> = match db.open_tree(keyspace) {
Ok(tree) => Some(tree),
Err(e) => { println!("error in opening Tree: {}", e); None },
};
return database;
}
pub fn db_insert_recipe(recipe: Recipe) {
let db = match open_database("recipes") {
Some(database) => database,
None => panic!(),
};
let bytes;
match bincode::serialize(&recipe) {
Ok(result) => bytes = result,
Err(error) => panic!("could not serialize recipe: {}", error),
}
match db.insert(recipe.id.as_simple().to_string(), bytes) {
Ok(_) => println!("inserted recipe"),
Err(error) => panic!("failed to insert recipe: {}", error),
}
db.flush().unwrap();
}
pub fn db_get_recipe(id: Uuid) -> Recipe {
let db = match open_database("recipes") {
Some(database) => database,
None => panic!(),
};
let db_entry = match db.get(id.as_simple().to_string()) {
Ok(entry) => entry,
Err(error) => panic!("failed to get recipe: {}", error),
};
if let Some(entry) = db_entry {
let read_entry = match bincode::deserialize(&entry) {
Ok(entry) => entry,
Err(error) => panic!("failed to deserialize entry: {}", error),
};
return read_entry;
} else {
panic!("failed to find entry");
}
}
pub fn db_get_all_recipes() -> Vec<Recipe> {
let db = match open_database("recipes") {
Some(database) => database,
None => panic!(),
};
let first_key = match db.first() {
Ok(pair) => match pair {
Some(key) => key.0,
None => panic!("error getting first key"),
}
Err(error) => panic!("error getting first key: {}", error),
};
let mut recipes_list: Vec<Recipe> = Vec::new();
let mut iter = db.range(first_key..);
loop {
match iter.next() {
Some(x) => {
let read_entry;
match bincode::deserialize(&x.unwrap().1) {
Ok(result) => read_entry = result,
Err(error) => panic!("failed to deserialize entry: {}", error),
}
recipes_list.push(read_entry);
},
None => break,
}
}
return recipes_list;
}

View File

@ -1,10 +1,14 @@
use build_html::{self, Html, HtmlContainer, ContainerType, Container};
use uuid::Uuid;
mod structures;
mod io;
mod tests;
fn main() {
use crate::structures::*;
use crate::structures::{MeasurementUnits::*, TimeUnits::*};
fn construct_main_page() {
// Specify info to go onto the base page
let base_page = build_html::HtmlPage::new()
.with_head_link("favicon.ico", "icon") // Favicon
@ -32,12 +36,11 @@ fn main() {
// Create (test) lists of tags & recipes
let tags: Vec<&str> = vec!["breakfast", "apples", "cheese", "vegan", "vegetarian"];
let recipes: Vec<&str> = vec!["Macaroni & Cheese", "Soup", "Apple Pie", "More Soup", "idk", "cum"];
let recipes = io::db_get_all_recipes();
home_page.add_raw(r#"<i>"#); // Tags in italics
home_page.add_paragraph("Tags:");
let mut tags_html = String::new(); // Create custom html string for tags
tags_html.push_str(r#"<p>"#); // Begin paragraph element
tags_html.push_str(r#"<p>Tags: "#); // Begin paragraph element
for x in tags { // Go through the list of tags
tags_html.push_str(format!("<a href='{}'>{}</a>,", x, x).as_str()); // Append the tags as a link
}
@ -49,11 +52,36 @@ fn main() {
home_page.add_header(2, "Recipes:");
let mut recipes_container = Container::new(ContainerType::UnorderedList);
for i in recipes {
recipes_container.add_link(i, i);
let mut link = String::new();
link.push_str("/");
link.push_str(&i.shortcode);
recipes_container.add_link(link, &i.name);
}
home_page.add_container(recipes_container);
home_page.add_container(footer);
io::write_html(home_page.to_html_string());
// Create the main page file
io::write_html(home_page.to_html_string(), "en", None);
}
fn rebuild() {
construct_main_page();
for i in io::db_get_all_recipes() {
i.construct_page();
}
}
fn main() {
/*
let full_recipe = Recipe::new(
"Full Recipe".to_string(),
(3, Minutes),
(4, Hours),
7,
vec![(3, Cups, "flour".to_string()), (4, Tablespoons, "sugar".to_string()), (1, Cups, "water".to_string())],
vec!["Mix flour and sugar in a bowl".to_string(), "Slowly add water".to_string(), "Bake in the oven for 4 hours".to_string()],
"Erin Nova".to_string(),
Some(vec![Tag('#', "meow".to_string()), Tag('#', "gay".to_string())]));*/
rebuild();
}

View File

@ -1,5 +1,9 @@
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"#;
@ -7,12 +11,14 @@ 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,
Min,
Sec,
Minutes,
Seconds,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum MeasurementUnits {
Grams,
Kilograms,
@ -21,13 +27,14 @@ pub enum MeasurementUnits {
Millilitres,
Gallons,
Ounces,
Pinch,
Drop,
Tablespoon,
Teaspoon,
Pinches,
Drops,
Cups,
Tablespoons,
Teaspoons,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Tag(pub char, pub String);
impl Tag {
@ -55,16 +62,140 @@ impl Tag {
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Recipe {
id: Uuid, // Unique recipe ID
name: String, // Full recipe name
prep_time: (i32, TimeUnits), // Preparation time: (value, units)
cooking_time: (i32, TimeUnits), // Cooking time: (value, units)
servings: u8, // How many servinge the recipe makes
ingredients: Vec<(i32, MeasurementUnits, String)>, // Vector of ingredients: (value, units, name)
directions: Vec<(u16, String)>, // List of instructions: (step #, details)
attribution: String, // Author name
posted_date: DateTime<Utc>, // Date the recipe was first posted
edited_date: DateTime<Utc>, // Date the recipe was last edited
tags: Vec<Tag>, // List of tags
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();
}

View File

@ -17,4 +17,9 @@ mod tests {
fn test_tag_to_string() {
assert_eq!(Tag('#', "test".to_string()).to_string(), "#test".to_string());
}
#[test]
fn test_shortcode_conversion() {
assert_eq!(construct_shortcode("Test SHORtcode".to_string()), "test-shortcode".to_string());
}
}

View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><link href="/favicon.ico" rel="icon"><link href="/style.css" rel="stylesheet"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="The cutest cooking site on the net :3"><title>Catgirl Cooking</title></head><body><h1>Another One!</h1><hr><ul class="recipe"><li><p>⏲️ Preparation time: 5 Minutes</p></li><li><p>🍳 Cooking time: 5 Hours</p></li><li><p>🍽️ Servings: 4</p></li></ul><h2>Ingredients:</h2><ul class="recipe"><li><p>0 Litres flour</p></li><li><p>1 Litres eggs</p></li><li><p>2 Litres sugar</p></li></ul><h2>Directions:</h2><ol class="recipe"><li><p>prepare ingredients</p></li><li><p>cook it</p></li></ol><h2>Author:</h2><p>Erin</p></body></html>

View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><link href="/favicon.ico" rel="icon"><link href="/style.css" rel="stylesheet"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="The cutest cooking site on the net :3"><title>Catgirl Cooking</title></head><body><h1>Full Recipe</h1><hr><ul class="recipe"><li><p>⏲️ Preparation time: 3 Minutes</p></li><li><p>🍳 Cooking time: 4 Hours</p></li><li><p>🍽️ Servings: 7</p></li></ul><h2>Ingredients:</h2><ul class="recipe"><li><p>3 Cups flour</p></li><li><p>4 Tablespoons sugar</p></li><li><p>1 Cups water</p></li></ul><h2>Directions:</h2><ol class="recipe"><li><p>Mix flour and sugar in a bowl</p></li><li><p>Slowly add water</p></li><li><p>Bake in the oven for 4 hours</p></li></ol><h2>Author:</h2><p>Erin Nova</p></body></html>

1
static/en/index.html Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><link href="favicon.ico" rel="icon"><link href="style.css" rel="stylesheet"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="The cutest cooking site on the net :3"><title>Catgirl Cooking</title></head><body><h1>🥘 Catgirl Cooking</h1><hr><p>The cutest cooking site on the net :3</p><p>Absolutely no ads, tracking, or nazis, ever.</p><i><p>Tags: <a href='breakfast'>breakfast</a>,<a href='apples'>apples</a>,<a href='cheese'>cheese</a>,<a href='vegan'>vegan</a>,<a href='vegetarian'>vegetarian</a>,</p></i><h2>Recipes:</h2><ul><li><a href="/recipe-test">Recipe Test</a></li><li><a href="/recipe-test">Recipe Test</a></li><li><a href="/full-recipe">Full Recipe</a></li><li><a href="/another-one!">Another One!</a></li></ul><footer><hr><a href="/">home</a><a href="/atom.xml">atom</a><p>Software licensed under the CNPLv7+</p><p>Recipes under Public Domain</p></footer></body></html>

View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><link href="/favicon.ico" rel="icon"><link href="/style.css" rel="stylesheet"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="The cutest cooking site on the net :3"><title>Catgirl Cooking</title></head><body><h1>Recipe Test</h1><hr><ul class="recipe"><li><p>⏲️ Preparation time: 3 Minutes</p></li><li><p>🍳 Cooking time: 3 Hours</p></li><li><p>🍽️ Servings: 4</p></li></ul><h2>Ingredients:</h2><ul class="recipe"><li><p>0 Litres flour</p></li><li><p>1 Litres sugar</p></li><li><p>2 Litres nothing</p></li></ul><h2>Directions:</h2><ol class="recipe"><li><p>prepare ingredients</p></li><li><p>cook it</p></li><li><p>meow</p></li></ol><h2>Author:</h2><p>Erin</p></body></html>

View File

@ -26,7 +26,6 @@ body {
max-width: 800px;
margin: auto;
padding: 0 16px;
margin-bottom: 500px;
font-family: 'Atkinson Hyperlegible', sans-serif;
}
@ -57,3 +56,7 @@ img {
ul {
column-count: 3;
}
ul.recipe {
column-count: 1;
}