From 6faace41c693871941d657a11dc63dae930f4769 Mon Sep 17 00:00:00 2001 From: Max Value Date: Mon, 12 May 2025 20:36:35 +0100 Subject: [PATCH] Completed renderer + stylesheet + rendering individual to latex + rendering individual to html + homepage + page on bakers percentage + gitignore + extra feilds to example recipe --- .gitignore | 3 + pages/percentage.html | 122 ++++++++++++++++++++++++++++++++++++++++ sous.py | 81 ++++++++++++++++++++++++-- src/straight-white.toml | 12 ++++ style.css | 13 +++++ templates/index.tex | 0 templates/template.html | 65 ++++++++++++++++++--- templates/template.tex | 47 ++++++++++++++++ 8 files changed, 328 insertions(+), 15 deletions(-) create mode 100644 .gitignore create mode 100644 pages/percentage.html create mode 100644 templates/index.tex diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..596f14d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/* +recipes/* +index.html diff --git a/pages/percentage.html b/pages/percentage.html new file mode 100644 index 0000000..d5f747b --- /dev/null +++ b/pages/percentage.html @@ -0,0 +1,122 @@ + + + + + + Bread + + +
+ Back to main page +

Bakers Percentage

+

+ To avoid making mistakes when converting recipe quantities, a system called "bakers percentage" is used. Each ingredient is given as a percentage where flour usually represents 100%. This way recipies can be easily compared to each other with flour as the common ground between the two. +

+

+ Take a look at these two recipes: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Recipe 1Recipe 2
Flour100%100%
Water65%74%
Olive oil-6%
Yeast1%1%
Salt1.5%1.5%
+

+ If all these figures where given as weights (and one recipe was portioned to produce significantly more) it might be difficult to compare the two recipes. However, becasue of the percentages it becomes easy to see that they both have a hydration of 65% and 74% respectively. This tells us recipe 1 is likely to be conventionally kneeded white yeasted bread and recipe 2 is likely to be some lighter bread with an airier crumb such as Ciabatta. +

+

+ If we then wanted to make either of these recipes, this would be easy. Simply add up the percentages to get a total then divide the target weight by this figure to work out what "1%" means in terms of grams. This can then be multiplied by the percentage of each ingredient to get the weight of each for your target quantity. +

+ + + + + 100 + % + + + 65 + % + + + 1 + % + + + 1.5 + % + + + = + + + 167.5 + % + + + + + Total weight (%) + + + = + + + 167.5 + % + + + + + Total weight (kg) + + + = + + + 10 + kg + + + + + + 1 + % + + + + + + 59.70 + g + + + + + + + diff --git a/sous.py b/sous.py index d6171c4..3671b91 100755 --- a/sous.py +++ b/sous.py @@ -1,13 +1,82 @@ -#! /bin/python +#! .venv/bin/python import tomllib -import os +from os import path, listdir, system +from jinja2 import Environment, FileSystemLoader +from datetime import datetime -class recipe: +class RecipeBook: def __init__(self): - pass + self.html_environment = Environment(loader = FileSystemLoader("templates")) + self.latex_environment = Environment( + loader = FileSystemLoader("templates"), + block_start_string = "|%", + block_end_string = "%|", + variable_start_string = "|~", + variable_end_string = "~|", + comment_start_string = "|#", + comment_end_string = "#|", + trim_blocks = True, + lstrip_blocks = True + ) + self.recipes = [] -with open("recipes/straight-white.toml", "rb") as f: - recipe = tomllib.load(f) + for filename in listdir("src"): + self.recipes.append(Recipe(filename)) + + def render_html(self): + keywords = {} + + for recipe in self.recipes: + recipe.render_html(self.html_environment) + + for word in recipe.recipe["information"]["keywords"]: + if word in keywords: + keywords[word].append((recipe.recipe["information"]["title"], recipe.recipe["html"])) + else: + keywords |= { word: [(recipe.recipe["information"]["title"], recipe.recipe["html"])]} + + template = self.html_environment.get_template("index.html") + with open("index.html", "w") as f: + f.write(template.render(recipes = self.recipes, keywords = keywords, time = datetime.now())) + + def render_latex(self): + for recipe in self.recipes: + recipe.render_latex(self.latex_environment) + +class Recipe: + def __init__(self, filename: str): + with open(path.join("src", filename), "rb") as f: + self.recipe = tomllib.load(f) + + total = sum([int(x) for x in self.recipe["ingredients"].values()]) + self.recipe.update(total = total) + self.recipe.update(kg = 1000 / total) + self.recipe.update(filename = filename) + self.recipe.update(html = filename[:-5] + ".html") + self.recipe.update(tex = filename[:-5] + ".tex") + self.recipe.update(pdf = filename[:-5] + ".pdf") + self.recipe.update(image = path.isfile(f"media/{filename[:-5]}.png")) + + def render_html(self, environment: Environment): + template = environment.get_template("template.html") + + with open(path.join("recipes", self.recipe["html"]), "w") as f: + f.write(template.render(recipe = self.recipe, time = datetime.now())) + + def render_latex(self, environment: Environment): + template = environment.get_template("template.tex") + + with open(path.join("build", self.recipe["tex"]), "w") as f: + f.write(template.render(recipe = self.recipe)) + + system(f"pdflatex -interaction='nonstopmode' -output-directory='build' 'build/{self.recipe["tex"]}'") + system(f"mv build/{self.recipe["pdf"]} recipes") + +# main + +recipe_book = RecipeBook() +recipe_book.render_html() +recipe_book.render_latex() diff --git a/src/straight-white.toml b/src/straight-white.toml index e0c8c35..b81aad5 100644 --- a/src/straight-white.toml +++ b/src/straight-white.toml @@ -1,5 +1,17 @@ [information] title = "Straight white yeasted loaf" +description = "This is the note" + +keywords = ["white", "yeasted"] + +notes = """ +This is the note + + + +This is a line break +""" + [ingredients] white-flour = 90 diff --git a/style.css b/style.css index e69de29..29b228e 100644 --- a/style.css +++ b/style.css @@ -0,0 +1,13 @@ +body { + font-family: sans-serif; +} +main { + width: min(750px, 80%); + margin: auto; +} +table { + width: 100%; +} +th { + text-align: left; +} diff --git a/templates/index.tex b/templates/index.tex new file mode 100644 index 0000000..e69de29 diff --git a/templates/template.html b/templates/template.html index 4af7662..ac544b5 100644 --- a/templates/template.html +++ b/templates/template.html @@ -1,12 +1,59 @@ - - - - - -
-
-
- + + + + {{ recipe.information.title }} + + +
+ Back to main page +

{{ recipe.information.title }}

+ [History] - + [PDF] +

{{ recipe.information.description }}

+

Notes

+ {{ recipe.information.notes|replace("\n", "
") }} +

Ingredients

+
+ + + + + + + + + + {% for name, weight in recipe.ingredients.items() %} + + + + + {% macro weighted(multiplier) %} + {% set rounded = (weight * recipe.kg * multiplier)|round|int %} + {% set real = (weight * recipe.kg * multiplier)|round(2) %} + {% set error = (real - rounded)|abs %} + {% if error < real * 0.05 %} + {{ rounded }} + {% else %} + {{ real }} + {% endif %} + {% endmacro %} + + + + + + + + {% endfor %} +
Bakers %500g total1kg total2kg total3kg total4kg total
{{ name|replace("-", " ")|title }}{{ weight }}%{{ weighted(0.5) }}{{ weighted(1) }}{{ weighted(2) }}{{ weighted(3) }}{{ weighted(4) }}
+

+ Percentage total: {{ recipe.total }}%
+ Values have been rounded where the error would not be significant (p < 0.05). +

+

Last compiled at {{ time }}

+
+ diff --git a/templates/template.tex b/templates/template.tex index e69de29..6881d92 100644 --- a/templates/template.tex +++ b/templates/template.tex @@ -0,0 +1,47 @@ +\documentclass{article} + +\usepackage[margin=2cm]{geometry} +\usepackage[scaled]{helvet} +\usepackage[T1]{fontenc} +\renewcommand\familydefault{\sfdefault} + +\begin{document} + +\section*{|~ recipe.information.title ~|} + +|~ recipe.information.description ~| + +\subsection*{Notes} + +|~ recipe.information.notes|replace("\n", "\n\n") ~| + +\subsection*{Ingredients} + +\renewcommand{\arraystretch}{1.2} +\begin{tabular}{lllllll} + & \textbf{Bakers \%} & \textbf{500g total} & \textbf{1kg total} & \textbf{2kg total} & \textbf{3kg total} & \textbf{4kg total} \\ + \hline + |% for name, weight in recipe.ingredients.items() %| + |~ name|replace("-", " ")|title ~| & + |~ weight ~|\% & + + |% macro weighted(multiplier) %| + |% set rounded = (weight * recipe.kg * multiplier)|round|int %| + |% set real = (weight * recipe.kg * multiplier)|round(2) %| + |% set error = (real - rounded)|abs %| + |% if error < real * 0.05 %| + |~ rounded ~| + |% else %| + |~ real ~| + |% endif %| + |% endmacro %| + + |~ weighted(0.5) ~| & + |~ weighted(1) ~| & + |~ weighted(2) ~| & + |~ weighted(3) ~| & + |~ weighted(4) ~| \\ + |% endfor %| +\end{tabular} + +\end{document} -- 2.39.2