I’ve been wanting to write this for awhile now, so glad to finally get pen to paper (ha) and breakdown the spec for Caramel’s Recipe format.
I created this format as a way to make recipes as interoperable as possible and to make passing rich information between Caramel users (and other apps that implement this format) incredibly easy. The format is also designed to be able to be picked apart by everyday people so they can pull out their information without any technical knowledge.
You should familiarize yourself with YAML and the Open Recipe Format as these are two structural pieces that define the data in this format. In this example, I’ll break apart the default Grilled Cheese & Tomato Soup recipe (download here) and describe both what is happening, why that choice was made, and if possible any tips about importing it into your local application.
Opening Technicals
The format extension is .recipe (com.contagious.Caramel.recipe) and are packages, so you can open them on macOS by right clicking and selecting “Show Package Contents”. The general structure looks something like this:
- recipe.yml
- metadata.yml
- Items/
- Basil.item
- Butter.item
- Fresh Bread.item
- …
- Resources/ (optional)
- recipe.png
- thumbnail.png
Our recipe is self-contained in recipe.yml:
recipe_uuid: default-NdvISKFRKc recipe_name: Grilled Cheese & Tomato Soup yield: 2 Servings prep_time: 900 bake_time: 900 cookbook: Quick and Easy Lunches For The Heart ingredients: - Tomato Soup: amounts: - amount: 1 - Garlic Salt: amounts: - amount: 2 Tsp - Butter: notes: Softened to be spreadable amounts: - amount: 1 Tbsp - Fresh bread: notes: '2 Slices: A day or so old' amounts: - amount: 6 Slices - Basil: amounts: - amount: 1 Tsp - Pepperjack: amounts: - amount: 2 steps: - step: "Preheat oven to 350\xBA" - step: 'If preferred: remove crusts from older breads. Butter all four pieces.' - step: Add Garlic Salt to older pieces and set on foiled tray. Place in oven for 15 minutes. - step: Empty Tomato Soup into saucepan, add basil, salt & pepper to taste. - step: Heat skillet and place remaining bread slices together (cheese between) on skillet. - step: Cook until just crisp and flip, repeat until sides are equal and cheese is just melted. - step: Remove sandwiches from skillet, add soup to bowl, and take bread from oven. - step: Cut up bread from oven and add as croutons to soup and enjoy! generated_by: Caramel 2.2.2 (59) format_version: 1.0
This follows the Open Recipe Format, with a few distinctions:
- prep_time, bake_time are in seconds
- generated_by and format_version tags are added
The current format is 1.0
Caramel is capable of reading the full ORF set, but may change a few items to match its model. Caramel can also import a recipe using only this file in the Recipe package (without metadata.yml or the Items directory). If your application can import .recipe format files, it should follow a similar methodology.
The ORF is incredibly well documented and has a lot of thought put into it so I highly suggest reading through it before building any sort of Recipe based application.
We chooses YAML over JSON, XML, or plist for the same reason as ORF: it’s easily readable and quick to read and write. To the average viewer, they can distinguish the different hunks of content easily and the actual file format contains a little bit less overhead.
Metadata Information
The metadata.yml contains the in-depth machine information for mapping your Recipe to an existing model. This file exists to prevent clutter to the recipe.yml file, make fetching some information faster, and define the rich context and connections to your Recipes:
recipe_uuid: default-NdvISKFRKc recipe_name: Grilled Cheese & Tomato Soup has_photo_representation: true ingredient_count: 6 cookbook: Quick and Easy Lunches For The Heart cookbook_uuid: default-VHVeZukEjF cookbook_group: null cookbook_order: 0 ingredient_uris: Tomato Soup: default-O0P5qhHbzW Fresh bread: default-gSdct7DC1G Basil: default-r1Vg4lFqBP Pepperjack: default-WQe10I2Coq Butter: default-HRRZphNKUT Garlic Salt: default-nVbvkfMu3j generated_by: Caramel 2.2.2 (59) format_version: 1.0
When importing a Recipe, you should attempt to first match the items to existing items to your store by comparing the URI/UUID. If nothing is found, you should then attempt to match by case-insensitive name and if that fails, then you should finally import the Item into the local store (using the .item contained). If it was previously found, you should not update the item’s information as the user may have made edits to distinguish their copy.
Items/*.item
Contained within the Items directory are the various items, which are also in a packaged form. These items can be individually taken from a .recipe directory or exported from Caramel’s Item section directly so your app should be able to import these independently of a Recipe. The structure is incredibly simple:
- item.yml
- category.yml (optional)
items.yml:
item_uuid: default-nVbvkfMu3j item_name: Garlic Salt category: Spices & Herbs category_order: 14 adds_to_stock: false generated_by: Caramel 2.2.2 (59) format_version: 1.0.0
and category.yml, which may be excluded if the item isn’t in one:
category_uuid: default-dtrepMEl9Q category_name: Spices & Herbs icon: 33 color: 11 generated_by: Caramel 2.2.2 (59) format_version: 1.0.0
And there you have it!
Soon I’d like to expand on this format to include more detailed expansion of properties for items and categories, but right now they’re scoped to Caramel’s inner model. Please shoot me a message if you have any questions or suggestions!