diff --git a/recipes.py b/recipes.py index ed68506..b2bb808 100644 --- a/recipes.py +++ b/recipes.py @@ -1,5 +1,6 @@ from abc import abstractmethod from collections import defaultdict +import collections import sys from typing import Dict, List, Any, Optional, Set import os @@ -10,6 +11,38 @@ import jinja2 import jsonschema 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: def __init__(self) -> None: @@ -17,7 +50,7 @@ class Context: self.default_unit = Unit(self, {"name": "piece"}) self.units.units.append(self.default_unit) self.ingredients = Ingredients(self) - self.issues: List[str] = [] + self.issues = Issues() class Element: @@ -103,12 +136,20 @@ class Units: return unit 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): def load(self, dct: Dict[str, Any]) -> None: if "prices" in dct: pricedb = PriceDBs(self.ctx) - self.ctx.issues += pricedb.load(dct["prices"]) + pricedb.load(dct["prices"]) self["prices"] = pricedb conversions = [] @@ -155,8 +196,8 @@ class Ingredient(Element): # find path between conversions path = self.dfs(conversions, unitfrom["name"], unitto["name"]) if path is None: - print( - f'WARNING: {self["name"]} has a known price, but conversion {unitfrom["name"]} -> {unitto["name"]} not known' + self.ctx.issues.warn(ISSUE_KNOWN_PRICE_UNKNOWN_CONVERSION, + f'{self["name"]} has a known price, but conversion {unitfrom["name"]} -> {unitto["name"]} not known' ) return None assert len(path) != 0 @@ -210,11 +251,10 @@ class PriceDBs: self.ctx = ctx self.pricedbs: List[PriceDB] = [] - def load(self, lst: List[Any]) -> List[str]: + def load(self, lst: List[Any]) -> None: for elem in lst: pricedb = PriceDB(self.ctx, elem) self.pricedbs.append(pricedb) - return self.ctx.issues def __repr__(self) -> str: return self.pricedbs.__repr__() @@ -229,7 +269,7 @@ class PriceDB(Element): unitstr = dct["unit"] self["unit"] = self.ctx.units.get(unitstr) 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: self["unit"] = self.ctx.default_unit @@ -238,7 +278,7 @@ class IngredientInstance(Element): def load(self, dct: Dict[str, Any]) -> None: ingredient = self.ctx.ingredients.get(dct["name"]) 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 if "amount" not in dct: @@ -248,7 +288,7 @@ class IngredientInstance(Element): unitstr = dct["unit"] self["unit"] = self.ctx.units.get(unitstr) if self["unit"] is None: - self.ctx.issues.append("unknown unit {unitstr}") + self.ctx.issues.error(ISSUE_UNKNOWN_UNIT, "unknown unit {unitstr}") else: self["unit"] = self.ctx.default_unit @@ -356,18 +396,17 @@ class Builder: unitsschema = self.load_file("schemas/units.json") jsonschema.validate(instance=unitsdct, schema=unitsschema) self.ctx.units.load(unitsdct) - if len(self.ctx.issues) != 0: - for issue in self.ctx.issues: - print("ERROR in units.yaml:", issue) + retcode = self.ctx.issues.check() + if retcode != 0: return 1 + self.ctx.units.validate() ingredientsdct = self.load_file(dir + "/ingredients.yaml") ingredientsschema = self.load_file("schemas/ingredients.json") jsonschema.validate(instance=ingredientsdct, schema=ingredientsschema) self.ctx.ingredients.load(ingredientsdct) - if len(self.ctx.issues) != 0: - for issue in self.ctx.issues: - print("ERROR in ingredients.yaml:", issue) + retcode = self.ctx.issues.check() + if retcode != 0: return 1 return 0 @@ -389,11 +428,12 @@ class Builder: recipe = Recipe(self.ctx, recipedct) recipe.srcpath = file recipe.outpath = file[:-5] + ".html" + if self.ctx.issues.check() != 0: + continue recipes.append(recipe) - if len(self.ctx.issues) != 0: - for issue in self.ctx.issues: - print("ERROR", issue) + retcode = self.ctx.issues.check() + if retcode != 0: return 1 self.rendertemplate(