restructure parsing, add pricedb support

This commit is contained in:
Emi Vasilek 2023-11-01 23:01:06 +01:00
parent 89cbf282bd
commit da38086e98
2 changed files with 224 additions and 89 deletions

View file

@ -1,10 +1,187 @@
from typing import Dict, List, Any from typing import Dict, List, Any, Optional
from dataclasses import dataclass
import os import os
import yaml import yaml
import jinja2 import jinja2
class Unit:
def __init__(self, name: str) -> None:
self.name = name
class Units:
def __init__(self) -> None:
self.units: List[Unit] = []
def load(self, lst: List[Any]) -> List[str]:
assert_list(lst)
for unit in lst:
assert_type(unit, "", str)
self.units.append(Unit(unit))
return []
def get(self, name: str) -> Optional[Unit]:
for unit in self.units:
if unit.name == name:
return unit
raise RuntimeError(f"unit {name} not found")
class Ingredient:
def load(self, dct: Dict[str, Any]) -> List[str]:
issues = []
issues += assert_dict(dct, ["name"], ["wdid", "pricedb"])
assert_type(dct, "name", str)
self.name = dct["name"]
self.wdid = -1
if "wdid" in dct:
assert_type(dct, "wdid", int)
self.wdid = dct["wdid"]
self.pricedb = None
if "pricedb" in dct:
assert_list(dct["pricedb"])
self.pricedb = PriceDBs()
issues += self.pricedb.load(dct["pricedb"])
return issues
class Ingredients:
def __init__(self) -> None:
self.ingredients: List[Ingredient] = []
def load(self, lst: List[Any]) -> List[str]:
issues = []
assert_list(lst)
for ingdct in lst:
ing = Ingredient()
issues += ing.load(ingdct)
self.ingredients.append(ing)
return issues
def get(self, name: str) -> Optional[Ingredient]:
for ing in self.ingredients:
if ing.name == name:
return ing
raise RuntimeError(f"ingredient {name} not found")
class PriceDBs:
def __init__(self) -> None:
self.pricedbs: List[PriceDB] = []
def load(self, lst: List[Any]) -> List[str]:
issues = []
assert_list(lst)
for elem in lst:
pricedb = PriceDB()
issues += pricedb.load(elem)
self.pricedbs.append(pricedb)
return issues
class PriceDB:
def load(self, dct: Dict[str, Any]) -> List[str]:
issues = []
issues += assert_dict(dct, ["price"], ["amount", "unit"])
if isinstance(dct["price"], float):
self.price = dct["price"]
elif isinstance(dct["price"], int):
self.price = float(dct["price"])
else:
raise RuntimeError(f"{dct['price']} has to be int or float")
self.amount = 1.0
if "amount" in dct:
if isinstance(dct["amount"], float):
self.amount = dct["amount"]
elif isinstance(dct["amount"], int):
self.amount = float(dct["amount"])
else:
raise RuntimeError(f"{dct['amount']} has to be int or float")
self.unit = None
if "unit" in dct:
assert_type(dct, "unit", str)
self.unit = units.get(dct["unit"])
return issues
class IngredientInstance:
def load(self, dct: Dict[str, Any]) -> List[str]:
issues = []
issues += assert_dict(dct, ["name"], ["amount", "unit", "note"])
assert_type(dct, "name", str)
self.name = dct["name"]
self.ingredient = ingredients.get(self.name)
self.amount = 1.0
if "amount" in dct:
if isinstance(dct["amount"], float):
self.amount = dct["amount"]
elif isinstance(dct["amount"], int):
self.amount = float(dct["amount"])
else:
raise RuntimeError(f"{dct['amount']} has to be int or float")
self.unit = None
if "unit" in dct:
assert_type(dct, "unit", str)
self.unit = units.get(dct["unit"])
self.note = ""
if "note" in dct:
assert_type(dct, "note", str)
self.note = dct["note"]
return issues
class RecipePart:
def load(self, dct: Dict[str, Any]) -> List[str]:
issues = []
issues += assert_dict(dct, ["title", "ingredients", "steps"], [])
assert_type(dct, "title", str)
self.title = dct["title"]
assert_type(dct, "ingredients", list)
self.ingredients: List[IngredientInstance] = []
for ing in dct["ingredients"]:
ingredient = IngredientInstance()
issues += ingredient.load(ing)
self.ingredients.append(ingredient)
assert_type(dct, "steps", list)
for elem in dct["steps"]:
assert_type(elem, "", str)
self.steps: List[str] = dct["steps"]
return issues
class Recipe:
def __init__(self) -> None:
self.parts: List[RecipePart] = []
self.srcpath = ""
self.outpath = ""
self.title = ""
def load(self, dct: Dict[str, Any]) -> List[str]:
rp = RecipePart()
issues = rp.load(dct)
self.parts = [rp]
self.title = rp.title
return issues
def load_file(file: str) -> Any: def load_file(file: str) -> Any:
print(f"loading {file}") print(f"loading {file}")
with open(file, encoding="utf-8") as f: with open(file, encoding="utf-8") as f:
@ -27,86 +204,17 @@ def assert_dict(
return issues return issues
def assert_type(dct: Dict[str, Any], key: str, type: type) -> List[str]: def assert_type(dct: Dict[str, Any], key: str, type: type) -> None:
if key == "": if key == "":
if not isinstance(dct, type): if not isinstance(dct, type):
return [f"{key} has to be a {type}"] raise RuntimeError(f"{key} has to be a {type}")
elif key in dct and not isinstance(dct[key], type): elif key in dct and not isinstance(dct[key], type):
return [f"{key} has to be a {type}"] raise RuntimeError(f"{key} has to be a {type}")
return []
def validate_units(units: List[Any]) -> List[str]: def assert_list(lst: List[Any]) -> None:
if not isinstance(units, list): if not isinstance(lst, list):
raise RuntimeError(f"{units} has to be a list") raise RuntimeError(f"{lst} 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( def rendertemplate(
@ -129,20 +237,45 @@ def rendertemplate(
f.write(outstr) f.write(outstr)
def main() -> None: units = Units()
issues = [] ingredients = Ingredients()
units = load_file("recipes/units.yaml")
issues += validate_units(units)
ingredients = load_file("recipes/ingredients.yaml")
issues += validate_ingredients(ingredients) def main() -> None:
issues: List[str] = []
unitsdct = load_file("recipes/units.yaml")
issues += units.load(unitsdct)
ingredientsdct = load_file("recipes/ingredients.yaml")
issues += ingredients.load(ingredientsdct)
if len(issues) != 0: if len(issues) != 0:
for issue in issues: for issue in issues:
print("ERROR", issue) print("ERROR", issue)
return return
recipes = get_recipes(units, ingredients) files = []
for _, _, filesx in os.walk("recipes/recipes"):
files = filesx
files.sort()
recipes: List[Recipe] = []
for file in files:
if not file.endswith(".yaml"):
print(f"unknown extension of {file}")
continue
recipe = Recipe()
recipedct = load_file("recipes/recipes/" + file)
issues += recipe.load(recipedct)
recipe.srcpath = file
recipe.outpath = file[:-5] + ".html"
recipes.append(recipe)
if len(issues) != 0:
for issue in issues:
print("ERROR", issue)
return
env = jinja2.Environment( env = jinja2.Environment(
loader=jinja2.FileSystemLoader("templates"), loader=jinja2.FileSystemLoader("templates"),
@ -154,7 +287,7 @@ def main() -> None:
recipetemplate = env.get_template("recipe.html") recipetemplate = env.get_template("recipe.html")
for recipe in recipes: for recipe in recipes:
rendertemplate(recipetemplate, "html", recipe["outpath"], recipe) rendertemplate(recipetemplate, "html", recipe.outpath, {"recipe": recipe})
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -1,19 +1,21 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>{{title}}</title> <title>{{recipe.title}}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<meta charset="utf-8"> <meta charset="utf-8">
</head> </head>
<body> <body>
<h1>{{title}}</h1> <h1>{{recipe.title}}</h1>
{% for part in recipe.parts %}
<h2>Ingredients</h2> <h2>Ingredients</h2>
{% for ing in ingredients %} {% for ing in part.ingredients %}
<li>{{ing.amount}} {{ing.unit}} {{ ing.name }}</li> <li>{{ing.amount}} {{ing.unit.name}} {{ ing.name }}</li>
{% endfor %} {% endfor %}
<h2>Steps</h2> <h2>Steps</h2>
{% for step in steps %} {% for step in part.steps %}
<li>{{ step }}</li> <li>{{ step }}</li>
{% endfor %} {% endfor %}
{% endfor %}
</body> </body>
</html> </html>