This commit is contained in:
Emi Vasilek 2023-11-06 13:10:49 +01:00
parent 353aee80cb
commit 347231f013
2 changed files with 135 additions and 175 deletions

View file

@ -1,3 +1,4 @@
from abc import abstractmethod
import sys import sys
from typing import Dict, List, Any, Optional, Set from typing import Dict, List, Any, Optional, Set
import os import os
@ -11,103 +12,111 @@ import jsonschema.exceptions
class Context: class Context:
def __init__(self) -> None: def __init__(self) -> None:
self.units = Units() self.units = Units(self)
self.ingredients = Ingredients(self) self.ingredients = Ingredients(self)
self.issues: List[str] = []
class Conversion: class Element:
def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None: 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]: def find_unit(name: str) -> Optional[Unit]:
for unit in units: for unit in self.ctx.units.units:
if unit.name == name: if unit["name"] == name:
return unit return unit
return None return None
fromunit = find_unit(dct["from"]) fromunit = find_unit(dct["from"])
if fromunit is None: if fromunit is None:
raise RuntimeError(f"unit {dct['from']} doesn't exist") raise RuntimeError(f"unit {fromunit} doesn't exist")
self.fromunit = fromunit self["from"] = fromunit
tounit = find_unit(dct["to"]) tounit = find_unit(dct["to"])
if tounit is None: if tounit is None:
raise RuntimeError(f"unit {tounit} doesn't exist") raise RuntimeError(f"unit {tounit} doesn't exist")
self.tounit = tounit self["to"] = tounit
self.ratio = dct["ratio"]
class Unit: class Unit(Element):
def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
self.name = dct["name"] oldunits = self.ctx.units.units[:]
self.ctx.units.units.append(self)
unitsx = units[:] conversions: List[Conversion] = []
unitsx.append(self)
self.conversions: List[Conversion] = []
if "conversions" in dct: if "conversions" in dct:
for convdct in dct["conversions"]: for convdct in dct["conversions"]:
if "from" in dct["conversions"]: if "from" in dct["conversions"]:
raise RuntimeError( raise RuntimeError(
f"conversions in units.yaml cannot have a from field, it is automatically assigned from the unit name" f"conversions in units.yaml cannot have a from field, it is automatically assigned from the unit name"
) )
convdct["from"] = self.name convdct["from"] = self["name"]
conversion = Conversion() conversion = Conversion(self.ctx, convdct)
conversion.load(unitsx, convdct) conversions.append(conversion)
self.conversions.append(conversion) self["conversions"] = conversions
self.aliases: List[str] = [] aliases: List[str] = []
if "aliases" in dct: if "aliases" in dct:
for alias in dct["aliases"]: for alias in dct["aliases"]:
self.aliases.append(alias) aliases.append(alias)
self["aliases"] = aliases
self.ctx.units.units = oldunits
class Units: class Units:
def __init__(self) -> None: def __init__(self, ctx: Context) -> None:
self.ctx = ctx
self.units: List[Unit] = [] self.units: List[Unit] = []
def load(self, lst: List[Any]) -> List[str]: def load(self, lst: List[Any]) -> None:
for unitdct in lst: for unitdct in lst:
unit = Unit() unit = Unit(self.ctx, unitdct)
unit.load(self.units, unitdct)
self.units.append(unit) self.units.append(unit)
return []
def get(self, name: str) -> Optional[Unit]: def get(self, name: str) -> Optional[Unit]:
for unit in self.units: for unit in self.units:
if unit.name == name: if unit["name"] == name:
return unit return unit
raise RuntimeError(f"unit {name} not found") raise RuntimeError(f"unit {name} not found")
class Ingredient: class Ingredient(Element):
def __init__(self, ctx: Context) -> None: def load(self, dct: Dict[str, Any]) -> 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
if "pricedb" in dct: if "pricedb" in dct:
self.pricedb = PriceDBs(self.ctx) pricedb = PriceDBs(self.ctx)
issues += self.pricedb.load(dct["pricedb"]) self.ctx.issues += pricedb.load(dct["pricedb"])
self["pricedb"] = pricedb
self.aliases = [] conversions = []
if "aliases" in dct:
for elem in dct["aliases"]:
self.aliases.append(elem)
self.conversions = []
if "conversions" in dct: if "conversions" in dct:
for dct in dct["conversions"]: for convdct in dct["conversions"]:
conversion = Conversion() conversion = Conversion(self.ctx, convdct)
conversion.load(self.ctx.units.units, dct) conversions.append(conversion)
self.conversions.append(conversion) self["conversions"] = conversions
return issues
class Ingredients: class Ingredients:
@ -115,17 +124,14 @@ class Ingredients:
self.ctx = ctx self.ctx = ctx
self.ingredients: List[Ingredient] = [] self.ingredients: List[Ingredient] = []
def load(self, lst: List[Any]) -> List[str]: def load(self, lst: List[Any]) -> None:
issues = []
for ingdct in lst: for ingdct in lst:
ing = Ingredient(self.ctx) ing = Ingredient(self.ctx, ingdct)
issues += ing.load(ingdct)
self.ingredients.append(ing) self.ingredients.append(ing)
return issues
def get(self, name: str) -> Optional[Ingredient]: def get(self, name: str) -> Optional[Ingredient]:
for ing in self.ingredients: 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 return ing
raise RuntimeError(f"ingredient {name} not found") raise RuntimeError(f"ingredient {name} not found")
@ -136,120 +142,74 @@ class PriceDBs:
self.pricedbs: List[PriceDB] = [] self.pricedbs: List[PriceDB] = []
def load(self, lst: List[Any]) -> List[str]: def load(self, lst: List[Any]) -> List[str]:
issues = []
for elem in lst: for elem in lst:
pricedb = PriceDB(self.ctx) pricedb = PriceDB(self.ctx, elem)
issues += pricedb.load(elem)
self.pricedbs.append(pricedb) self.pricedbs.append(pricedb)
return issues return self.ctx.issues
class PriceDB: class PriceDB(Element):
def __init__(self, ctx: Context) -> None: def load(self, dct: Dict[str, Any]) -> None:
self.ctx = ctx 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: if "unit" in dct:
try: try:
self.unit = self.ctx.units.get(dct["unit"]) self["unit"] = self.ctx.units.get(dct["unit"])
except RuntimeError as e: except RuntimeError as e:
issues.append(str(e)) self.ctx.issues.append(str(e))
return issues else:
self["unit"] = None
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"]
class IngredientInstance(Element):
def load(self, dct: Dict[str, Any]) -> None:
try: try:
self.ingredient = self.ctx.ingredients.get(self.name) self["ingredient"] = self.ctx.ingredients.get(dct["name"])
except RuntimeError as e: except RuntimeError as e:
issues.append(str(e)) self.ctx.issues.append(str(e))
self.amount = self.defaultamount if "amount" not in dct:
if "amount" in dct: self["amount"] = 1.0
self.amount = dct["amount"]
self.unit = self.defaultunit
if "unit" in dct: if "unit" in dct:
try: try:
self.unit = self.ctx.units.get(dct["unit"]) self["unit"] = self.ctx.units.get(dct["unit"])
except RuntimeError as e: except RuntimeError as e:
issues.append(str(e)) self.ctx.issues.append(str(e))
self.note = "" else:
if "note" in dct: self["unit"] = None
self.note = dct["note"]
self.alternatives = [] if "note" not in dct:
self["note"] = ""
alternatives = []
if "or" in dct: if "or" in dct:
for ingdct in dct["or"]: for ingdct in dct["or"]:
ingredient = IngredientInstance( ingredient = IngredientInstance(self.ctx, ingdct)
self.ctx, defaultamount=self.amount, defaultunit=self.unit alternatives.append(ingredient)
) self["alternatives"] = alternatives
ingredient.load(ingdct)
self.alternatives.append(ingredient)
return issues
class Subrecipe: class Recipe(Element):
def __init__(self, ctx: Context) -> None: def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None:
self.ctx = ctx super().__init__(ctx, dct)
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] = []
self.srcpath = "" self.srcpath = ""
self.outpath = "" self.outpath = ""
self.title = ""
def load(self, dct: Dict[str, Any]) -> List[str]: def load(self, dct: Dict[str, Any]) -> None:
issues: List[str] = [] 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: if "subrecipes" in dct:
self.title = dct["title"]
for partdct in dct["subrecipes"]: for partdct in dct["subrecipes"]:
rp = Subrecipe(self.ctx) rp = Recipe(self.ctx, partdct)
issues += rp.load(partdct) subrecipes.append(rp)
self.subrecipes.append(rp) self["subrecipes"] = subrecipes
else:
rp = Subrecipe(self.ctx)
issues = rp.load(dct)
self.subrecipes = [rp]
self.title = rp.title
return issues
class Builder: class Builder:
@ -298,30 +258,27 @@ class Builder:
self.outfiles.add(file) self.outfiles.add(file)
def load(self, dir: str) -> int: def load(self, dir: str) -> int:
issues: List[str] = []
unitsdct = self.load_file(dir + "/units.yaml") unitsdct = self.load_file(dir + "/units.yaml")
unitsschema = self.load_file("schemas/units.json") unitsschema = self.load_file("schemas/units.json")
jsonschema.validate(instance=unitsdct, schema=unitsschema) jsonschema.validate(instance=unitsdct, schema=unitsschema)
issues += self.ctx.units.load(unitsdct) self.ctx.units.load(unitsdct)
if len(issues) != 0: if len(self.ctx.issues) != 0:
for issue in issues: for issue in self.ctx.issues:
print("ERROR in units.yaml:", issue) print("ERROR in units.yaml:", issue)
return 1 return 1
ingredientsdct = self.load_file(dir + "/ingredients.yaml") ingredientsdct = self.load_file(dir + "/ingredients.yaml")
ingredientsschema = self.load_file("schemas/ingredients.json") ingredientsschema = self.load_file("schemas/ingredients.json")
jsonschema.validate(instance=ingredientsdct, schema=ingredientsschema) jsonschema.validate(instance=ingredientsdct, schema=ingredientsschema)
issues += self.ctx.ingredients.load(ingredientsdct) self.ctx.ingredients.load(ingredientsdct)
if len(issues) != 0: if len(self.ctx.issues) != 0:
for issue in issues: for issue in self.ctx.issues:
print("ERROR in ingredients.yaml:", issue) print("ERROR in ingredients.yaml:", issue)
return 1 return 1
return 0 return 0
def run(self, dir: str) -> int: def run(self, dir: str) -> int:
issues = []
files = [] files = []
for _, _, filesx in os.walk(dir + "/recipes"): for _, _, filesx in os.walk(dir + "/recipes"):
files = filesx files = filesx
@ -333,16 +290,15 @@ class Builder:
if not file.endswith(".yaml"): if not file.endswith(".yaml"):
print(f"unknown extension of {file}") print(f"unknown extension of {file}")
continue continue
recipe = Recipe(self.ctx)
recipedct = self.load_file(dir + "/recipes/" + file) recipedct = self.load_file(dir + "/recipes/" + file)
jsonschema.validate(instance=recipedct, schema=recipeschema) jsonschema.validate(instance=recipedct, schema=recipeschema)
issues += recipe.load(recipedct) recipe = Recipe(self.ctx, recipedct)
recipe.srcpath = file recipe.srcpath = file
recipe.outpath = file[:-5] + ".html" recipe.outpath = file[:-5] + ".html"
recipes.append(recipe) recipes.append(recipe)
if len(issues) != 0: if len(self.ctx.issues) != 0:
for issue in issues: for issue in self.ctx.issues:
print("ERROR", issue) print("ERROR", issue)
return 1 return 1
@ -359,7 +315,7 @@ class Builder:
format="html", format="html",
file=recipe.outpath, file=recipe.outpath,
dir=dir, dir=dir,
args={"recipe": recipe} args={"recipe": recipe},
) )
return 0 return 0

