initial commit
This commit is contained in:
commit
f6d33add17
3 changed files with 194 additions and 0 deletions
161
recipes.py
Normal file
161
recipes.py
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
import os
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
|
||||||
|
def load_file(file: str) -> Any:
|
||||||
|
print(f"loading {file}")
|
||||||
|
with open(file, encoding="utf-8") as f:
|
||||||
|
ymltxt = f.read()
|
||||||
|
return yaml.safe_load(ymltxt)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_dict(
|
||||||
|
dct: Dict[str, Any], required_keys: List[str], optional_keys: List[str]
|
||||||
|
) -> List[str]:
|
||||||
|
issues = []
|
||||||
|
if not isinstance(dct, dict):
|
||||||
|
raise RuntimeError(f"{dct} has to be a dict")
|
||||||
|
for reqkey in required_keys:
|
||||||
|
if reqkey not in dct:
|
||||||
|
issues.append(f"{reqkey} is required")
|
||||||
|
extraelems = [x for x in dct.keys() if x not in required_keys + optional_keys]
|
||||||
|
if len(extraelems) != 0:
|
||||||
|
issues.append(f"{extraelems} not allowed")
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def assert_type(dct: Dict[str, Any], key: str, type: type) -> List[str]:
|
||||||
|
if key == "":
|
||||||
|
if not isinstance(dct, type):
|
||||||
|
return [f"{key} has to be a {type}"]
|
||||||
|
elif key in dct and not isinstance(dct[key], type):
|
||||||
|
return [f"{key} has to be a {type}"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def validate_units(units: List[Any]) -> List[str]:
|
||||||
|
if not isinstance(units, list):
|
||||||
|
raise RuntimeError(f"{units} has to be a list")
|
||||||
|
issues = []
|
||||||
|
for unit in units:
|
||||||
|
issues += assert_type(unit, "", str)
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ingredients(ingredients: Any) -> List[str]:
|
||||||
|
if not isinstance(ingredients, list):
|
||||||
|
raise RuntimeError(f"{ingredients} has to be a list")
|
||||||
|
issues = []
|
||||||
|
for ing in ingredients:
|
||||||
|
issues += assert_dict(ing, ["name"], ["wdid"])
|
||||||
|
issues += assert_type(ing, "name", str)
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def validate_recipe(recipe: Any, units: List[Any], ingredients: List[Any]) -> List[str]:
|
||||||
|
issues = []
|
||||||
|
issues += assert_dict(recipe, ["title", "ingredients", "steps"], [])
|
||||||
|
|
||||||
|
issues += assert_type(recipe, "title", str)
|
||||||
|
if not isinstance(recipe["ingredients"], list):
|
||||||
|
raise RuntimeError(f'{recipe["ingredients"]} has to be a list')
|
||||||
|
if not isinstance(recipe["steps"], list):
|
||||||
|
raise RuntimeError(f'{recipe["steps"]} has to be a list')
|
||||||
|
|
||||||
|
for ingredient in recipe["ingredients"]:
|
||||||
|
issues += assert_dict(ingredient, ["name"], ["amount", "unit", "note"])
|
||||||
|
issues += assert_type(ingredient, "name", str)
|
||||||
|
if (
|
||||||
|
"amount" in ingredient
|
||||||
|
and not isinstance(ingredient["amount"], int)
|
||||||
|
and not isinstance(ingredient["amount"], float)
|
||||||
|
):
|
||||||
|
issues.append("each ingredient has to have a int or float amount")
|
||||||
|
issues += assert_type(ingredient, "unit", str)
|
||||||
|
issues += assert_type(ingredient, "note", str)
|
||||||
|
if ingredient["name"] not in [ing["name"] for ing in ingredients]:
|
||||||
|
issues.append(f"unknown ingredient {ingredient['name']}")
|
||||||
|
|
||||||
|
for step in recipe["steps"]:
|
||||||
|
if not isinstance(step, str):
|
||||||
|
issues.append("each step has to be a string")
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def get_recipes(units: List[Any], ingredients: List[Any]) -> List[Any]:
|
||||||
|
recipes = []
|
||||||
|
for _, _, files in os.walk("recipes/recipes"):
|
||||||
|
files.sort()
|
||||||
|
for file in files:
|
||||||
|
recipe = load_file("recipes/recipes/" + file)
|
||||||
|
try:
|
||||||
|
issues = validate_recipe(recipe, units, ingredients)
|
||||||
|
except RuntimeError as e:
|
||||||
|
print("WARNING:", e, "skipping")
|
||||||
|
continue
|
||||||
|
if len(issues) != 0:
|
||||||
|
for issue in issues:
|
||||||
|
print("WARNING:", file, issue, "skipping")
|
||||||
|
continue
|
||||||
|
recipe["srcpath"] = file
|
||||||
|
if not file.endswith(".yaml"):
|
||||||
|
print(f"unknown extension of {file}")
|
||||||
|
continue
|
||||||
|
recipe["outpath"] = file[:-5] + ".html"
|
||||||
|
recipes.append(recipe)
|
||||||
|
return recipes
|
||||||
|
|
||||||
|
|
||||||
|
def rendertemplate(
|
||||||
|
template: jinja2.Template, format: str, file: str, args: Any
|
||||||
|
) -> None:
|
||||||
|
print(f"rendering {file}")
|
||||||
|
outstr = template.render(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir("out")
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(f"out/{format}")
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with open(f"out/{format}/{file}", "w", encoding="utf-8") as f:
|
||||||
|
f.write(outstr)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
issues = []
|
||||||
|
units = load_file("recipes/units.yaml")
|
||||||
|
issues += validate_units(units)
|
||||||
|
|
||||||
|
ingredients = load_file("recipes/ingredients.yaml")
|
||||||
|
issues += validate_ingredients(ingredients)
|
||||||
|
|
||||||
|
if len(issues) != 0:
|
||||||
|
for issue in issues:
|
||||||
|
print("ERROR", issue)
|
||||||
|
return
|
||||||
|
|
||||||
|
recipes = get_recipes(units, ingredients)
|
||||||
|
|
||||||
|
env = jinja2.Environment(
|
||||||
|
loader=jinja2.FileSystemLoader("templates"),
|
||||||
|
autoescape=jinja2.select_autoescape(),
|
||||||
|
)
|
||||||
|
|
||||||
|
indextemplate = env.get_template("index.html")
|
||||||
|
rendertemplate(indextemplate, "html", "index.html", {"recipes": recipes})
|
||||||
|
|
||||||
|
recipetemplate = env.get_template("recipe.html")
|
||||||
|
for recipe in recipes:
|
||||||
|
rendertemplate(recipetemplate, "html", recipe["outpath"], recipe)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
14
templates/index.html
Normal file
14
templates/index.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Recipes</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Recipes</h1>
|
||||||
|
{% for recipe in recipes %}
|
||||||
|
<li><a href="{{ recipe.outpath }}">{{ recipe.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
19
templates/recipe.html
Normal file
19
templates/recipe.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{{title}}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<h2>Ingredients</h2>
|
||||||
|
{% for ing in ingredients %}
|
||||||
|
<li>{{ing.amount}} {{ing.unit}} {{ ing.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
<h2>Steps</h2>
|
||||||
|
{% for step in steps %}
|
||||||
|
<li>{{ step }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue