overhaul warning and error handling

This commit is contained in:
Emi Vasilek 2023-11-16 00:28:23 +01:00
parent ffa0253193
commit 5d9ddf60b7

View file

@ -1,5 +1,6 @@
from abc import abstractmethod from abc import abstractmethod
from collections import defaultdict from collections import defaultdict
import collections
import sys import sys
from typing import Dict, List, Any, Optional, Set from typing import Dict, List, Any, Optional, Set
import os import os
@ -10,6 +11,38 @@ import jinja2
import jsonschema import jsonschema
import jsonschema.exceptions import jsonschema.exceptions
ISSUE_UNKNOWN_UNIT = "unknown-unit"
ISSUE_UNKNOWN_INGREDIENT = "unknown-ingredient"
ISSUE_DUPLICATE_UNITS = "duplicate-units"
ISSUE_KNOWN_PRICE_UNKNOWN_CONVERSION = "known-price-unknown-conversion"
class Issue:
def __init__(self, id: str, msg: str) -> None:
self.id = id
self.msg = msg
class Issues:
def __init__(self) -> None:
self.errors: List[Issue] = []
self.warnings: List[Issue] = []
def error(self, id: str, msg: str) -> None:
self.errors.append(Issue(id, msg))
def warn(self, id: str, msg: str) -> None:
self.warnings.append(Issue(id, msg))
def check(self) -> int:
retcode = len(self.errors) != 0
for msg in self.errors:
print(f"ERROR {msg.id}: {msg.msg}")
for msg in self.warnings:
print(f"WARNING {msg.id}: {msg.msg}")
self.errors.clear()
self.warnings.clear()
return retcode
class Context: class Context:
def __init__(self) -> None: def __init__(self) -> None:
@ -17,7 +50,7 @@ class Context:
self.default_unit = Unit(self, {"name": "piece"}) self.default_unit = Unit(self, {"name": "piece"})
self.units.units.append(self.default_unit) self.units.units.append(self.default_unit)
self.ingredients = Ingredients(self) self.ingredients = Ingredients(self)
self.issues: List[str] = [] self.issues = Issues()
class Element: class Element:
@ -103,12 +136,20 @@ class Units:
return unit return unit
return None return None
def validate(self) -> None:
unitnames = []
for unit in self.units:
unitnames.append(unit["name"])
for unitname, num in collections.Counter(unitnames).items():
if num>1:
self.ctx.issues.error(ISSUE_DUPLICATE_UNITS, f"units.yaml: {unitname} should only have one entry, found {num}")
class Ingredient(Element): class Ingredient(Element):
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
if "prices" in dct: if "prices" in dct:
pricedb = PriceDBs(self.ctx) pricedb = PriceDBs(self.ctx)
self.ctx.issues += pricedb.load(dct["prices"]) pricedb.load(dct["prices"])
self["prices"] = pricedb self["prices"] = pricedb
conversions = [] conversions = []
@ -155,8 +196,8 @@ class Ingredient(Element):
# find path between conversions # find path between conversions
path = self.dfs(conversions, unitfrom["name"], unitto["name"]) path = self.dfs(conversions, unitfrom["name"], unitto["name"])
if path is None: if path is None:
print( self.ctx.issues.warn(ISSUE_KNOWN_PRICE_UNKNOWN_CONVERSION,
f'WARNING: {self["name"]} has a known price, but conversion {unitfrom["name"]} -> {unitto["name"]} not known' f'{self["name"]} has a known price, but conversion {unitfrom["name"]} -> {unitto["name"]} not known'
) )
return None return None
assert len(path) != 0 assert len(path) != 0
@ -210,11 +251,10 @@ class PriceDBs:
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]) -> None:
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
def __repr__(self) -> str: def __repr__(self) -> str:
return self.pricedbs.__repr__() return self.pricedbs.__repr__()
@ -229,7 +269,7 @@ class PriceDB(Element):
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.error(ISSUE_UNKNOWN_UNIT, f"unknown unit {unitstr}")
else: else:
self["unit"] = self.ctx.default_unit self["unit"] = self.ctx.default_unit
@ -238,7 +278,7 @@ class IngredientInstance(Element):
def load(self, dct: Dict[str, Any]) -> None: def load(self, dct: Dict[str, Any]) -> None:
ingredient = self.ctx.ingredients.get(dct["name"]) ingredient = self.ctx.ingredients.get(dct["name"])
if ingredient is None: if ingredient is None:
self.ctx.issues.append(f"unknown ingredient {dct['name']}") self.ctx.issues.error(ISSUE_UNKNOWN_INGREDIENT, f"unknown ingredient {dct['name']}")
self["ingredient"] = ingredient self["ingredient"] = ingredient
if "amount" not in dct: if "amount" not in dct:
@ -248,7 +288,7 @@ class IngredientInstance(Element):
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.error(ISSUE_UNKNOWN_UNIT, "unknown unit {unitstr}")
else: else:
self["unit"] = self.ctx.default_unit self["unit"] = self.ctx.default_unit
@ -356,18 +396,17 @@ class Builder:
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: retcode = self.ctx.issues.check()
for issue in self.ctx.issues: if retcode != 0:
print("ERROR in units.yaml:", issue)
return 1 return 1
self.ctx.units.validate()
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: retcode = self.ctx.issues.check()
for issue in self.ctx.issues: if retcode != 0:
print("ERROR in ingredients.yaml:", issue)
return 1 return 1
return 0 return 0
@ -389,11 +428,12 @@ class Builder:
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"
if self.ctx.issues.check() != 0:
continue
recipes.append(recipe) recipes.append(recipe)
if len(self.ctx.issues) != 0: retcode = self.ctx.issues.check()
for issue in self.ctx.issues: if retcode != 0:
print("ERROR", issue)
return 1 return 1
self.rendertemplate( self.rendertemplate(