View file

@ -1,7 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% macro ingredientpart(ing) -%} {% macro ingredientpart(ing) -%}
{{ing.amount|numprint}} {{ing.unit.name}} {{ ing.name }} {{ing.amount|numprint}} {{ing["unit"].name}} {{ ing.name }}
{%- endmacro %} {%- endmacro %}
{% macro ingredient(ing) -%} {% macro ingredient(ing) -%}
{{ingredientpart(ing)}} {{ingredientpart(ing)}}
{% if ing.alternatives|length != 0 %} {% if ing.alternatives|length != 0 %}
@ -10,22 +11,25 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}
{%block title%}{{recipe.title}}{%endblock%}
{%block body %} {% macro getrecipe(rec) -%}
<a href="index.html">back</a> {% for subrecipe in rec.subrecipes %}
<h1>{{recipe.title}}</h1> {{getrecipe(subrecipe)}}
{% for subrecipe in recipe.subrecipes %} {% endfor %}
{% if recipe.subrecipes|length != 1 %} <h1>{{rec.title}}</h1>
<h2>{{subrecipe.title}}</h2>
{% endif %}
<h3>Ingredients</h3> <h3>Ingredients</h3>
{% for ing in subrecipe.ingredients %} {% for ing in rec.ingredients %}
<li>{{ingredient(ing)}}</li> <li>{{ingredient(ing)}}</li>
{% endfor %} {% endfor %}
<h3>Steps</h3> <h3>Steps</h3>
{% for step in subrecipe.steps %} {% for step in rec.steps %}
<li>{{ step }}</li> <li>{{ step }}</li>
{% endfor %} {% endfor %}
<hr> <hr>
{% endfor %} {%- endmacro %}
{%block title%}{{recipe.title}}{%endblock%}
{%block body %}
<a href="index.html">back</a>
{{getrecipe(recipe)}}
{%endblock%} {%endblock%}