diff --git a/README.md b/README.md index 138aa45..5a7e47c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,32 @@ # Recipes for Engineers -Store recipes in a nerdy JSON tree format and use that to generate [Cooking for Engineers](https://www.cookingforengineers.com)-style tabular recipe cards in HTML. +Store recipes in a nerdy JSON tree format and use that to generate [Cooking for +Engineers](https://www.cookingforengineers.com)-style tabular recipe cards in +HTML. -Right now this project is just a set of scripts. +I used these scripts to create my [recipes +site](https://tilde.town/~gamerdonkey/recipes/) here on town. Fun fact: you can +replace the `html` with `json` in the URLs to get my recipes in their raw JSON +format. ## Requirements -These scripts require the `anytree` Python library. +Requires the [anytree Python +library](https://anytree.readthedocs.io/en/latest/). -## `create_recipe_json.py` +## Main Scripts -This is a fairly naive script that walks you through the process of turning a recipe into a tree structure. It has you list out ingredients, and then add tasks to perform on those ingredients. You end up with a tree that is something like this: +### `create_recipe_json.py` + +This is a fairly naive script that walks you through the process of turning a +recipe into a tree structure. It has you list out ingredients, and then add +tasks to perform on those ingredients. + +You can add ingredients and then add tasks to perform on those ingredients. +The results of those tasks can have more tasks applied to them, until you end +up with your finished product. + +The tree you build has a structure like this, with the last step at the root: ``` shake and grill for 20 more mins @@ -28,12 +44,31 @@ shake and grill for 20 more mins +-- red potatoes ``` -Leaves are raw ingredients, other nodes are tasks. The tree is output to a JSON file. +Leaves are raw ingredients, other nodes are tasks. A recipe can have multiple +tree roots. Once you are done adding ingredients and tasks, all trees are +output to a JSON file based on the name you give to the recipe. -## `recipe_json_to_html.py` +### `recipes2html.py` -This script takes that JSON file and does a recursive depth-first search to render a webpage with an HTML-table-based recipe card. +***WARNING: These scripts do not sanitize most inputs!*** Parts of the JSON can +have HTML which will be rendered out in the browser, which was very convenient +for me but a terrible idea for security. **DO NOT RUN THIS CODE ON JSON YOU +DID NOT CREATE YOURSELF!** + +This script takes an argument pointing to a directory of recipe JSON files, in +the format created by the `create_recipe_json.py` script. It then does a +recursive depth-first search on each tree in that file to generate an HTML +file with a traditional recipe and a tabular-style recipe card. It also takes +an optional argument pointing to a file and, if given, the contents of that +file will be inserted into the header section of the generated `index.html`. ## Known Issues -- A single node cannot have multiple tasks done on it (nerdy reason: because a node cannot have two parents). So this cannot represent, for example, mixing cinnamon and sugar, then splitting that mixture up and doing separate subsequent operations with the two batches. +- A single node cannot have multiple tasks done on it (nerdy reason: because a +node cannot have two parents). So this cannot perfectly represent, for example, +mixing cinnamon and sugar, then splitting that mixture up and doing separate +subsequent operations with the two batches. + This has come up once in my [zucchini bread +recipe](https://tilde.town/~gamerdonkey/recipes/lemony_olive_oil_zucchini_bread.html), +and I worked around the issue by just adding an instruction to set the +ingredients aside and use them later. diff --git a/create_recipe_json.py b/create_recipe_json.py index 1d5f651..dd33af2 100644 --- a/create_recipe_json.py +++ b/create_recipe_json.py @@ -2,6 +2,7 @@ import json from anytree import Node, RenderTree from anytree.exporter import DictExporter +from recipe import Recipe def print_nodes(): @@ -15,42 +16,61 @@ node = "start" while node: print_nodes() - node = input("Add node: ") + node = input("Add ingredient/task: ") if node: - needs = input("List needs (comma-separated): ") + needs = "" + if nodes: + needs = input("List needs (comma-separated): ") + description = input("Detailed description: ") if needs: result = input("Result of node (e.g. chopped carrots): ") new_node = Node(node, description=description, result=result) - for e in [int(key.strip()) for key in needs.split(",")]: - nodes.pop(e).parent = new_node + for key in needs.split(","): + try: + nodes.pop(int(key.strip())).parent = new_node + except KeyError: + print(f"<{key.strip()}> was not a valid ingredient or task") else: new_node = Node(node, description=description, result=node) nodes[i] = new_node i += 1 + else: + for key, tree_node in nodes.items(): + print(key, end=" ") + for pre, _, render_node in RenderTree(tree_node): + print(f"{pre}{render_node.name}") -root_keys = nodes.keys() + confirm = input("Done adding ingredients and tasks? (y/N): ") -for key in root_keys: - for pre, _, node in RenderTree(nodes[key]): - print(f"{pre}{node.name}") + if not confirm or not confirm.lower().startswith("y"): + node = "continue" + +name = "" +try: + while not name: + name = input("Recipe name (required to save, ctrl-c to exit): ") +except KeyboardInterrupt: + print("Exiting...") + exit(0) -name = input("Recipe name: ") description = input("Recipe description: ") -if name: - sub_recipes = [] - exporter = DictExporter() - for key in root_keys: - sub_recipes.append(exporter.export(nodes[key])) +servings = input("Servings: ") +prep_time = input("Prep time: ") +cook_time = input("Cook time: ") - recipe = {"name": name, "description": description, "sub_recipes": sub_recipes} +recipe = Recipe(name, description, servings, prep_time, cook_time) +for _, node in nodes.items(): + recipe.add_sub_recipe(node) - filename = name.replace(" ", "_").lower() +filename = f"{name.replace(' ', '_').lower()}.json" - with open(f"{filename}.json", 'w') as f: - f.write(json.dumps(recipe, indent=2)) +print(f"Writing to {filename}...") +with open(filename, 'w') as f: + f.write(recipe.to_json()) +print("Done!") diff --git a/recipe.py b/recipe.py index 40752f9..5223b8e 100644 --- a/recipe.py +++ b/recipe.py @@ -1,6 +1,9 @@ +import html import json +from anytree import Node from anytree.importer import DictImporter +from anytree.exporter import DictExporter from sys import argv @@ -14,29 +17,32 @@ class TableCell: def to_html(self): if self.is_header: - html = f"
{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 ""}
+