diff --git a/recipes.py b/recipes.py index 2f70ed5..7daf5d0 100644 --- a/recipes.py +++ b/recipes.py @@ -1,3 +1,4 @@ +from abc import abstractmethod import sys from typing import Dict, List, Any, Optional, Set import os @@ -11,103 +12,111 @@ import jsonschema.exceptions class Context: def __init__(self) -> None: - self.units = Units() + self.units = Units(self) self.ingredients = Ingredients(self) + self.issues: List[str] = [] -class Conversion: - def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None: +class Element: + def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None: + self.ctx = ctx + self.dct = dct + self.load(dct) + for elem in self.dct.values(): + if isinstance(elem, dict): + raise RuntimeError("Something wasn't processed properly") + + def __contains__(self, item: Any) -> bool: + return item in self.dct + + def __getitem__(self, key: str) -> Any: + return self.dct[key] + + def __setitem__(self, key: str, val: Any) -> None: + self.dct[key] = val + + def __repr__(self) -> str: + return self.dct.__repr__() + + @abstractmethod + def load(self, dct: Dict[str, Any]) -> None: + ... + + +class Conversion(Element): + def load(self, dct: Dict[str, Any]) -> None: def find_unit(name: str) -> Optional[Unit]: - for unit in units: - if unit.name == name: + for unit in self.ctx.units.units: + if unit["name"] == name: return unit return None fromunit = find_unit(dct["from"]) if fromunit is None: - raise RuntimeError(f"unit {dct['from']} doesn't exist") - self.fromunit = fromunit + raise RuntimeError(f"unit {fromunit} doesn't exist") + self["from"] = fromunit tounit = find_unit(dct["to"]) if tounit is None: raise RuntimeError(f"unit {tounit} doesn't exist") - self.tounit = tounit - - self.ratio = dct["ratio"] + self["to"] = tounit -class Unit: - def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None: - self.name = dct["name"] +class Unit(Element): + def load(self, dct: Dict[str, Any]) -> None: + oldunits = self.ctx.units.units[:] + self.ctx.units.units.append(self) - unitsx = units[:] - unitsx.append(self) - - self.conversions: List[Conversion] = [] + conversions: List[Conversion] = [] if "conversions" in dct: for convdct in dct["conversions"]: if "from" in dct["conversions"]: raise RuntimeError( f"conversions in units.yaml cannot have a from field, it is automatically assigned from the unit name" ) - convdct["from"] = self.name - conversion = Conversion() - conversion.load(unitsx, convdct) - self.conversions.append(conversion) + convdct["from"] = self["name"] + conversion = Conversion(self.ctx, convdct) + conversions.append(conversion) + self["conversions"] = conversions - self.aliases: List[str] = [] + aliases: List[str] = [] if "aliases" in dct: for alias in dct["aliases"]: - self.aliases.append(alias) + aliases.append(alias) + self["aliases"] = aliases + self.ctx.units.units = oldunits class Units: - def __init__(self) -> None: + def __init__(self, ctx: Context) -> None: + self.ctx = ctx self.units: List[Unit] = [] - def load(self, lst: List[Any]) -> List[str]: + def load(self, lst: List[Any]) -> None: for unitdct in lst: - unit = Unit() - unit.load(self.units, unitdct) + unit = Unit(self.ctx, unitdct) self.units.append(unit) - return [] def get(self, name: str) -> Optional[Unit]: for unit in self.units: - if unit.name == name: + if unit["name"] == name: return unit raise RuntimeError(f"unit {name} not found") -class Ingredient: - def __init__(self, ctx: Context) -> None: - self.ctx = ctx - - def load(self, dct: Dict[str, Any]) -> List[str]: - issues = [] - self.name = dct["name"] - - self.wdid = -1 - if "wdid" in dct: - self.wdid = dct["wdid"] - - self.pricedb = None +class Ingredient(Element): + def load(self, dct: Dict[str, Any]) -> None: if "pricedb" in dct: - self.pricedb = PriceDBs(self.ctx) - issues += self.pricedb.load(dct["pricedb"]) + pricedb = PriceDBs(self.ctx) + self.ctx.issues += pricedb.load(dct["pricedb"]) + self["pricedb"] = pricedb - self.aliases = [] - if "aliases" in dct: - for elem in dct["aliases"]: - self.aliases.append(elem) - - self.conversions = [] + conversions = [] if "conversions" in dct: - for dct in dct["conversions"]: - conversion = Conversion() - conversion.load(self.ctx.units.units, dct) - self.conversions.append(conversion) - return issues + for convdct in dct["conversions"]: + conversion = Conversion(self.ctx, convdct) + conversions.append(conversion) + self["conversions"] = conversions class Ingredients: @@ -115,17 +124,14 @@ class Ingredients: self.ctx = ctx self.ingredients: List[Ingredient] = [] - def load(self, lst: List[Any]) -> List[str]: - issues = [] + def load(self, lst: List[Any]) -> None: for ingdct in lst: - ing = Ingredient(self.ctx) - issues += ing.load(ingdct) + ing = Ingredient(self.ctx, ingdct) self.ingredients.append(ing) - return issues def get(self, name: str) -> Optional[Ingredient]: for ing in self.ingredients: - if ing.name == name or name in ing.aliases: + if ing["name"] == name or "aliases" in ing and name in ing["aliases"]: return ing raise RuntimeError(f"ingredient {name} not found") @@ -136,120 +142,74 @@ class PriceDBs: self.pricedbs: List[PriceDB] = [] def load(self, lst: List[Any]) -> List[str]: - issues = [] for elem in lst: - pricedb = PriceDB(self.ctx) - issues += pricedb.load(elem) + pricedb = PriceDB(self.ctx, elem) self.pricedbs.append(pricedb) - return issues + return self.ctx.issues -class PriceDB: - def __init__(self, ctx: Context) -> None: - self.ctx = ctx +class PriceDB(Element): + def load(self, dct: Dict[str, Any]) -> None: + if "amount" not in dct: + self["amount"] = 1.0 - def load(self, dct: Dict[str, Any]) -> List[str]: - issues = [] - self.price = dct["price"] - - self.amount = 1.0 - if "amount" in dct: - self.amount = dct["amount"] - - self.unit = None if "unit" in dct: try: - self.unit = self.ctx.units.get(dct["unit"]) + self["unit"] = self.ctx.units.get(dct["unit"]) except RuntimeError as e: - issues.append(str(e)) - return issues - - -class IngredientInstance: - def __init__( - self, ctx: Context, defaultamount: float = 1, defaultunit: Optional[Unit] = None - ) -> None: - self.ctx = ctx - self.defaultamount = float(defaultamount) - self.defaultunit: Optional[Unit] = defaultunit - - def load(self, dct: Dict[str, Any]) -> List[str]: - issues = [] - self.name = dct["name"] + self.ctx.issues.append(str(e)) + else: + self["unit"] = None +class IngredientInstance(Element): + def load(self, dct: Dict[str, Any]) -> None: try: - self.ingredient = self.ctx.ingredients.get(self.name) + self["ingredient"] = self.ctx.ingredients.get(dct["name"]) except RuntimeError as e: - issues.append(str(e)) + self.ctx.issues.append(str(e)) - self.amount = self.defaultamount - if "amount" in dct: - self.amount = dct["amount"] + if "amount" not in dct: + self["amount"] = 1.0 - self.unit = self.defaultunit if "unit" in dct: try: - self.unit = self.ctx.units.get(dct["unit"]) + self["unit"] = self.ctx.units.get(dct["unit"]) except RuntimeError as e: - issues.append(str(e)) - self.note = "" - if "note" in dct: - self.note = dct["note"] + self.ctx.issues.append(str(e)) + else: + self["unit"] = None - self.alternatives = [] + if "note" not in dct: + self["note"] = "" + + alternatives = [] if "or" in dct: for ingdct in dct["or"]: - ingredient = IngredientInstance( - self.ctx, defaultamount=self.amount, defaultunit=self.unit - ) - ingredient.load(ingdct) - self.alternatives.append(ingredient) - - return issues + ingredient = IngredientInstance(self.ctx, ingdct) + alternatives.append(ingredient) + self["alternatives"] = alternatives -class Subrecipe: - def __init__(self, ctx: Context) -> None: - self.ctx = ctx - - def load(self, dct: Dict[str, Any]) -> List[str]: - issues = [] - self.title = dct["title"] - - self.ingredients: List[IngredientInstance] = [] - for ing in dct["ingredients"]: - ingredient = IngredientInstance(self.ctx) - issues += ingredient.load(ing) - self.ingredients.append(ingredient) - - self.steps: List[str] = dct["steps"] - - return issues - - -class Recipe: - def __init__(self, ctx: Context) -> None: - self.ctx = ctx - self.subrecipes: List[Subrecipe] = [] +class Recipe(Element): + def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None: + super().__init__(ctx, dct) self.srcpath = "" self.outpath = "" - self.title = "" - def load(self, dct: Dict[str, Any]) -> List[str]: - issues: List[str] = [] + def load(self, dct: Dict[str, Any]) -> None: + ingredients: List[IngredientInstance] = [] + if "ingredients" in dct: + for ing in dct["ingredients"]: + ingredient = IngredientInstance(self.ctx, ing) + ingredients.append(ingredient) + self["ingredients"] = ingredients + + subrecipes: List[Recipe] = [] if "subrecipes" in dct: - self.title = dct["title"] - for partdct in dct["subrecipes"]: - rp = Subrecipe(self.ctx) - issues += rp.load(partdct) - self.subrecipes.append(rp) - else: - rp = Subrecipe(self.ctx) - issues = rp.load(dct) - self.subrecipes = [rp] - self.title = rp.title - return issues + rp = Recipe(self.ctx, partdct) + subrecipes.append(rp) + self["subrecipes"] = subrecipes class Builder: @@ -298,30 +258,27 @@ class Builder: self.outfiles.add(file) def load(self, dir: str) -> int: - issues: List[str] = [] - unitsdct = self.load_file(dir + "/units.yaml") unitsschema = self.load_file("schemas/units.json") jsonschema.validate(instance=unitsdct, schema=unitsschema) - issues += self.ctx.units.load(unitsdct) - if len(issues) != 0: - for issue in issues: + self.ctx.units.load(unitsdct) + if len(self.ctx.issues) != 0: + for issue in self.ctx.issues: print("ERROR in units.yaml:", issue) return 1 ingredientsdct = self.load_file(dir + "/ingredients.yaml") ingredientsschema = self.load_file("schemas/ingredients.json") jsonschema.validate(instance=ingredientsdct, schema=ingredientsschema) - issues += self.ctx.ingredients.load(ingredientsdct) - if len(issues) != 0: - for issue in issues: + self.ctx.ingredients.load(ingredientsdct) + if len(self.ctx.issues) != 0: + for issue in self.ctx.issues: print("ERROR in ingredients.yaml:", issue) return 1 return 0 def run(self, dir: str) -> int: - issues = [] files = [] for _, _, filesx in os.walk(dir + "/recipes"): files = filesx @@ -333,16 +290,15 @@ class Builder: if not file.endswith(".yaml"): print(f"unknown extension of {file}") continue - recipe = Recipe(self.ctx) recipedct = self.load_file(dir + "/recipes/" + file) jsonschema.validate(instance=recipedct, schema=recipeschema) - issues += recipe.load(recipedct) + recipe = Recipe(self.ctx, recipedct) recipe.srcpath = file recipe.outpath = file[:-5] + ".html" recipes.append(recipe) - if len(issues) != 0: - for issue in issues: + if len(self.ctx.issues) != 0: + for issue in self.ctx.issues: print("ERROR", issue) return 1 @@ -359,7 +315,7 @@ class Builder: format="html", file=recipe.outpath, dir=dir, - args={"recipe": recipe} + args={"recipe": recipe}, ) return 0 diff --git a/templates/recipe.html b/templates/recipe.html index 92037f4..fc31df2 100644 --- a/templates/recipe.html +++ b/templates/recipe.html @@ -1,7 +1,8 @@ {% extends "base.html" %} {% macro ingredientpart(ing) -%} - {{ing.amount|numprint}} {{ing.unit.name}} {{ ing.name }} +{{ing.amount|numprint}} {{ing["unit"].name}} {{ ing.name }} {%- endmacro %} + {% macro ingredient(ing) -%} {{ingredientpart(ing)}} {% if ing.alternatives|length != 0 %} @@ -10,22 +11,25 @@ {% endfor %} {% endif %} {%- endmacro %} -{%block title%}{{recipe.title}}{%endblock%} -{%block body %} -back -

{{recipe.title}}

-{% for subrecipe in recipe.subrecipes %} -{% if recipe.subrecipes|length != 1 %} -

{{subrecipe.title}}

-{% endif %} + +{% macro getrecipe(rec) -%} +{% for subrecipe in rec.subrecipes %} +{{getrecipe(subrecipe)}} +{% endfor %} +

{{rec.title}}

Ingredients

-{% for ing in subrecipe.ingredients %} +{% for ing in rec.ingredients %}
  • {{ingredient(ing)}}
  • {% endfor %}

    Steps

    -{% for step in subrecipe.steps %} +{% for step in rec.steps %}
  • {{ step }}
  • {% endfor %}
    -{% endfor %} +{%- endmacro %} + +{%block title%}{{recipe.title}}{%endblock%} +{%block body %} +back +{{getrecipe(recipe)}} {%endblock%} \ No newline at end of file