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

Preparation

    {"\n".join([f"
  1. {task}
  2. " 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())