crlf -> lf

This commit is contained in:
Emi Vasilek 2023-11-06 23:55:13 +01:00
parent 15ce2f152f
commit d42cb1beee
3 changed files with 394 additions and 394 deletions

View file

@ -1,349 +1,349 @@
from abc import abstractmethod 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
import json import json
import yaml import yaml
import jinja2 import jinja2
import jsonschema import jsonschema
import jsonschema.exceptions import jsonschema.exceptions
class Context: class Context:
def __init__(self) -> None: def __init__(self) -> None:
self.units = Units(self) self.units = Units(self)
self.ingredients = Ingredients(self) self.ingredients = Ingredients(self)
self.issues: List[str] = [] self.issues: List[str] = []
class Element: class Element:
def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None: def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None:
self.ctx = ctx self.ctx = ctx
self.dct = dct self.dct = dct
self.load(dct) self.load(dct)
for elem in self.dct.values(): for elem in self.dct.values():
if isinstance(elem, dict): if isinstance(elem, dict):
raise RuntimeError("Something wasn't processed properly") raise RuntimeError("Something wasn't processed properly")
def __contains__(self, item: Any) -> bool: def __contains__(self, item: Any) -> bool:
return item in self.dct return item in self.dct
def __getitem__(self, key: str) -> Any: def __getitem__(self, key: str) -> Any:
return self.dct[key] return self.dct[key]
def __setitem__(self, key: str, val: Any) -> None: def __setitem__(self, key: str, val: Any) -> None:
self.dct[key] = val self.dct[key] = val
def __repr__(self) -> str: def __repr__(self) -> str:
return self.dct.__repr__() return self.dct.__repr__()
@abstractmethod @abstractmethod
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
... ...
class Conversion(Element): class Conversion(Element):
def load(self, dct: Dict[str, Any]) -> None: 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 self.ctx.units.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 {fromunit} doesn't exist") raise RuntimeError(f"unit {fromunit} doesn't exist")
self["from"] = 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["to"] = tounit self["to"] = tounit
class Unit(Element): class Unit(Element):
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
oldunits = self.ctx.units.units[:] oldunits = self.ctx.units.units[:]
self.ctx.units.units.append(self) self.ctx.units.units.append(self)
conversions: List[Conversion] = [] 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(
"conversions in units.yaml cannot have a from field, it is automatically assigned from the unit name" "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(self.ctx, convdct) conversion = Conversion(self.ctx, convdct)
conversions.append(conversion) conversions.append(conversion)
self["conversions"] = conversions self["conversions"] = conversions
aliases: List[str] = [] aliases: List[str] = []
if "aliases" in dct: if "aliases" in dct:
for alias in dct["aliases"]: for alias in dct["aliases"]:
aliases.append(alias) aliases.append(alias)
self["aliases"] = aliases self["aliases"] = aliases
self.ctx.units.units = oldunits self.ctx.units.units = oldunits
class Units: class Units:
def __init__(self, ctx: Context) -> None: def __init__(self, ctx: Context) -> None:
self.ctx = ctx self.ctx = ctx
self.units: List[Unit] = [] self.units: List[Unit] = []
def load(self, lst: List[Any]) -> None: def load(self, lst: List[Any]) -> None:
for unitdct in lst: for unitdct in lst:
unit = Unit(self.ctx, unitdct) unit = Unit(self.ctx, unitdct)
self.units.append(unit) self.units.append(unit)
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 or "aliases" in unit and name in unit["aliases"]: if unit["name"] == name or "aliases" in unit and name in unit["aliases"]:
return unit return unit
return None return None
class Ingredient(Element): class Ingredient(Element):
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
if "pricedb" in dct: if "pricedb" in dct:
pricedb = PriceDBs(self.ctx) pricedb = PriceDBs(self.ctx)
self.ctx.issues += pricedb.load(dct["pricedb"]) self.ctx.issues += pricedb.load(dct["pricedb"])
self["pricedb"] = pricedb self["pricedb"] = pricedb
conversions = [] conversions = []
if "conversions" in dct: if "conversions" in dct:
for convdct in dct["conversions"]: for convdct in dct["conversions"]:
conversion = Conversion(self.ctx, convdct) conversion = Conversion(self.ctx, convdct)
conversions.append(conversion) conversions.append(conversion)
self["conversions"] = conversions self["conversions"] = conversions
class Ingredients: class Ingredients:
def __init__(self, ctx: Context) -> None: def __init__(self, ctx: Context) -> None:
self.ctx = ctx self.ctx = ctx
self.ingredients: List[Ingredient] = [] self.ingredients: List[Ingredient] = []
def load(self, lst: List[Any]) -> None: def load(self, lst: List[Any]) -> None:
for ingdct in lst: for ingdct in lst:
ing = Ingredient(self.ctx, ingdct) ing = Ingredient(self.ctx, ingdct)
self.ingredients.append(ing) self.ingredients.append(ing)
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 "aliases" in ing and name in ing["aliases"]: if ing["name"] == name or "aliases" in ing and name in ing["aliases"]:
return ing return ing
return None return None
class PriceDBs: class PriceDBs:
def __init__(self, ctx: Context) -> None: def __init__(self, ctx: Context) -> None:
self.ctx = ctx self.ctx = ctx
self.pricedbs: List[PriceDB] = [] self.pricedbs: List[PriceDB] = []
def load(self, lst: List[Any]) -> List[str]: def load(self, lst: List[Any]) -> List[str]:
for elem in lst: for elem in lst:
pricedb = PriceDB(self.ctx, elem) pricedb = PriceDB(self.ctx, elem)
self.pricedbs.append(pricedb) self.pricedbs.append(pricedb)
return self.ctx.issues return self.ctx.issues
class PriceDB(Element): class PriceDB(Element):
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
if "amount" not in dct: if "amount" not in dct:
self["amount"] = 1.0 self["amount"] = 1.0
if "unit" in dct: if "unit" in dct:
unitstr = dct["unit"] unitstr = dct["unit"]
self["unit"] = self.ctx.units.get(unitstr) self["unit"] = self.ctx.units.get(unitstr)
if self["unit"] is None: if self["unit"] is None:
self.ctx.issues.append(f"unknown unit {unitstr}") self.ctx.issues.append(f"unknown unit {unitstr}")
else: else:
self["unit"] = None self["unit"] = None
class IngredientInstance(Element): class IngredientInstance(Element):
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
self["ingredient"] = self.ctx.ingredients.get(dct["name"]) self["ingredient"] = self.ctx.ingredients.get(dct["name"])
if self["ingredient"] is None: if self["ingredient"] is None:
self.ctx.issues.append(f"unknown ingredient {dct['name']}") self.ctx.issues.append(f"unknown ingredient {dct['name']}")
if "amount" not in dct: if "amount" not in dct:
self["amount"] = 1.0 self["amount"] = 1.0
if "unit" in dct: if "unit" in dct:
unitstr = dct["unit"] unitstr = dct["unit"]
self["unit"] = self.ctx.units.get(unitstr) self["unit"] = self.ctx.units.get(unitstr)
if self["unit"] is None: if self["unit"] is None:
self.ctx.issues.append("unknown unit {unitstr}") self.ctx.issues.append("unknown unit {unitstr}")
else: else:
self["unit"] = None self["unit"] = None
if "note" not in dct: if "note" not in dct:
self["note"] = "" self["note"] = ""
alternatives = [] alternatives = []
if "or" in dct: if "or" in dct:
for ingdct in dct["or"]: for ingdct in dct["or"]:
ingredient = IngredientInstance(self.ctx, ingdct) ingredient = IngredientInstance(self.ctx, ingdct)
alternatives.append(ingredient) alternatives.append(ingredient)
self["alternatives"] = alternatives self["alternatives"] = alternatives
class Recipe(Element): class Recipe(Element):
def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None: def __init__(self, ctx: Context, dct: Dict[str, Any]) -> None:
super().__init__(ctx, dct) super().__init__(ctx, dct)
self.srcpath = "" self.srcpath = ""
self.outpath = "" self.outpath = ""
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
ingredients: List[IngredientInstance] = [] ingredients: List[IngredientInstance] = []
if "ingredients" in dct: if "ingredients" in dct:
for ing in dct["ingredients"]: for ing in dct["ingredients"]:
ingredient = IngredientInstance(self.ctx, ing) ingredient = IngredientInstance(self.ctx, ing)
ingredients.append(ingredient) ingredients.append(ingredient)
self["ingredients"] = ingredients self["ingredients"] = ingredients
subrecipes: List[Recipe] = [] subrecipes: List[Recipe] = []
if "subrecipes" in dct: if "subrecipes" in dct:
for partdct in dct["subrecipes"]: for partdct in dct["subrecipes"]:
rp = Recipe(self.ctx, partdct) rp = Recipe(self.ctx, partdct)
subrecipes.append(rp) subrecipes.append(rp)
self["subrecipes"] = subrecipes self["subrecipes"] = subrecipes
class Builder: class Builder:
def __init__(self) -> None: def __init__(self) -> None:
self.jinjaenv = jinja2.Environment( self.jinjaenv = jinja2.Environment(
loader=jinja2.FileSystemLoader("templates"), loader=jinja2.FileSystemLoader("templates"),
autoescape=jinja2.select_autoescape(), autoescape=jinja2.select_autoescape(),
) )
def numprint(input: int) -> str: def numprint(input: int) -> str:
out = str(input) out = str(input)
if out.endswith(".0"): if out.endswith(".0"):
return out.split(".", maxsplit=1)[0] return out.split(".", maxsplit=1)[0]
if out == "0.5": if out == "0.5":
return "1/2" return "1/2"
if out == "0.25": if out == "0.25":
return "1/4" return "1/4"
if out == "0.75": if out == "0.75":
return "3/4" return "3/4"
return out return out
self.jinjaenv.filters["numprint"] = numprint self.jinjaenv.filters["numprint"] = numprint
self.ctx = Context() self.ctx = Context()
# list of output files that will be built # list of output files that will be built
self.outfiles: Set[str] = set() self.outfiles: Set[str] = set()
def load_file(self, file: str) -> Any: def load_file(self, 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:
txt = f.read() txt = f.read()
if file.endswith(".json"): if file.endswith(".json"):
return json.loads(txt) return json.loads(txt)
return yaml.safe_load(txt) return yaml.safe_load(txt)
def rendertemplate( def rendertemplate(
self, templatepath: str, format: str, file: str, dir: str, args: Any self, templatepath: str, format: str, file: str, dir: str, args: Any
) -> None: ) -> None:
template = self.jinjaenv.get_template(templatepath) template = self.jinjaenv.get_template(templatepath)
print(f"rendering {file}") print(f"rendering {file}")
outstr = template.render(args) outstr = template.render(args)
os.makedirs(f"{dir}/out/{format}", exist_ok=True) os.makedirs(f"{dir}/out/{format}", exist_ok=True)
with open(f"{dir}/out/{format}/{file}", "w", encoding="utf-8") as f: with open(f"{dir}/out/{format}/{file}", "w", encoding="utf-8") as f:
f.write(outstr) f.write(outstr)
self.outfiles.add(file) self.outfiles.add(file)
def load(self, dir: str) -> int: def load(self, dir: str) -> int:
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)
self.ctx.units.load(unitsdct) self.ctx.units.load(unitsdct)
if len(self.ctx.issues) != 0: if len(self.ctx.issues) != 0:
for issue in self.ctx.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)
self.ctx.ingredients.load(ingredientsdct) self.ctx.ingredients.load(ingredientsdct)
if len(self.ctx.issues) != 0: if len(self.ctx.issues) != 0:
for issue in self.ctx.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:
files = [] files = []
for _, _, filesx in os.walk(dir + "/recipes"): for _, _, filesx in os.walk(dir + "/recipes"):
files = filesx files = filesx
files.sort() files.sort()
recipes: List[Recipe] = [] recipes: List[Recipe] = []
recipeschema = self.load_file("schemas/recipe.json") recipeschema = self.load_file("schemas/recipe.json")
for file in files: for file in files:
if not file.endswith(".yaml"): if not file.endswith(".yaml"):
print(f"unknown extension of {file}") print(f"unknown extension of {file}")
continue continue
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)
recipe = Recipe(self.ctx, 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(self.ctx.issues) != 0: if len(self.ctx.issues) != 0:
for issue in self.ctx.issues: for issue in self.ctx.issues:
print("ERROR", issue) print("ERROR", issue)
return 1 return 1
self.rendertemplate( self.rendertemplate(
templatepath="index.html", templatepath="index.html",
format="html", format="html",
file="index.html", file="index.html",
dir=dir, dir=dir,
args={"recipes": recipes}, args={"recipes": recipes},
) )
for recipe in recipes: for recipe in recipes:
self.rendertemplate( self.rendertemplate(
templatepath="recipe.html", templatepath="recipe.html",
format="html", format="html",
file=recipe.outpath, file=recipe.outpath,
dir=dir, dir=dir,
args={"recipe": recipe}, args={"recipe": recipe},
) )
return 0 return 0
def finish(self, dir: str) -> int: def finish(self, dir: str) -> int:
files = set() files = set()
for _, _, filesx in os.walk(f"{dir}/out/html"): for _, _, filesx in os.walk(f"{dir}/out/html"):
files = set(filesx) files = set(filesx)
# files we did not generate, probably left by a previous run, but not valid anymore # files we did not generate, probably left by a previous run, but not valid anymore
extra_files = files - self.outfiles extra_files = files - self.outfiles
for file in extra_files: for file in extra_files:
print(f"removing obsolete {file}") print(f"removing obsolete {file}")
os.remove(f"{dir}/out/html/{file}") os.remove(f"{dir}/out/html/{file}")
return 0 return 0
def build(self, path: str) -> int: def build(self, path: str) -> int:
fcs = [self.load, self.run, self.finish] fcs = [self.load, self.run, self.finish]
for func in fcs: for func in fcs:
try: try:
ret = func(path) ret = func(path)
if ret != 0: if ret != 0:
return ret return ret
except jsonschema.exceptions.ValidationError as e: except jsonschema.exceptions.ValidationError as e:
print("ERROR:", e) print("ERROR:", e)
return 1 return 1
return 0 return 0
if __name__ == "__main__": if __name__ == "__main__":
builder = Builder() builder = Builder()
sys.exit(builder.build("recipes")) sys.exit(builder.build("recipes"))

View file

@ -1,8 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{%block title%}Recipes{%endblock%} {%block title%}Recipes{%endblock%}
{%block body %} {%block body %}
<h1>Recipes</h1> <h1>Recipes</h1>
{% for recipe in recipes %} {% for recipe in recipes %}
<li><a href="{{ recipe.outpath }}">{{ recipe.title }}</a></li> <li><a href="{{ recipe.outpath }}">{{ recipe.title }}</a></li>
{% endfor %} {% endfor %}
{%endblock%} {%endblock%}

View file

@ -1,39 +1,39 @@
{% 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 %}
{% for alting in ing.alternatives %} {% for alting in ing.alternatives %}
or {{ingredientpart(alting)}} or {{ingredientpart(alting)}}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}
{% macro getrecipe(rec) -%} {% macro getrecipe(rec) -%}
{% for subrecipe in rec.subrecipes %} {% for subrecipe in rec.subrecipes %}
{{getrecipe(subrecipe)}} {{getrecipe(subrecipe)}}
{% endfor %} {% endfor %}
<h1>{{rec.title}}</h1> <h1>{{rec.title}}</h1>
{% if rec.ingredients|length != 0 %} {% if rec.ingredients|length != 0 %}
<h3>Ingredients</h3> <h3>Ingredients</h3>
{% for ing in rec.ingredients %} {% for ing in rec.ingredients %}
<li>{{ingredient(ing)}}</li> <li>{{ingredient(ing)}}</li>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if rec.steps|length != 0 %} {% if rec.steps|length != 0 %}
<h3>Steps</h3> <h3>Steps</h3>
{% for step in rec.steps %} {% for step in rec.steps %}
<li>{{ step }}</li> <li>{{ step }}</li>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<hr> <hr>
{%- endmacro %} {%- endmacro %}
{%block title%}{{recipe.title}}{%endblock%} {%block title%}{{recipe.title}}{%endblock%}
{%block body %} {%block body %}
<a href="index.html">back</a> <a href="index.html">back</a>
{{getrecipe(recipe)}} {{getrecipe(recipe)}}
{%endblock%} {%endblock%}