commit f6d33add17c621f4c9e876069e7f9ea549c7b59a Author: Emi Vasilek Date: Wed Nov 1 19:36:58 2023 +0100 initial commit diff --git a/recipes.py b/recipes.py new file mode 100644 index 0000000..55ed303 --- /dev/null +++ b/recipes.py @@ -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() diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..52b1116 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,14 @@ + + + + Recipes + + + + +

Recipes

+ {% for recipe in recipes %} +
  • {{ recipe.title }}
  • + {% endfor %} + + \ No newline at end of file diff --git a/templates/recipe.html b/templates/recipe.html new file mode 100644 index 0000000..5bfcd9e --- /dev/null +++ b/templates/recipe.html @@ -0,0 +1,19 @@ + + + + {{title}} + + + + +

    {{title}}

    +

    Ingredients

    + {% for ing in ingredients %} +
  • {{ing.amount}} {{ing.unit}} {{ ing.name }}
  • + {% endfor %} +

    Steps

    + {% for step in steps %} +
  • {{ step }}
  • + {% endfor %} + + \ No newline at end of file