comfy-recipes/recipes.py

303 lines
8.5 KiB
Python

from typing import Dict, List, Any, Optional
from dataclasses import dataclass
import os
import yaml
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)
try:
self.unit = units.get(dct["unit"])
except RuntimeError as e:
issues.append(str(e))
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"]
try:
self.ingredient = ingredients.get(self.name)
except RuntimeError as e:
issues.append(str(e))
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)
try:
self.unit = units.get(dct["unit"])
except RuntimeError as e:
issues.append(str(e))
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:
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) -> None:
if key == "":
if not isinstance(dct, type):
raise RuntimeError(f"{key} has to be a {type}")
elif key in dct and not isinstance(dct[key], type):
raise RuntimeError(f"{key} has to be a {type}")
def assert_list(lst: List[Any]) -> None:
if not isinstance(lst, list):
raise RuntimeError(f"{lst} has to be a {list}")
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)
units = Units()
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:
for issue in issues:
print("ERROR", issue)
return
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(
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": recipe})
if __name__ == "__main__":
main()