import html
import json
from anytree import Node
from anytree.importer import DictImporter
from anytree.exporter import DictExporter
from sys import argv
class TableCell:
def __init__(self, name, rowspan="", colspan="", description="", is_header=False):
self.name = name
self.rowspan = rowspan
self.colspan = colspan
self.description = description
self.is_header = is_header
def to_html(self):
if self.is_header:
output = f"
{self.name}
"
elif not self.name:
output = f'
'
else:
output = f"
{self.name}
"
return output
class Recipe:
def __init__(self, name: str, desc: str, servings: str, prep_time: str, cook_time: str, sub_recipes: list[Node]=[]):
self.name = name
self.desc = desc
self.servings = servings
self.prep_time = prep_time
self.cook_time = cook_time
self._sub_recipes = sub_recipes
def add_sub_recipe(self, tree_root: Node):
self._sub_recipes.append(tree_root)
def generate_ingredient_list(self):
ingredients = []
for sub_recipe in self._sub_recipes:
for ingredient in sub_recipe.leaves:
if ingredient.description:
ingredients.append(f"{ingredient.name} -- {ingredient.description}")
else:
ingredients.append(ingredient.name)
return ingredients
def generate_task_list(self):
tasks = []
for sub_recipe in self._sub_recipes:
tasks.extend(self.generate_steps_depth_first(sub_recipe, steps=[]))
return tasks
def generate_steps_depth_first(self, node, steps=[]):
for child in sorted(node.children, key=lambda x: x.height, reverse=True):
steps = self.generate_steps_depth_first(child, steps)
if not node.is_leaf:
steps.append(node.description if node.description else node.name)
return steps
def generate_tables(self):
tables = []
for sub_recipe in self._sub_recipes:
tables.append(self.build_table_rows_depth_first(sub_recipe))
return tables
def build_table_rows_depth_first(self, node, rows=None):
if rows is None:
rows = []
for child in sorted(node.children, key=lambda x: x.height, reverse=True):
rows = self.build_table_rows_depth_first(child, rows)
colspan = 0
if node.siblings:
max_sibling_height = max([s.height for s in node.siblings])
if max_sibling_height > node.height:
colspan = max_sibling_height - node.height + 1
if node.is_leaf:
rows.append([TableCell(node.name, description=node.description, is_header=True)])
if colspan > 1:
rows[-1].append(TableCell("", colspan=colspan-1))
else:
child_leaves = len(node.leaves)
cur_row = len(rows) - child_leaves
rows[cur_row].append(TableCell(node.name, rowspan=child_leaves, colspan=colspan, description=node.description))
return rows
@classmethod
def from_json(cls, json_string: str):
recipe_dict = json.loads(json_string)
name = recipe_dict["name"]
desc = recipe_dict.get("description")
servings = recipe_dict.get("servings")
prep_time = recipe_dict.get("prep_time")
cook_time = recipe_dict.get("cook_time")
importer = DictImporter()
sub_recipes = [importer.import_(data) for data in recipe_dict["sub_recipes"]]
return cls(name, desc, servings, prep_time, cook_time, sub_recipes)
def to_dict(self):
sub_recipes_dict = []
exporter = DictExporter()
for sub_recipe in self._sub_recipes:
sub_recipes_dict.append(exporter.export(sub_recipe))
return {
"name": self.name,
"description": self.desc,
"servings": self.servings,
"prep_time": self.prep_time,
"cook_time": self.cook_time,
"sub_recipes": sub_recipes_dict
}
def to_json(self):
return json.dumps(self.to_dict(), indent=2)
def to_html(self):
return f"""
{self.name}
{self.name}
{self.desc}
{f"Servings: {self.servings} " if self.servings else ""}
{f"Preparation Time: {self.prep_time} " if self.prep_time else ""}
{f"Cook Time: {self.cook_time} " if self.cook_time else ""}
Needs
{"\n".join([f"
{ingredient}
" for ingredient in self.generate_ingredient_list()])}
Preparation
{"\n".join([f"
{task}
" for task in self.generate_task_list()])}
Tabular Layout
{"\n".join(
[f"
\n{"".join(
[f"
\n{"".join(
[f" {cell.to_html()}\n" for cell in row]
)}
\n" for row in table]
)}
" for table in self.generate_tables()]
)}
"""
if __name__ == "__main__":
if len(argv) < 2:
print(f"usage: {argv[0]} ")
exit(1)
with open(argv[1], 'r') as f:
print(Recipe.from_json(f.read()).to_html())