├── .gitignore ├── CONTRIBUTING.md ├── EBNF.md ├── LICENSE ├── README.md ├── examples ├── Coffee Souffle.cook ├── Easy Pancakes.cook ├── Fried Rice.cook └── Olivier Salad.cook ├── proposal-templates └── 0000-spec-proposal-template.md ├── proposals ├── 0002-add-recipe-introduction-section.md ├── 0003-front-matter.md ├── 0004-multiline-steps.md ├── 0005-note-blocks.md ├── 0006-sections.md ├── 0007-canonical-metadata.md ├── 0008-mise-en-place.md ├── 0010-servings.md └── 0015-other-recipes.md └── tests ├── README.md └── canonical.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 1. Create a PR with your proposal based on a template in `proposal-templates` folder. 2 | 2. Start a new discussion in https://github.com/cooklang/spec/discussions to get feedback on your proposal from the community. 3 | 3. Core team members will review the proposal and accept or reject changes to Cooklang during two weeks. 4 | 4. After proposal is accepted it will be added to main specification as soon as official applications (f.e. CookCLI or iOS mobile application) implement required features. 5 | -------------------------------------------------------------------------------- /EBNF.md: -------------------------------------------------------------------------------- 1 | 2 | # Cooklang EBNF description 3 | 4 | WIP, the EBNF is outdated and doesn't contain latest changes yet. 5 | 6 | ```ebnf 7 | recipe = { metadata | step | note }- ; 8 | 9 | (* not sure how to show that, but two below should start from a new line *) 10 | metadata = ">", ">", { text item - ":" }-, ":", { text item }-, new line character ; 11 | step = { text item | ingredient | cookware | timer }-, new line character, blank line ; 12 | 13 | note marker = ">" ; 14 | note = { note marker, { text item }, blank line }-, blank line ; 15 | 16 | ingredient = one word ingredient | multiword ingredient ; 17 | one word ingredient = "@", one word component ; 18 | multiword ingredient = "@", multiword component ; 19 | 20 | cookware = one word cookware | multiword cookware ; 21 | one word cookware = "#", one word component ; 22 | multiword cookware = "#", multiword component ; 23 | 24 | timer = no name timer | one word timer | multiword timer ; 25 | no name timer = "~", no name component ; 26 | one word timer = "~", one word component ; 27 | multiword timer = "~", multiword component ; 28 | 29 | no name component = "{", [ amount ], "}" ; 30 | one word component = word, [ "{", [ amount ], "}" ] ; 31 | multiword component = word, { text item - "{" }-, "{", [ amount ], "}" ; 32 | 33 | amount = quantity | ( quantity, "%", units ) ; 34 | quantity = { text item - "%" - "}" }- ; 35 | units = { text item - "}" }- ; 36 | 37 | word = { text item - white space - punctuation character }- ; 38 | text item = ? any character except new line character ? ; 39 | 40 | (* https://en.wikipedia.org/wiki/Template:General_Category_(Unicode) *) 41 | new line character = ? newline characters (U+000A ~ U+000D, U+0085, U+2028, and U+2029) ? ; 42 | white space = ? Unicode General Category Zs and CHARACTER TABULATION (U+0009) ? ; 43 | punctuation character = ? Unicode General Category P* ? ; 44 | blank line = { white space }, new line character ; 45 | 46 | comments = "-", "-", text item, new line character ; 47 | block comments = "[", "-", ? any character except "-" followed by "]" ?, "-", "]" ; 48 | ``` 49 | 50 | # Symbols 51 | 52 | |**Usage**|**Notation** 53 | :-----:|:-----: 54 | definition |**=** 55 | concatenation |, 56 | termination |; 57 | alternation || 58 | optional |[ ... ] 59 | repetition |{ ... } 60 | repetition (at least 1) |{ ... }- 61 | grouping |( ... ) 62 | terminal string |" ... " 63 | terminal string |' ... ' 64 | comment |(* ... *) 65 | special sequence |? ... ? 66 | exception |- 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Cooklang contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Just heads up, that not all latest language features supported in the apps yet. 2 | > You can track progress at https://github.com/orgs/cooklang/projects/4 3 | 4 | * [About Cooklang](#about-cooklang) 5 | * [The .cook Recipe Specification](#the-cook-recipe-specification) 6 | * [Ingredients](#ingredients) 7 | * [Steps](#steps) 8 | * [Comments](#comments) 9 | * [Metadata](#metadata) 10 | * [Cookware](#cookware) 11 | * [Timer](#timer) 12 | * [Shopping Lists](#shopping-lists) 13 | * [Conventions](#conventions) 14 | * [Adding Pictures](#adding-pictures) 15 | * [Canonical metadata](#canonical-metadata) 16 | * [Advanced](#advanced) 17 | * [Notes](#notes) 18 | * [Sections](#sections) 19 | * [Short-hand preparations](#short-hand-preparations) 20 | * [Projects Which Use Cooklang](#projects-which-use-cooklang) 21 | * [Syntax Highlighting](#syntax-highlighting) 22 | 23 | ## About Cooklang 24 | Cooklang is the markup language at the center of an open-source ecosystem for cooking and recipe management. In Cooklang, each text file is a recipe written as plain-english instructions with markup syntax to add machine-parsible information about required ingredients, cookware, time, and metadata. 25 | 26 | ## The .cook recipe specification 27 | Below is the specification for defining a recipe in Cooklang. 28 | 29 | ### Ingredients 30 | 31 | To define an ingredient, use the `@` symbol. If the ingredient's name contains multiple words, indicate the end of the name with `{}`. 32 | 33 | ```cooklang 34 | Then add @salt and @ground black pepper{} to taste. 35 | ``` 36 | 37 | To indicate the quantity of an item, place the quantity inside `{}` after the name. 38 | 39 | ```cooklang 40 | Poke holes in @potato{2}. 41 | ``` 42 | 43 | To use a unit of an item, such as weight or volume, add a `%` between the quantity and unit. 44 | 45 | ```cooklang 46 | Place @bacon strips{1%kg} on a baking sheet and glaze with @syrup{1/2%tbsp}. 47 | ``` 48 | 49 | Now you can try Cooklang and experiment with a few things in the [Cooklang Playground](https://cooklang.github.io/cooklang-rs/)! 50 | 51 | ### Steps 52 | 53 | Each paragraph in your recipe file is a cooking step. Separate steps with an empty line. 54 | 55 | ```cooklang 56 | A step, 57 | the same step. 58 | 59 | A different step. 60 | ``` 61 | 62 | ### Comments 63 | You can add comments up to the end of the line to Cooklang text with `--`. 64 | ```cooklang 65 | -- Don't burn the roux! 66 | 67 | Mash @potato{2%kg} until smooth -- alternatively, boil 'em first, then mash 'em, then stick 'em in a stew. 68 | ``` 69 | 70 | Or block comments with `[- comment text -]`. 71 | ```cooklang 72 | Slowly add @milk{4%cup} [- TODO change units to litres -], keep mixing 73 | ``` 74 | 75 | ### Metadata 76 | Recipes are more than just steps and ingredients—they also include context, such as preparation times, authorship, and dietary relevance. You can add metadata to your recipe using YAML front matter, add `---` at the beginning of a file and `---` at the end of the front matter block. 77 | 78 | ```yaml 79 | --- 80 | title: Spaghetti Carbonara 81 | tags: 82 | - pasta 83 | - quick 84 | - comfort food 85 | --- 86 | ``` 87 | 88 | ### Cookware 89 | You can define any necessary cookware with `#`. Like ingredients, you don't need to use braces if it's a single word. 90 | ```cooklang 91 | Place the potatoes into a #pot. 92 | Mash the potatoes with a #potato masher{}. 93 | ``` 94 | 95 | ### Timer 96 | You can define a timer using `~`. 97 | ```cooklang 98 | Lay the potatoes on a #baking sheet{} and place into the #oven{}. Bake for ~{25%minutes}. 99 | ``` 100 | 101 | Timers can have a name too: 102 | 103 | ```cooklang 104 | Boil @eggs{2} for ~eggs{3%minutes}. 105 | ``` 106 | 107 | Applications can use this name in notifications. 108 | 109 | ## Shopping Lists 110 | To support the creation of shopping lists by apps and the command line tool, Cooklang includes a specification for a configuration file to define how ingredients should be grouped on the final shopping list. 111 | You can use `[]` to define a category name. These names are arbitrary, so you can customize them to meet your needs. For example, each category could be an aisle or section of the store, such as `[produce]` and `[deli]`. 112 | ```toml 113 | [produce] 114 | potatoes 115 | 116 | [dairy] 117 | milk 118 | butter 119 | ``` 120 | Or, you might be going to multiple stores, in which case you might use `[Tesco]` and `[Costco]`. 121 | ```toml 122 | [Costco] 123 | potatoes 124 | milk 125 | butter 126 | 127 | [Tesco] 128 | bread 129 | salt 130 | ``` 131 | You can also define synonyms with `|`. 132 | ```toml 133 | [produce] 134 | potatoes 135 | 136 | [dairy] 137 | milk 138 | butter 139 | 140 | [deli] 141 | chicken 142 | 143 | [canned goods] 144 | tuna|chicken of the sea 145 | ``` 146 | 147 | ## Conventions 148 | 149 | There are things which aren't part of the language specification but rather common conventions used in tools built on top of the language. 150 | 151 | ### Adding Pictures 152 | You can add images to your recipe by including a supported image file (`.png`,`.jpg`) matching the name of the recipe in the same directory. 153 | ```sh 154 | Baked Potato.cook 155 | Baked Potato.jpg 156 | ``` 157 | You can also add images for specific steps by including a step number before the file extension. 158 | ```sh 159 | Chicken French.cook 160 | Chicken French.0.jpg 161 | Chicken French.3.jpg 162 | ``` 163 | 164 | ### Canonical metadata 165 | 166 | To use your recipes across different apps, follow the conventions on how to name metadata in common cases: 167 | 168 | | Key | Purpose | Example value | 169 | | --- | --- | --- | 170 | | `source`, `source.name` | Where the recipe came from. Usually a URL, can also be text (eg. a book title). | `https://example.org/recipe`, `The Palomar Cookbook `, `mums` | 171 | | `author`, `source.author` | The author of the recipe. | `John Doe` | 172 | | `source.url`|The URL of the recipe if nested format is used.|`https://example.org/recipe`| 173 | | `servings`, `serves`, `yield` | Indicates how many people the recipe is for. Used for scaling quantities. Leading number is used for scaling, anything else is ignored but shown as units. | `2`,`15 cups worth` | 174 | | `course`, `category` | Meal category or course. | `dinner` | 175 | | `locale` | The locale of the recipe. Used for spelling/grammar during edits, and for pluralisation of amounts. Uses ISO 639 language code, then optionally an underscore and the ISO 3166 alpha2 "country code" for dialect variants | `es_VE`, `en_GB`, `fr` | 176 | | `time required`, `time` or `duration` | The preparation + cook time of the recipe. Various formats can be parsed, if in doubt use `HhMm` format to avoid plurals and locales. | `45 minutes`, `1 hour 30 minutes`,`1h30m` | 177 | | `prep time`, `time.prep`|Time for preparation steps only.|`2 hour 30 min`| 178 | | `cook time`, `time.cook`|Time for actual cooking steps.|`10 minutes`| 179 | | `difficulty`|Recipe difficulty level.|`easy`| 180 | | `cuisine`|The cuisine of the recipe.|`French`| 181 | | `diet`|Indicates a dietary restriction or guideline for which this recipe or menu item is suitable, e.g. diabetic, halal etc.|`gluten-free`, or array of values| 182 | | `tags`|List of descriptive tags.|`[2022, baking, summer]`| 183 | | `image`, `images`, `picture`, `pictures`|URL to a recipe image.|`https://example.org/recipe_image.jpg` or array of URLs| 184 | | `title`|Title of the recipe.|`Uzbek Manti`| 185 | | `introduction`, `description`|Additional notes about the recipe.|`This recipe is a traditional Uzbek dish that is made with a variety of vegetables and meat.`| 186 | 187 | ## Advanced 188 | 189 | ### Notes 190 | To include relevant background, insights, or personal anecdotes that aren't part of the cooking steps, use notes. Start a new line with `>` and add your story. 191 | 192 | ```cooklang 193 | > Don't burn the roux! 194 | 195 | Mash @potato{2%kg} until smooth -- alternatively, boil 'em first, then mash 'em, then stick 'em in a stew. 196 | ``` 197 | 198 | ### Sections 199 | Some recipes are more complex than others and may include components that need to be prepared separately. In such cases, you can use the section syntax, e.g., `==Dough==`. The section name and the `=` symbols after it are optional, and the number of `=` symbols does not matter. 200 | 201 | ```cooklang 202 | = Dough 203 | 204 | Mix @flour{200%g} and @water{100%ml} together until smooth. 205 | 206 | == Filling == 207 | 208 | Combine @cheese{100%g} and @spinach{50%g}, then season to taste. 209 | ``` 210 | 211 | ### Short-hand preparations 212 | 213 | Many recipes involve repetitive ingredient preparations, such as peeling or chopping. To simplify this, you can define these common preparations directly within the ingredient reference using shorthand syntax: 214 | 215 | ```cooklang 216 | Mix @onion{1}(peeled and finely chopped) and @garlic{2%cloves}(peeled and minced) into paste. 217 | ``` 218 | 219 | ### Referencing other recipes 220 | 221 | You can reference other recipes using the existing `@` ingredient syntax, inferring relative file paths from the ingredient name: 222 | 223 | ```cooklang 224 | Pour over with @./sauces/Hollandaise{150%g}. 225 | ``` 226 | 227 | These preparations should be clearly displayed in the ingredient list, allowing you to get everything ready before you start cooking. 228 | 229 | ## Projects Which Use Cooklang 230 | * [Cooklang playground](https://cooklang.github.io/cooklang-rs/) 231 | * [Obsidian plugin](https://github.com/deathau/cooklang-obsidian) 232 | * [Official command line application](https://github.com/cooklang/CookCLI) 233 | * [Community alternative command line application](https://github.com/Zheoni/cooklang-chef) 234 | * [Official iOS application](https://cooklang.org/app/) 235 | * [Official Android application](https://cooklang.org/app/) 236 | 237 | 238 | ## Syntax Highlighting 239 | 240 | * [Emacs](https://github.com/cooklang/cook-mode) 241 | * [Nano](https://github.com/le-jun/cooklang.nanorc) 242 | * [SublimeText](https://github.com/cooklang/CookSublime) 243 | * [Vim](https://github.com/luizribeiro/vim-cooklang) 244 | * [VSCode](https://github.com/cooklang/CookVSCode) 245 | * More options: See [syntax highlighting documentation](https://cooklang.org/docs/syntax-highlighting/). 246 | 247 | ## Roadmap 248 | 249 | There's a GitHub board where we show what we're working on and what's next https://github.com/orgs/cooklang/projects/4. 250 | -------------------------------------------------------------------------------- /examples/Coffee Souffle.cook: -------------------------------------------------------------------------------- 1 | -- make caramel 2 | 3 | Crack the @egg yoke{1} into a bowl, then add the @condenced milk{125%g} and @instant coffee{3tsp}, and mix until a nice caramel is formed. Do not allow to bubble. 4 | 5 | Pour into a #bowl and leave to cool down for ~{15%minutes}. 6 | 7 | heat @water{1,1/2cups} and @gelatine{3tsp} then stir till gelatine is dissolved completely.Once gelatine is colled down mix it with the caramel. 8 | 9 | Beat @eggwhite{1} until formy. Then mix the whites bit by bit to the the gelatinecaramel mixture. 10 | 11 | 12 | Pour into a #puddingbowl and leave in fridge for 2 hours minimum. Add some @cashew{60%g} and enjoy! 13 | -------------------------------------------------------------------------------- /examples/Easy Pancakes.cook: -------------------------------------------------------------------------------- 1 | -- TODO add source 2 | 3 | Crack the @eggs{3} into a blender, then add the @flour{125%g}, @milk{250%ml} and @sea salt{1%pinch}, and blitz until smooth. 4 | 5 | Pour into a #bowl and leave to stand for ~{15%minutes}. 6 | 7 | Melt the butter (or a drizzle of @oil if you want to be a bit healthier) in a #large non-stick frying pan{} on a medium heat, then tilt the pan so the butter coats the surface. 8 | 9 | Pour in 1 ladle of batter and tilt again, so that the batter spreads all over the base, then cook for 1 to 2 minutes, or until it starts to come away from the sides. 10 | 11 | Once golden underneath, flip the pancake over and cook for 1 further minute, or until cooked through. 12 | 13 | Serve straightaway with your favourite topping. 14 | -------------------------------------------------------------------------------- /examples/Fried Rice.cook: -------------------------------------------------------------------------------- 1 | -- TODO add source 2 | 3 | Mix together @oyster sauce{1%tbsp}, @soy sauce{5%tbsp} and @sesame oil{5%tsp}, set aside. 4 | 5 | Heat @peanut oil{1%tbsp} in a wok or heavy based skillet/fry pan over medium heat. Add @eggs{2%items} and cook until scrambled, then remove onto plate. 6 | 7 | Increase heat to high and add chopped @bacon{75%cup}, excess fat trimmed. Cook until golden, then remove onto plate with egg - about 2 minutes. 8 | 9 | Drain off excess bacon fat. 10 | 11 | Add remaining @peanut oil{2%tbsp}, minced @garlic{2%gloves} and @ginger{3%tsp}. Return wok to stove on high heat. Stir fry as it comes up to heat, don’t let it burn. When garlic starts to sizzle, add finally chopped @onion{2%medium} and stir fry for 2 minutes until golden. 12 | 13 | Add @Chinese cooking wine{1%tbsp} and @sugar{2%tsp} and let it simmer rapidly, stirring, for 20 seconds, until mostly evaporated. 14 | 15 | Add @prawns{150%g} and stir fry for 1 minute to heat through if pre-cooked, cook longer to cook if raw. 16 | 17 | Add refrigerated overnight @cooked rice{3%cups}, Sauce, eggs, bacon and thinly sliced @scallions{5%items}. Stir fry for 2 minutes until rice is hot - around 2 minutes. 18 | 19 | Transfer to serving plate, sprinkle with thinly sliced @scallions{2%items} and serve. 20 | -------------------------------------------------------------------------------- /examples/Olivier Salad.cook: -------------------------------------------------------------------------------- 1 | -- TODO add source 2 | 3 | Zero step is cook @corn beef{1%kg}. Put into a large pan and simmer for ~{2%hours}. 4 | 5 | The first step is to cook your @potatoes{3%medium} and @carrots{3%medium}. I used a steamer, but you can always go the traditional route and boil them. In either case, peel the carrots but not the potatoes. 6 | 7 | Steam the potatoes for ~{30%minutes} to start with, and then add the peeled carrots. Continue steaming for 10-15 more minutes, or until the potatoes and carrots are firm but tender when poked. 8 | 9 | Meanwhile, cook your @frozen peas{1%cup} according to package directions. I use the kind that can be steamed in the package in the microwave. When they are done, set them aside to cool. 10 | 11 | When the potatoes and carrots are done, allow them to cool to the point that you can handle them easily. 12 | 13 | Peel the potatoes. Using your fingers or the back of a knife, gently scrape the thin layer of skin off of the potatoes. Dice them into 1cm cube-ish shapes and put them into a medium serving bowl. 14 | 15 | Next, dice your carrots. I've heard it said that a Soviet housewife could be judged on her housekeeping skills by how finely she could dice vegetables for her soups and salads. I, however, won't judge you. In fact, if you chop your potatoes and carrots a little larger, I would probably even thank you. I happen to like chunky salads. 16 | 17 | Toss the carrots and a cup of steamed peas into the bowl with the potatoes. 18 | 19 | Peel and dice your hardboiled @eggs{4}. Again, I know some like to have their salads with finely diced ingredients, but I don't. So dice them however you like. 20 | 21 | Chop @pickles{6} finely. I used small snacking dill pickles, so I needed to use six of them. If you have larger pickles, try using three and see if that is enough for you. 22 | 23 | Add the meat if using and mix everything together gently before you add the @mayonnaise{1%cup}. 24 | 25 | Stir in one cup of mayo to start with, and add more if you think that the salad needs more binding together. 26 | 27 | Cover the salad and chill for at least one hour or overnight to allow the flavors to come together. And of course, garnish with finelly chopped @dill{1%tbsp}. This is a Russian salad, after all. 28 | -------------------------------------------------------------------------------- /proposal-templates/0000-spec-proposal-template.md: -------------------------------------------------------------------------------- 1 | # Feature name 2 | 3 | * Proposal: [NNNN-filename](NNNN-filename.md) 4 | * Authors: [Author 1](https://github.com/cooklangdev), [Author 2](https://github.com/cooklangdev) 5 | * Review Manager: TBD 6 | * Status: **Awaiting review** 7 | 8 | *During the review process, add the following fields as needed:* 9 | 10 | * Decision Notes: [Rationale](https://github.com/cooklang/spec/discussions), [Additional Commentary](https://github.com/cooklang/spec/discussions) 11 | * Previous Revision: [1](https://github.com/cooklang/spec/blob/...commit-ID.../proposals/NNNN-filename.md) 12 | * Previous Proposal: [XXXX-filename](XXXX-filename.md) 13 | 14 | ## Introduction 15 | 16 | A short description of what the feature is. Try to keep it to a 17 | single-paragraph "elevator pitch" so the reader understands what 18 | problem this proposal is addressing. 19 | 20 | Discussion thread: [Discussion thread topic for that proposal](https://github.com/cooklang/spec/discussions). 21 | 22 | ## Motivation 23 | 24 | Describe the problems that this proposal seeks to address. If the 25 | problem is that some common pattern is currently hard to express, show 26 | how one can currently get a similar effect and describe its 27 | drawbacks. If it's completely new functionality that cannot be 28 | emulated, motivate why this new functionality would help Cooklang community 29 | use Cooklang better. 30 | 31 | ## Proposed solution 32 | 33 | Describe your solution to the problem. Provide examples and describe 34 | how they work. Show how your solution is better than current 35 | workarounds (if any). 36 | 37 | ## Detailed design 38 | 39 | Describe the design of the solution in detail. If it involves new 40 | syntax in the language, show the additions and changes to the Cooklang 41 | grammar. If it's a new API, show the full API and its documentation 42 | comments detailing what it does. The detail in this section should be 43 | sufficient for someone who is *not* one of the authors to be able to 44 | reasonably implement the feature. 45 | 46 | ## Effect on applications which use Cooklang 47 | 48 | Changes in Cooklang syntax often require supplementary UI changes in 49 | apps which use it (otherwise why these changes if nothing will 50 | use them). Describe in details what changes should be implemented. 51 | The detail in this section should be sufficient for someone 52 | who is *not* one of the authors to be able to reasonably implement 53 | the feature. 54 | 55 | ### CookCLI (terminal and web-server) 56 | 57 | Does the proposal change the output of 58 | [CookCLI](https://github.com/cooklang/CookCLI)? Does it require adding 59 | a new sub-command to fully use this feature? 60 | 61 | ### Mobile applications 62 | 63 | Does the proposal require changes in mobile application UI? Describe 64 | what screens should be changed and how. Give as much details as 65 | possible. 66 | 67 | ## Alternatives considered 68 | 69 | Describe alternative approaches to addressing the same problem, and 70 | why you chose this approach instead. 71 | 72 | ## Acknowledgments 73 | 74 | If significant changes or improvements suggested by members of the 75 | community were incorporated into the proposal as it developed, take a 76 | moment here to thank them for their contributions. Cooklang evolution is a 77 | collaborative process, and everyone's input should receive recognition! 78 | -------------------------------------------------------------------------------- /proposals/0002-add-recipe-introduction-section.md: -------------------------------------------------------------------------------- 1 | # Add Recipe Introduction Section 2 | 3 | * Proposal: [0002-add-recipe-introduction-section](0002-add-recipe-introduction-section.md) 4 | * Authors: [Chris Born](https://github.com/pelted) 5 | * Review Manager: @dubadub 6 | * Status: **Rejected** 7 | 8 | ## Introduction 9 | 10 | This feature would add support for an introduction section to a recipe. In 11 | addition it would facilitate defining ingredients at the top of a `.cook` file. 12 | 13 | Discussion thread: [Description, notes, and 14 | metadata](https://github.com/cooklang/spec/discussions/46). 15 | 16 | ## Motivation 17 | 18 | A title alone is just not enough context to describe a recipe. Often a single 19 | recipe is part of a larger meal plan or tradition. There may be other 20 | side-dishes that pair well with one particular main course; maybe even a wine. 21 | It would be great if we could provide this additional context at the beginning 22 | of a `.cook` file and have it presented in the UI. 23 | 24 | Additionally, recipes found online, or from family cookbooks often define 25 | ingredients near the very beginning. *Cooklang* does this already with the way 26 | it parses ingredients and presents that to the user in the UI. This proposal 27 | would also extend this to the creation of a `.cook` file by providing the choice 28 | of pre-defining ingredients in the introduction section. 29 | 30 | ## Proposed solution 31 | 32 | Description text is prefixed with `==` similar to how comments are already 33 | defined in the spec. Ingredients would be prefixed with the comment syntax of 34 | `--` and ignored as steps. (Currently the processor would consider each of these 35 | ingredients as a step unless prefixed as a comment.) 36 | 37 | ## Detailed design 38 | 39 | **Example:** 40 | 41 | ```cooklang 42 | >> source: https://mexicanfoodjournal.com/easy-salsa-roja/ 43 | >> tags: mexican, salsa 44 | 45 | == A versatile red salsa prepared with tomatoes that works well with many Mexican dishes. 46 | Also pairs well with the [salsa verde] recipe made with tomatillos. 47 | == Make sure that your tomatoes are really red and ripe which gives the salsa better color and a richer flavor. 48 | Really fresh cilantro gives the salsa a nice herbal tang. Avoid tired wilted cilantro because 49 | it has lost much of its flavor. 50 | 51 | -- @roma tomatoes{5} 52 | -- @white onion{1} 53 | -- @serrano chiles{2} 54 | -- @garlic{2%cloves} 55 | -- @cilantro{12%stalks} 56 | 57 | Remove the stems, then slice the @serrano chiles{2} in half and remove the seeds. -- Leave the seeds for more heat. 58 | 59 | -- remaining steps 60 | ``` 61 | 62 | ## Effect on applications which use Cooklang 63 | 64 | The biggest change to the applications would be the presentation of the 65 | introduction text in the user interface. 66 | 67 | ### CookCLI (terminal and web-server) 68 | 69 | This proposal would require a change to the CLI output. A description or 70 | introduction section would be expected to be inserted immediately before the 71 | ingredients. 72 | 73 | ### Mobile applications 74 | 75 | The description test would likely require its own initial "tab" to the left of 76 | "Ingredients" This could also be the initial view for a recipe with the 77 | description under the header image followed by the ingredients list. However, 78 | depending on how much text is here it may be a disservice to users to "push 79 | down" list of ingredients. 80 | 81 | ## Alternatives considered 82 | 83 | 1. A section break defined by `=====`. Anything before this break indicator is 84 | considered part of the *introduction/description* section. Text blocks are 85 | rendered together, and any ingredients defined here are processed as if they 86 | were in the recipe steps. Anything following this section break would be parsed 87 | as usual. 88 | 89 | 2. Description text is prefixed with `==` similar to how comments are already 90 | defined in the spec. Any ingredients defined alone are considered just that 91 | and ignored as steps. (Currently the processor would consider each of these 92 | ingredients as a step.) 93 | 94 | After my initial commit of this proposal I evolved a bit in thinking to the 95 | current proposed implementation. 96 | 97 | ## Acknowledgments 98 | 99 | Just wanted to thank the work that has gone into Cooklang to this point. It 100 | really is a great tool and I look forward to helping it along with some new 101 | ideas. 102 | -------------------------------------------------------------------------------- /proposals/0003-front-matter.md: -------------------------------------------------------------------------------- 1 | # Front Matter 2 | 3 | * Proposal: [0003-front-matter](0003-front-matter.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Released** 6 | * Pull Request: https://github.com/cooklang/spec/pull/98 7 | 8 | * Previous Revision: NA 9 | * Previous Proposal: NA 10 | 11 | ## Introduction 12 | 13 | Idea is to replace the current metadata syntax `>> key: value` with a more 14 | familiar and flexible [YAML](https://yaml.org/spec/1.2.2/) front matter format. 15 | 16 | ## Motivation 17 | 18 | Current metadata syntax is not flexible and very limited in what it can express. 19 | For example, it's impossible to express metadata in a structured way, 20 | like a list of featured ingredients with their quantities and units. 21 | 22 | Also, if key contains characters like `:`, it becomes impossible to 23 | parse it using the current syntax. 24 | 25 | Instead of investing time and effort in inventing yet another ad-hoc 26 | metadata syntax, it's better to use a well-established and flexible format 27 | like YAML or TOML. 28 | 29 | ## Proposed solution 30 | 31 | Proposal is to replace the current metadata syntax `>> key: value` with a 32 | more flexible [YAML](https://yaml.org/spec/1.2.2/) front matter format. Similar to how 33 | [Markdown](https://en.wikipedia.org/wiki/Markdown) files use front matter to 34 | store metadata at the top of the file (for example, title of the page, date, 35 | etc [Hugo docs](https://gohugo.io/content-management/front-matter/)). 36 | 37 | ## Detailed design 38 | 39 | We should still keep the current `>> key: value` syntax for compatibility 40 | with the existing metadata syntax. 41 | 42 | ### YAML 43 | 44 | [YAML](https://yaml.org/spec/1.2.2/) is a human-readable data serialization standard. 45 | 46 | To use YAML front matter, we need to add `---` at the beginning and 47 | `---` at the end of the front matter block. 48 | 49 | ```yaml 50 | --- 51 | title: Spaghetti Carbonara 52 | tags: 53 | - pasta 54 | - quick 55 | - italian 56 | - comfort food 57 | --- 58 | ``` 59 | 60 | ## Effect on applications which use Cooklang 61 | 62 | Main change is in the syntax of the metadata, so no changes are required 63 | in applications which use Cooklang. 64 | 65 | The only problem can be if some applications are using the current metadata 66 | and expect value to be a string. After the change, the value will be not 67 | only a string, but a lists or hashmaps. Also, the value can be a complex 68 | structure with nested lists and hashmaps. 69 | 70 | ### CookCLI (terminal and web-server) 71 | 72 | No changes are required in CookCLI. 73 | 74 | ### Mobile applications 75 | 76 | No changes are required in mobile applications. 77 | 78 | ## Alternatives considered 79 | 80 | Just as a joke, we could use XML or CSV for metadata, but why would anyone 81 | want to use such format for metadata? 82 | 83 | Also, alternative is to put effort and develop on current ad-hoc metadata 84 | syntax, but why would anyone want to do that when syntax is familar to everyone 85 | and parsers are already written? 86 | 87 | We also discussed an option to support multiple formats at the same time (YAML/TOML/JSON) 88 | but decided to stick with YAML only for simplicity. 89 | 90 | ## Acknowledgments 91 | 92 | Idea is taken from a commentary in [this discussion](https://github.com/cooklang/spec/discussions/63#discussioncomment-8735359). 93 | Thanks to [@ringods](https://github.com/ringods) for the idea. 94 | -------------------------------------------------------------------------------- /proposals/0004-multiline-steps.md: -------------------------------------------------------------------------------- 1 | # Multiline steps 2 | 3 | * Proposal: [0004-multiline-steps](0004-multiline-steps.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Released** 6 | 7 | ## Introduction 8 | 9 | The proposal aims to make Cooklang recipes more readable and 10 | git-friendly by treating single line breaks as continuous 11 | text (similar to Markdown), while using double line breaks 12 | to separate distinct recipe steps. 13 | 14 | This allows recipe authors to wrap long lines at around 15 | 80 characters for better readability in text editors and 16 | cleaner git diffs, without creating unintended step breaks. 17 | 18 | It's a simple change that makes recipe writing more natural 19 | while maintaining compatibility with existing applications. 20 | 21 | NOTE! This change will break the existing recipes which use single line breaks 22 | to separate steps. 23 | 24 | Discussion thread: [Use two line breaks for steps](https://github.com/cooklang/spec/discussions/65) and [Support Bullet Points](https://github.com/cooklang/spec/discussions/60). 25 | 26 | ## Motivation 27 | 28 | Single line breaks are generally used in plain text formats like email 29 | and Markdown to keep paragraphs from displaying as one long line on “dumb” 30 | clients like cat. Two line breaks generally indicate a paragraph break. 31 | 32 | Wrapping paragraphs with single line breaks at somewhere 33 | around 80 characters also makes git diffs somewhat nicer to work with. 34 | 35 | ## Proposed solution 36 | 37 | Don’t parse single line breaks as separate steps for the Steps 38 | display in the app. Parse two line breaks as a step break. 39 | 40 | ## Detailed design 41 | 42 | Two examples below show how the same parsed recipe will look before and after the change. 43 | 44 | Before: 45 | 46 | ```cooklang 47 | A step, 48 | next step. 49 | 50 | A different step. 51 | ``` 52 | 53 | After: 54 | 55 | ```cooklang 56 | A step, 57 | the same step. 58 | 59 | A different step. 60 | ``` 61 | 62 | ## Effect on applications which use Cooklang 63 | 64 | The change is purely internal recipe representation and doesn't 65 | require any changes in the apps. If users don't use double newline between 66 | paragraphs, they will need to update their recipes manually. 67 | 68 | ## Alternatives considered 69 | 70 | Describe alternative approaches to addressing the same problem, and 71 | why you chose this approach instead. 72 | 73 | ## Acknowledgments 74 | 75 | The original idea was suggested in email by Liam Hupfer. It was then implemented 76 | by [@Zheoni](https://github.com/Zheoni) as parser extension. 77 | -------------------------------------------------------------------------------- /proposals/0005-note-blocks.md: -------------------------------------------------------------------------------- 1 | # Note blocks 2 | 3 | * Proposal: [0005-note-blocks](0005-note-blocks.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Released** 6 | 7 | ## Introduction 8 | 9 | This proposal outlines a plan to add a new block type to the Cooklang language: the note. 10 | 11 | This feature will allow recipe authors to embed standalone notes in their recipes, 12 | giving them a way to store relevant background, insights, or personal anecdotes 13 | that aren't part of the cooking steps. Notes won’t be displayed during cooking mode, 14 | but they will appear in the recipe details view, providing context without cluttering 15 | the active instructions. 16 | 17 | Discussion thread: ["Additional notes" section which isn't a step, but extra information about a recipe](https://github.com/cooklang/spec/discussions/81) 18 | and [Description, notes, and metadata...](https://github.com/cooklang/spec/discussions/46). 19 | 20 | ## Motivation 21 | 22 | Recipes are more than a sequence of steps; they’re stories, memories, and moments 23 | preserved. Imagine a recipe handed down through generations: a great-grandmother’s 24 | Siberian fish soup, spiced with the unique blend she carried across a journey. 25 | While the specifics of that story might be fictional, the desire to capture memories 26 | like it is very real. 27 | 28 | While these stories don’t change the outcome of a dish, they’re significant. Cooklang, 29 | however, currently lacks an effective way to preserve this kind of narrative 30 | information. Metadata or additional steps can somewhat work, but neither 31 | fits naturally, often cluttering the recipe. A dedicated syntax for 32 | notes will let recipe creators embed these details in a way that keeps 33 | them distinct from the active cooking process. 34 | 35 | ## Proposed solution 36 | 37 | The solution is to introduce a new block type in Cooklang for notes, using 38 | a Markdown-inspired syntax for clarity and simplicity. 39 | 40 | To add a note, authors will simply use a > symbol at the beginning of a paragraph: 41 | 42 | ```cook 43 | > This dish is even better the next day, after the flavors have melded overnight. 44 | ``` 45 | 46 | This approach keeps note-taking lightweight and integrates easily with the language, 47 | while visually signaling that the text is extra information. Notes will be displayed 48 | in the recipe details but hidden during cooking, making them easily accessible 49 | when needed without disrupting the cooking flow. 50 | 51 | 52 | ## Detailed design 53 | 54 | The design introduces a straightforward syntax addition to the Cooklang grammar. 55 | In a recipe file, any line beginning with `>` will be recognized as a note and 56 | rendered as such in applications that support the new syntax. 57 | 58 | Also we should support starting every line with `>`, just like in markdown. For example: 59 | 60 | ```cook 61 | > This is a long note 62 | > and the user writes the `>` at the beginning of 63 | > every line. All the `>` markup is stripped from the 64 | > note "output". 65 | ``` 66 | 67 | This is optional, so it's the same as: 68 | 69 | ```cook 70 | > This is a long note 71 | and the user writes the `>` at the beginning of 72 | every line. All the `>` markup is stripped from the 73 | note "output". 74 | ``` 75 | 76 | It's the same as markdown blockquotes but one paragraph per note block. 77 | In Markdown this would be two paragraphs in the same blockquoute but 78 | in Cooklang it's all the same paragraph, newline is removed. 79 | 80 | ```md 81 | > In markdown 82 | > 83 | > Two paragraphs 84 | ``` 85 | 86 | Unlike Markdown, Cooklang shouldn't support nesting notes inside each other. 87 | 88 | ### EBNF update 89 | 90 | ``` 91 | recipe = { metadata | step | note }- ; 92 | 93 | blank line = { white space }, new line character ; 94 | note marker = ">" ; 95 | note = { note marker, { text item }, blank line }-, blank line ; 96 | ``` 97 | Symbols and definitions are [here](https://github.com/cooklang/spec/blob/main/EBNF.md). 98 | 99 | ## Effect on applications which use Cooklang 100 | 101 | The addition of note syntax will affect the output of CookCLI. No new sub-commands 102 | are needed; however, the recipe parser must recognize and render > blocks as notes, 103 | showing them in the recipe detail view but hiding them during the cooking view. 104 | 105 | ### Mobile applications 106 | 107 | Mobile apps will need UI adjustments to display notes in the recipe details. Notes 108 | should be visually distinct, perhaps styled in italics or a slightly different color, 109 | ensuring they’re recognized as supplementary information. 110 | 111 | ## Alternatives considered 112 | 113 | ### Embedding Notes in Metadata 114 | 115 | Storing notes as metadata is a possible approach, but it lacks the intuitive, 116 | in-line placement that allows notes to appear naturally within the recipe flow. Metadata 117 | is typically reserved for more structured data like prep times or dietary information, 118 | so this could lead to cluttered, less-readable recipes. 119 | 120 | ### Using Special Delimiters in Steps 121 | 122 | Another alternative would be to add an indicator within the steps themselves. However, 123 | since notes are not meant to appear in the cooking flow, this would be counterintuitive 124 | and likely disrupt the cooking experience. The block quote syntax keeps notes separate 125 | and accessible without interference. 126 | 127 | ## Acknowledgments 128 | 129 | The feature has been implemented by [@Zheoni](https://github.com/Zheoni) as parser extension. 130 | -------------------------------------------------------------------------------- /proposals/0006-sections.md: -------------------------------------------------------------------------------- 1 | # Sections 2 | 3 | * Proposal: [0006-sections](0006-sections.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Released** 6 | 7 | ## Introduction 8 | 9 | This proposal aims to introduce a new block type to the Cooklang language called "section." Sections a common structure blocks within recipes, allowing users to group and organize content more intuitively. By breaking recipes into named, clearly defined segments, the Cooklang language can improve readability, usability, and content structure, especially for more complex recipes. 10 | 11 | **Discussion threads**: [Break down recipe instructions further](https://github.com/cooklang/spec/discussions/59), [Functionality for splitting into components and listing component ingredients](https://github.com/cooklang/spec/discussions/72), and [Description, notes, and metadata...](https://github.com/cooklang/spec/discussions/46). 12 | 13 | **Extensions link**: [Extensions](https://github.com/cooklang/cooklang-rs/blob/main/extensions.md#section-block) 14 | 15 | 16 | ## Motivation 17 | 18 | In its current form, Cooklang organises recipe instructions and ingredients linearly, limiting the ability to express hierarchical or segmented recipe components. For complex recipes that involve separate steps (e.g., "Dough," "Filling," "Topping") or variations within a recipe (such as optional garnish sections), a flat format can make the instructions harder to follow. 19 | 20 | In these cases, while part of a single recipe, sections could be prepared separately—for instance, making dough the night before to allow it to rest. This means that when working on the dough, the user only needs to gather the ingredients related to that section. 21 | 22 | Currently, users can attempt to work around this by manually adding section titles to the instructions, but this approach introduces extra steps and does not allow for selecting section-specific ingredients easily. 23 | 24 | By introducing a formal syntax for sections, we can: 25 | 26 | 1. Improve the readability and maintainability of complex recipes. 27 | 2. Enable the preparation of specific parts of a recipe independently. 28 | 3. Lay the foundation for future features, such as cross-referencing sections. 29 | 30 | In summary, adding sections provides a structured approach to organising complex recipes, making them easier to write, read, and implement across Cooklang-compatible applications. 31 | 32 | ## Proposed solution 33 | 34 | We propose introducing a new syntax to Cooklang that defines sections as explicit blocks. The syntax would look like this: 35 | 36 | ```cooklang 37 | = Dough 38 | 39 | Mix @flour{200%g} and @water{50%ml} together until smooth. 40 | 41 | == Filling == 42 | 43 | Combine @cheese{100%g} and @spinach{50%g}, then season to taste. 44 | ``` 45 | 46 | In this example, each `= Section` header represents a distinct logical group within the recipe. The number of `=` characters does not matter, and the closing `=` is optional. The text of the section title is not parsed for ingredients or cookware. 47 | 48 | A section ends either when the next section begins or at the end of the file. 49 | 50 | 51 | ## Detailed design 52 | 53 | The new syntax for sections introduces `=` as a section identifier repeated at least once, followed by the optional section name. Optionally users can mark end of section with `=` repetaed at least once. The `= Section` header will be parsed as a block header, and any steps that follow belong to that section until a new section is defined or the file ends. 54 | 55 | ### Grammar Changes 56 | 57 | In Cooklang grammar, sections would be defined as: 58 | ``` 59 | section title = { '=' }-, { text item }, { '=' }; 60 | section content = { step }-; 61 | ``` 62 | 63 | Sections are closed either by a new section header or the end of the recipe file. 64 | 65 | ### Example 66 | 67 | Consider the following recipe: 68 | 69 | ```cooklang 70 | == Dough == 71 | 72 | Mix @flour{200%g} and @water{50%ml} together until smooth. 73 | 74 | == Filling == 75 | 76 | Combine @cheese{100%g} and @spinach{50%g} and season to taste. 77 | 78 | == Assembly == 79 | Roll out dough and spread filling on top. 80 | ``` 81 | 82 | This syntax will create three sections, each with a title and content. The Cooklang parser can use this hierarchy to organize recipe steps, which UI applications can display accordingly. 83 | 84 | 85 | ## Effect on applications which use Cooklang 86 | 87 | ### CookCLI (terminal and web-server) 88 | 89 | The addition of sections to Cooklang would improve the functionality of CookCLI, offering improved parsing and output options. With sections, recipes could display a hierarchical structure, making the output more readable and navigable for complex recipes. Key enhancements include: 90 | 91 | - Sections can generate a table of contents for easier navigation in lengthy recipes. 92 | - Sections would support recipes that involve independent preparation processes, such as multiple dishes cooked in parallel. 93 | - Ingredients listed under sections improve clarity by associating them directly with the relevant steps. 94 | - Parallel cooking steps can be grouped into sections, aiding workflows that require simultaneous preparation. 95 | 96 | ### Mobile applications 97 | 98 | Mobile applications will need UI updates to accommodate sections. These updates would include: 99 | 100 | - Displaying sections in recipe view with clear headers improves readability and provides users with an overview of the recipe’s structure. 101 | - Grouping ingredients by section in recipe view improves user experience when cooking parts of recipe. 102 | - Improved UI handling of sections supports seamless interaction for recipes with separate preparation steps or simultaneous tasks. 103 | 104 | 105 | ## Alternatives considered 106 | 107 | 1. Using Inline Titles Only. Allow users to simply add titles inline without sectioning syntax. This would avoid grammar changes but wouldn’t provide the parsing structure or UI flexibility that a formalized section syntax can offer. 108 | 109 | 2. Markdown-Style Sections. Another option was to use a Markdown-style header (e.g., `# Dough`). However, this approach would conflict with existing cookware syntax. Also 110 | 111 | 3. Component-Based Approach. Sections could have been represented as "components" in separate recipe files. However, "sections" provide a more universally understood term for users, focusing more on organization than on logical "components" or dependencies. 112 | 113 | 114 | ## Acknowledgments 115 | 116 | The syntax is inspired by [wiki markup](https://en.wikipedia.org/wiki/Help:Section). The feature has been implemented by [@Zheoni](https://github.com/Zheoni) as a parser extension. 117 | -------------------------------------------------------------------------------- /proposals/0007-canonical-metadata.md: -------------------------------------------------------------------------------- 1 | # Canonical metadata 2 | 3 | * Proposal: [0007-canonical-metadata](0007-canonical-metadata.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Released** 6 | 7 | ## Introduction 8 | 9 | Canonical metadata provides a standardised way to attach key information to recipes in Cooklang. This proposal aims to establish a core set of metadata keys, allowing consistent and machine-readable annotations. These enhancements will simplify recipe organisation, sharing, and usability across tools while maintaining Cooklang's minimalist philosophy. 10 | 11 | Discussion threads: 12 | - [Add known/standard metadata keys to documentation](https://github.com/cooklang/spec/discussions/101) 13 | - [Description, notes, and metadata...](https://github.com/cooklang/spec/discussions/46) 14 | - ["Additional notes" section which isn't a step, but extra information about a recipe](https://github.com/cooklang/spec/discussions/81) 15 | - [Use metadata for recipe picture URL](https://github.com/cooklang/spec/discussions/64) 16 | 17 | Relevant extensions: [Special Metadata](https://github.com/cooklang/cooklang-rs/blob/main/extensions.md#special-metadata) 18 | 19 | ## Motivation 20 | 21 | Recipes are more than just steps and ingredients—they also include context, such as preparation times, authorship, and dietary relevance. While metadata can be approximated through custom fields today, this approach leads to fragmentation and ambiguity. For instance: 22 | - Different users might define preparation time as `prep time`, `time required`, or `duration`. 23 | - There is no convention for structured data, making automation (e.g., scaling servings or sorting by difficulty) more challenging. 24 | 25 | This proposal seeks to unify and simplify how metadata is defined and consumed, fostering interoperability between tools and improving the user experience for both developers and end-users. 26 | 27 | ## Proposed solution 28 | 29 | Introduce a standard set of metadata keys with clear purposes and formatting rules. These keys will be optional but encouraged for consistency. Below is the proposed table of metadata: 30 | 31 | | Key | Purpose | Example value | 32 | | --- | --- | --- | 33 | | `source`, `source.name` | Where the recipe came from. Usually a URL, can also be text (eg. a book title). | `https://example.org/recipe`, `The Palomar Cookbook `, `mums` | 34 | | `author`, `source.author` | The author of the recipe. | `John Doe` | 35 | | `source.url`|The URL of the recipe if nested format is used.|`https://example.org/recipe`| 36 | | `servings`, `serves`, `yield` | Indicates how many people the recipe is for. Used for scaling quantities. Leading number is used for scaling, anything else is ignored but shown as units. | `2`,`15 cups worth` | 37 | | `course`, `category` | Meal category or course. | `dinner` | 38 | | `locale` | The locale of the recipe. Used for spelling/grammar during edits, and for pluralisation of amounts. Uses ISO 639 language code, then optionally an underscore and the ISO 3166 alpha2 "country code" for dialect variants | `es_VE`, `en_GB`, `fr` | 39 | | `time required`, `time` or `duration` | The preparation + cook time of the recipe. Various formats can be parsed, if in doubt use `HhMm` format to avoid plurals and locales. | `45 minutes`, `1 hour 30 minutes`,`1h30m` | 40 | | `prep time`, `time.prep`|Time for preparation steps only.|`2 hour 30 min`| 41 | | `cook time`, `time.cook`|Time for actual cooking steps.|`10 minutes`| 42 | | `difficulty`|Recipe difficulty level.|`easy`| 43 | | `cuisine`|The cuisine of the recipe.|`French`| 44 | | `diet`|Indicates a dietary restriction or guideline for which this recipe or menu item is suitable, e.g. diabetic, halal etc.|`gluten-free`, or array of values| 45 | | `tags`|List of descriptive tags.|`[2022, baking, summer]`| 46 | | `image`, `images`, `picture`, `pictures`|URL to a recipe image.|`https://example.org/recipe_image.jpg` or array of URLs| 47 | | `title`|Title of the recipe.|`Uzbek Manti`| 48 | | `introduction`, `description`|Additional notes about the recipe.|`This recipe is a traditional Uzbek dish that is made with a variety of vegetables and meat.`| 49 | 50 | ## Detailed design 51 | 52 | The front-matter section in Cooklang files will allow key-value pairs as structured metadata. Keys are case-insensitive. Latest conflicting declaration takes precedence. 53 | 54 | ## Effect on applications which use Cooklang 55 | 56 | ### CookCLI (terminal and web-server) 57 | 58 | CookCLI might implement a command to list recipes filtered by these standard metadata values. Also the web-server can display canonical metadata with fancy icons. 59 | 60 | ### Mobile applications 61 | 62 | Mobile apps should allow users to search and filter recipes by these canonical metadata values. Also they can display canonical metadata with icons in recipe detail screen. 63 | 64 | ## Alternatives considered 65 | 66 | We try to provide synonyms support, so users can guess most of the time what the value should be. 67 | 68 | ## Acknowledgments 69 | 70 | All discussion thread participants: 71 | - [Add known/standard metadata keys to documentation](https://github.com/cooklang/spec/discussions/101) 72 | - [Description, notes, and metadata...](https://github.com/cooklang/spec/discussions/46) 73 | - ["Additional notes" section which isn't a step, but extra information about a recipe](https://github.com/cooklang/spec/discussions/81) 74 | - [Use metadata for recipe picture URL](https://github.com/cooklang/spec/discussions/64) 75 | -------------------------------------------------------------------------------- /proposals/0008-mise-en-place.md: -------------------------------------------------------------------------------- 1 | # Mise en place 2 | 3 | * Proposal: [0008-mise-en-place](0008-mise-en-place.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Released** 6 | 7 | 8 | ## Introduction 9 | 10 | In professional kitchens, ["mise en place"](https://en.wikipedia.org/wiki/Mise_en_place) (everything in its place) is a core principle. Similarly, in Cooklang, we can optimise the workflow by introducing a shorthand syntax for ingredients, allowing recipes to be written more concisely. By enabling this syntax, users can avoid repetitive and redundant instructions, improving readability and ease of use. 11 | 12 | Discussion thread: [Allow for short hand preparations in ingredients list to not double count](https://github.com/cooklang/spec/discussions/57) and [Ingredient variants shorthand](https://github.com/cooklang/spec/discussions/74). 13 | 14 | Extensions link: [Extensions](https://github.com/cooklang/cooklang-rs/blob/main/extensions.md#component-note) 15 | 16 | ## Motivation 17 | 18 | One of Cooklang's main goals is to create a recipe format that is both human-readable and easy to write. However, many recipes require the repetition of common ingredient preparations (such as peeling, chopping, or marinating). This redundancy can result in unnecessarily lengthy instructions. For instance, the following two lines: 19 | 20 | ```cooklang 21 | Peel and finely chop @onion{1}. Peel and mince @garlic{2%cloves}. 22 | 23 | Mix onion and garlic into paste. 24 | ``` 25 | 26 | While clear, the repetition of ingredient preparations creates clutter. A more efficient approach would be to allow shorthand notation for ingredient preparations, enabling us to condense the recipe while maintaining clarity. 27 | 28 | ## Proposed solution 29 | 30 | The solution involves allowing users to define common ingredient preparations within the ingredient reference itself, using a shorthand syntax. This means that instead of writing out detailed preparation instructions multiple times, users can append these instructions directly to the ingredient references. This reduces redundancy without losing any important information. 31 | 32 | For example: 33 | 34 | **Current syntax:** 35 | 36 | ```cooklang 37 | Peel and finely chop @onion{1}. Peel and mince @garlic{2%cloves}. 38 | 39 | Mix onion and garlic into paste. 40 | ``` 41 | 42 | **Shorthand syntax (proposed):** 43 | 44 | ```cooklang 45 | Mix @onion{1}(peeled and finely chopped) and @garlic{2%cloves}(peeled and minced) into paste. 46 | ``` 47 | 48 | In this approach, the preparations are attached to the ingredient references, reducing the need to repeat them. 49 | 50 | ## Detailed design 51 | 52 | The Cooklang grammar would need to support this new shorthand syntax by parsing the parentheses following an ingredient reference as part of the ingredient specification. 53 | 54 | We shouldn't parse shorthand preparations if there's a whitespace between ingredient reference and parentheses. 55 | 56 | ## Effect on applications which use Cooklang 57 | 58 | ### CookCLI (terminal and web-server) 59 | 60 | CookCLI recipe output should contain ingredients with preparations. 61 | 62 | ``` 63 | Ingredients: 64 | garlic, peeled and minced 3 cloves 65 | onion, peeled and finely chopped 1 66 | ... 67 | ``` 68 | 69 | Similar change is required for the web-server. 70 | 71 | ### Mobile applications 72 | 73 | Mobile apps will need UI adjustments to extra ingredient information in the recipe details. Also it would be beneficial to add an extra cooking step before all the steps where we request the user to gather and prepare required ingredients. 74 | 75 | 76 | ## Alternatives considered 77 | 78 | 1. Add it inside quantity block after units using comma: 79 | 80 | ``` 81 | When the sauce has thickened, stir in the @fresh basil{1/4%cup, chopped}. 82 | ``` 83 | 84 | or pipe: 85 | 86 | ``` 87 | Combine @oats{6%cups|steel-cut}, @raisins{1%cup|golden}, and @coconut{2%cups|shredded} in a #bowl. 88 | ``` 89 | 90 | This approach may conflict with a future servings proposal where we need to use multiple quantities. 91 | 92 | ## Acknowledgments 93 | 94 | Special thanks to the community members who have contributed to discussions about shorthand ingredient preparation. The feature has been implemented by [@Zheoni](https://github.com/Zheoni) as parser extension. 95 | -------------------------------------------------------------------------------- /proposals/0010-servings.md: -------------------------------------------------------------------------------- 1 | # Servings 2 | 3 | * Proposal: [0010-servings](0010-servings.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Accepted, in implementation** 6 | 7 | ## Introduction 8 | 9 | This proposal introduces a standardized way to specify serving sizes in recipes and scale 10 | ingredient quantities appropriately. It allows recipe authors to define supported 11 | serving sizes and automatically or manually adjust ingredient quantities, making 12 | it easier for users to adapt recipes to their needs. 13 | 14 | Discussion thread: [Is servings supported?](https://github.com/cooklang/spec/discussions/70) 15 | and [Default serving size](https://github.com/cooklang/spec/discussions/66). 16 | 17 | ## Motivation 18 | 19 | Currently, Cooklang lacks a standardized way to handle recipe scaling 20 | based on serving sizes. This leads to several issues: 21 | 22 | 1. Users need to manually calculate ingredient quantities when scaling recipes up or down 23 | 2. Recipe authors can't specify non-linear scaling relationships for ingredients 24 | 3. Applications can't automatically adjust quantities based on desired servings 25 | 26 | These limitations make it harder for users to adapt recipes to their needs 27 | and for applications to provide serving-related features. 28 | 29 | ## Proposed solution 30 | 31 | The solution utilizes two main components: 32 | 33 | 1. A metadata tag for defining supported serving sizes (introduced in 34 | https://github.com/cooklang/spec/blob/main/proposals/0007-canonical-metadata.md): 35 | 36 | ```cooklang 37 | --- 38 | servings: 2 39 | --- 40 | ``` 41 | 42 | If metadata tag isn't present we assume that servings is 1. The ingredients in a recipe are 43 | scaled to this specified servings size. 44 | 45 | 2. Three ways to specify ingredient scaling: 46 | 47 | a. Default linear scaling. Regular Cooklang recipe should scale each ingredient linearly: 48 | 49 | ```cooklang 50 | Add @milk{1/2%cup} and mix until smooth. 51 | ``` 52 | 53 | This will use defined servings and rescale appropriately. Imagine having this recipe: 54 | 55 | ```cooklang 56 | --- 57 | servings: 2 58 | --- 59 | 60 | Add @milk{1/2%cup} and mix until smooth. 61 | ``` 62 | 63 | To make it scaled to 4 servings we just linearly multiply each ingredient to ratio 64 | of desired serving to default serving. 65 | 66 | ```cooklang 67 | -- ratio = 2 (4 desired servings / 2 default servings) 68 | -- multiply milk quanity by ratio 2 69 | 70 | Add @milk{1%cup} and mix until smooth. 71 | ``` 72 | 73 | b. Unscaled. Sometimes we need to lock ingredient amount to one value 74 | 75 | ```cooklang 76 | --- 77 | servings: 2 78 | --- 79 | 80 | Add @milk{=1%cup} and mix until smooth. 81 | ``` 82 | 83 | All these options can be mixed together in one recipe. 84 | 85 | ### Baking percentages 86 | 87 | In this setup baking percentages should just work. 88 | 89 | ```cooklang 90 | Mix @flour{1000%g} and @water{600%g}. 91 | ``` 92 | 93 | Users should be able to calculate ratio factor knowing how much flour they want to use. Ratio = 600 / 1000 = 0.6. 94 | 95 | ### Timers 96 | 97 | Timers don't scale. Seems that in most cases portion size stays the same 98 | and hence cooking time should stay the same. Of course there're recipes 99 | that need timer scaling for example turkey roasting, but in that case we can 100 | leave it up to user to set the proper timer. 101 | 102 | ### Cookware 103 | 104 | Cookware doesn't scale. 105 | 106 | ## Detailed design 107 | 108 | ### Metadata Tag Syntax 109 | 110 | The servings metadata tag follows this format: 111 | ``` 112 | >> servings: number 113 | ``` 114 | 115 | ### Ingredient Scaling Syntax 116 | 117 | 1. Linear scaling 118 | ``` 119 | @ingredient{quantity%unit} 120 | ``` 121 | Nothing required. Default recipe should scale linearly. 122 | 123 | 2. Pinned ingredients: 124 | ``` 125 | @ingredient{=quantity%unit} 126 | ``` 127 | - Ingredients quanitites locked with `=` scaling notation maintain 128 | the same quantity for all serving sizes. 129 | 130 | ## Effect on applications which use Cooklang 131 | 132 | ### CookCLI (terminal and web-server) 133 | 134 | CookCLI should be updated to: 135 | 1. Add a `--servings` flag to specify desired serving size on recipe 136 | read and shopping list generation 137 | 2. Include serving information in JSON output 138 | 3. Automatically calculate scaled quantities in recipe output 139 | 140 | Example CLI usage: 141 | ```bash 142 | cook recipe read recipe.cook --servings 4 143 | ``` 144 | 145 | ### Mobile applications 146 | 147 | Mobile apps should add: 148 | 149 | 1. Recipe View Changes: 150 | - Add serving size selector 151 | - Display current serving size 152 | - Show scaled ingredient quantities 153 | 154 | 2. Recipe List Changes: 155 | - Show default serving size in recipe previews 156 | 157 | 3. Shopping List Changes: 158 | - Show serving size in shopping list 159 | - Show scaled quantities in shopping list 160 | 161 | ## Alternatives considered 162 | 163 | Originally thinking about having manual scaling with explicit quantities 164 | for each serving size. We can give supported servings and in each ingredient specify manual amounts. 165 | 166 | ```cooklang 167 | --- 168 | servings: 2|4|6 169 | --- 170 | 171 | Add @milk{1|1.5|2%cup} and mix until smooth. 172 | ``` 173 | 174 | Linear scaling covers most common cases, while manual scaling handles exceptions where 175 | ingredients don't scale linearly. 176 | 177 | I don't think there would be usage for this, but amount of complexity it adds is too high. 178 | Also we would need to show users if metadata doesn't match the number of quantities in the recipe. 179 | 180 | In the future we might go extra mile an provide way to specify scaling function that 181 | will calculate scaling when non-linear scaling is needed. 182 | 183 | ## Acknowledgments 184 | 185 | Thanks to the Cooklang community members who participated in the discussions about servings 186 | support and default serving sizes, particularly in GitHub discussions [Is servings supported?](https://github.com/cooklang/spec/discussions/70) 187 | and [Default serving size](https://github.com/cooklang/spec/discussions/66). Their input 188 | helped shape this proposal into a more robust and user-friendly solution. Also huge thanks to 189 | [@Zheoni](https://github.com/Zheoni) for implementing this feature. 190 | -------------------------------------------------------------------------------- /proposals/0015-other-recipes.md: -------------------------------------------------------------------------------- 1 | # Reference other recipes 2 | 3 | * Proposal: [0015-other-recipes](0015-other-recipes.md) 4 | * Authors: [Alexey Dubovskoy](https://github.com/dubadub) 5 | * Status: **Accepted, in implementation** 6 | 7 | ## Introduction 8 | 9 | This proposal introduces the ability to reference ingredients that are essentially 10 | other recipes using relative file paths. This feature allows recipes to be modular 11 | and reusable, enabling cooks to maintain base recipes (like sauces, dressings, or 12 | common components) that can be referenced from multiple other recipes. 13 | 14 | Discussion threads: 15 | - [Include other recipes](https://github.com/cooklang/spec/discussions/55) 16 | - [Link/Include one recipe in another?](https://github.com/cooklang/spec/discussions/117) 17 | 18 | ## Motivation 19 | 20 | Currently, Cooklang doesn't have a built-in way to reference other recipes. This 21 | leads to several issues: 22 | 23 | 1. Recipe duplication - Common components like sauces or marinades need to be 24 | copied into each recipe that uses them 25 | 2. Maintenance challenges - When a base recipe is updated, all copies need to be 26 | manually updated 27 | 3. Limited organization capabilities - Cannot create meta-recipes or meal plans 28 | (like "Thanksgiving Dinner" or "Weekly Meal Prep") that reference multiple 29 | individual recipes with their respective quantities 30 | 31 | ## Proposed solution 32 | 33 | Add support for referencing other recipes using the existing @ ingredient syntax, 34 | inferring relative file paths from the recipe name: 35 | 36 | ```cooklang 37 | Add @./Pesto Sauce{100%g} and stir. 38 | Add @../sauces/Bechamel{200%ml} to the pan. 39 | Add @./Hollandaise{150%g} and mix well. 40 | ``` 41 | 42 | When the parser detects a relative path, it marks the ingredient as a reference 43 | that can be parsed separately. 44 | 45 | ## Detailed design 46 | 47 | Two main approaches have been considered for recipe references: 48 | 49 | ### Approach 1: Special Recipe Reference Syntax 50 | 51 | This approach is implemented as a parser extension: 52 | 53 | ```cooklang 54 | Add @@Boiled Icing{6%tsp} to the cake. 55 | ``` 56 | 57 | **Advantages:** 58 | - Works in non-filesystem environments (e.g., databases) 59 | - More sharing-friendly as it doesn't assume specific directory structures 60 | - Simpler for non-technical users who don't understand file paths 61 | - Allows for flexible organization as recipes can be reorganized without updating 62 | references 63 | 64 | **Disadvantages:** 65 | - Requires recipe lookup/resolution rules to be defined 66 | - May be ambiguous when multiple recipes have the same name 67 | - Introduces new syntax that may not be immediately intuitive 68 | - Requires clear documentation about lookup behavior 69 | 70 | ### Approach 2: File-based References (Current Proposal) 71 | 72 | ```cooklang 73 | Add @./Boiled Icing{6%tsp} to the cake. 74 | Add @../sauces/Bechamel{200%ml} to the pan. 75 | ``` 76 | 77 | **Advantages:** 78 | - Explicit and unambiguous about recipe location 79 | - Follows familiar file system patterns 80 | - No inference required about recipe location 81 | - Consistent with how other systems handle file references 82 | 83 | **Disadvantages:** 84 | - References break when files are moved 85 | - Less suitable for database-backed implementations 86 | - Requires understanding of relative file paths 87 | - May make recipe sharing more complex due to assumed directory structures 88 | 89 | After careful consideration, this proposal recommends the file-based approach 90 | (Approach 2) because: 91 | 1. It maintains clarity and explicitness about recipe locations 92 | 2. It follows the Unix philosophy of being explicit rather than magical 93 | 3. It allows for clear error messages when recipes can't be found 94 | 4. It enables static analysis of recipe dependencies 95 | 96 | The parser will: 97 | 1. Identify recipe references starting with `@./`, `@../` or Windows `@.\`, `@..\` 98 | 2. Mark these ingredients as references and store their relative paths 99 | 3. Set the ingredient name as the file stem of the reference 100 | 101 | The parser will not: 102 | 1. Validate that the relative path exists 103 | 2. Load and parse the referenced recipe 104 | 3. Apply the specified quantity to the referenced recipe 105 | 106 | These responsibilities are left to the parser caller. 107 | 108 | ## Effect on Applications Using Cooklang 109 | 110 | Applications should display a link to referenced recipes in ingredient lists. For 111 | shopping lists, applications need to recursively apply quantities and extract 112 | ingredients from referenced recipes. 113 | 114 | ### CookCLI (Terminal and Web-server) 115 | 116 | CookCLI needs to be updated to: 117 | 1. Output an extra note indicating that an ingredient is a reference and its path 118 | 2. Adjust shopping list generation to include ingredients from referenced recipes 119 | 120 | ### Mobile Applications 121 | 122 | Mobile apps need to: 123 | 1. Add UI elements to indicate referenced recipes with links 124 | 2. Update shopping list generation to include referenced recipe ingredients 125 | 3. Allow users to navigate to referenced recipes 126 | 4. Provide visual feedback when referenced recipes cannot be found 127 | 128 | ### Implementation Considerations 129 | 130 | When implementing recipe references, systems should: 131 | 1. Maintain a cache of recipe locations to avoid repeated filesystem traversal 132 | 2. Provide clear error messages for missing or circular references 133 | 3. Provide tools for finding broken references when recipes are reorganized 134 | 135 | ## Alternatives Considered 136 | 137 | 1. Separate syntax for referencing recipes (`@@recipe_name{quantity}`): 138 | While more consistent with existing ingredient syntax, this would be more 139 | verbose and harder to read. 140 | 141 | 2. Using unique IDs instead of file paths: 142 | This would make recipes less human-readable and harder to manage manually. 143 | 144 | 3. Using an include-style syntax (like `!include`): 145 | The `@` syntax was chosen as it's more consistent with Cooklang's existing 146 | conventions for ingredients. 147 | 148 | ## Acknowledgments 149 | 150 | Thanks to the Cooklang community members who provided feedback on the relative path 151 | syntax and cross-platform compatibility considerations. Special thanks to 152 | [@Zheoni](https://github.com/Zheoni) for implementing the extension that preceded 153 | this feature. 154 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Canonical tests for parsers 2 | 3 | ## Datastructure 4 | 5 | Every tests looks like this: 6 | 7 | ``` 8 | testBasicDirection: ‹ 1 9 | source: | ‹ 2 10 | Add a bit of chilli 11 | result: ‹ 3 12 | steps: 13 | - 14 | - type: text ‹ 4 15 | value: "Add a bit of chilli" 16 | metadata: ‹ 5 17 | "cooking time": 30 mins 18 | ``` 19 | 20 | Key components: 21 | 22 | 1. Test name. It gives an idea what feature this test is testing and can be useful for debugging. 23 | 2. Source. Recipe text file as it'd provided by a user. 24 | 3. Result. Expected result of parsing. It consists of multiple steps and metadata. 25 | 4. Direction item. Every step consists of multiple direction items. There're four types: 26 | * `text` has `value` which contains text used in step. 27 | * `ingredient` has `name`, `quantity` and `units`. 28 | * `cookware` has `name` and `quantity`. 29 | * `timer` has `name`, `quantity` and `units`. 30 | 5. Metadata is in key/value form. 31 | 32 | Example usage in https://github.com/cooklang/CookInSwift. 33 | -------------------------------------------------------------------------------- /tests/canonical.yaml: -------------------------------------------------------------------------------- 1 | version: 7 2 | tests: 3 | testBasicDirection: 4 | source: | 5 | Add a bit of chilli 6 | result: 7 | steps: 8 | - 9 | - type: text 10 | value: "Add a bit of chilli" 11 | metadata: {} 12 | 13 | 14 | testComments: 15 | source: | 16 | -- testing comments 17 | result: 18 | steps: [] 19 | metadata: {} 20 | 21 | 22 | testCommentsAfterIngredients: 23 | source: | 24 | @thyme{2%sprigs} -- testing comments 25 | and some text 26 | result: 27 | steps: 28 | - 29 | - type: ingredient 30 | name: "thyme" 31 | quantity: 2 32 | units: "sprigs" 33 | - type: text 34 | value: " and some text" 35 | metadata: {} 36 | 37 | 38 | testCommentsWithIngredients: 39 | source: | 40 | -- testing comments 41 | @thyme{2%sprigs} 42 | result: 43 | steps: 44 | - 45 | - type: ingredient 46 | name: "thyme" 47 | quantity: 2 48 | units: "sprigs" 49 | metadata: {} 50 | 51 | 52 | testDirectionsWithDegrees: 53 | source: | 54 | Heat oven up to 200°C 55 | result: 56 | steps: 57 | - 58 | - type: text 59 | value: "Heat oven up to 200°C" 60 | metadata: {} 61 | 62 | 63 | testDirectionsWithNumbers: 64 | source: | 65 | Heat 5L of water 66 | result: 67 | steps: 68 | - 69 | - type: text 70 | value: "Heat 5L of water" 71 | metadata: {} 72 | 73 | 74 | testDirectionWithIngredient: 75 | source: | 76 | Add @chilli{3%items}, @ginger{10%g} and @milk{1%l}. 77 | result: 78 | steps: 79 | - 80 | - type: text 81 | value: "Add " 82 | - type: ingredient 83 | name: "chilli" 84 | quantity: 3 85 | units: "items" 86 | - type: text 87 | value: ", " 88 | - type: ingredient 89 | name: "ginger" 90 | quantity: 10 91 | units: "g" 92 | - type: text 93 | value: " and " 94 | - type: ingredient 95 | name: "milk" 96 | quantity: 1 97 | units: "l" 98 | - type: text 99 | value: "." 100 | metadata: {} 101 | 102 | 103 | testEquipmentMultipleWords: 104 | source: | 105 | Fry in #frying pan{} 106 | result: 107 | steps: 108 | - 109 | - type: text 110 | value: "Fry in " 111 | - type: cookware 112 | name: "frying pan" 113 | quantity: 1 114 | metadata: {} 115 | 116 | 117 | testEquipmentMultipleWordsWithLeadingNumber: 118 | source: | 119 | Fry in #7-inch nonstick frying pan{ } 120 | result: 121 | steps: 122 | - 123 | - type: text 124 | value: "Fry in " 125 | - type: cookware 126 | name: "7-inch nonstick frying pan" 127 | quantity: 1 128 | metadata: {} 129 | 130 | 131 | testEquipmentMultipleWordsWithSpaces: 132 | source: | 133 | Fry in #frying pan{ } 134 | result: 135 | steps: 136 | - 137 | - type: text 138 | value: "Fry in " 139 | - type: cookware 140 | name: "frying pan" 141 | quantity: 1 142 | metadata: {} 143 | 144 | 145 | testEquipmentOneWord: 146 | source: | 147 | Simmer in #pan for some time 148 | result: 149 | steps: 150 | - 151 | - type: text 152 | value: "Simmer in " 153 | - type: cookware 154 | name: "pan" 155 | quantity: 1 156 | - type: text 157 | value: " for some time" 158 | metadata: {} 159 | 160 | 161 | testEquipmentQuantity: 162 | source: | 163 | #frying pan{2} 164 | result: 165 | steps: 166 | - 167 | - type: cookware 168 | name: "frying pan" 169 | quantity: 2 170 | metadata: {} 171 | 172 | 173 | testEquipmentQuantityOneWord: 174 | source: | 175 | #frying pan{three} 176 | result: 177 | steps: 178 | - 179 | - type: cookware 180 | name: "frying pan" 181 | quantity: three 182 | metadata: {} 183 | 184 | 185 | testEquipmentQuantityMultipleWords: 186 | source: | 187 | #frying pan{two small} 188 | result: 189 | steps: 190 | - 191 | - type: cookware 192 | name: "frying pan" 193 | quantity: two small 194 | metadata: {} 195 | 196 | 197 | testFractions: 198 | source: | 199 | @milk{1/2%cup} 200 | result: 201 | steps: 202 | - 203 | - type: ingredient 204 | name: "milk" 205 | quantity: 0.5 206 | units: "cup" 207 | metadata: {} 208 | 209 | 210 | testFractionsInDirections: 211 | source: | 212 | knife cut about every 1/2 inches 213 | result: 214 | steps: 215 | - 216 | - type: text 217 | value: "knife cut about every 1/2 inches" 218 | metadata: {} 219 | 220 | 221 | testFractionsLike: 222 | source: | 223 | @milk{01/2%cup} 224 | result: 225 | steps: 226 | - 227 | - type: ingredient 228 | name: "milk" 229 | quantity: "01/2" 230 | units: "cup" 231 | metadata: {} 232 | 233 | 234 | testFractionsWithSpaces: 235 | source: | 236 | @milk{1 / 2 %cup} 237 | result: 238 | steps: 239 | - 240 | - type: ingredient 241 | name: "milk" 242 | quantity: 0.5 243 | units: "cup" 244 | metadata: {} 245 | 246 | 247 | testIngredientMultipleWordsWithLeadingNumber: 248 | source: | 249 | Top with @1000 island dressing{ } 250 | result: 251 | steps: 252 | - 253 | - type: text 254 | value: "Top with " 255 | - type: ingredient 256 | name: "1000 island dressing" 257 | quantity: "some" 258 | units: "" 259 | metadata: {} 260 | 261 | 262 | testIngredientWithEmoji: 263 | source: | 264 | Add some @🧂 265 | result: 266 | steps: 267 | - 268 | - type: text 269 | value: "Add some " 270 | - type: ingredient 271 | name: "🧂" 272 | quantity: "some" 273 | units: "" 274 | metadata: {} 275 | 276 | 277 | testIngredientExplicitUnits: 278 | source: | 279 | @chilli{3%items} 280 | result: 281 | steps: 282 | - 283 | - type: ingredient 284 | name: "chilli" 285 | quantity: 3 286 | units: "items" 287 | metadata: {} 288 | 289 | 290 | testIngredientExplicitUnitsWithSpaces: 291 | source: | 292 | @chilli{ 3 % items } 293 | result: 294 | steps: 295 | - 296 | - type: ingredient 297 | name: "chilli" 298 | quantity: 3 299 | units: "items" 300 | metadata: {} 301 | 302 | 303 | testIngredientImplicitUnits: 304 | source: | 305 | @chilli{3} 306 | result: 307 | steps: 308 | - 309 | - type: ingredient 310 | name: "chilli" 311 | quantity: 3 312 | units: "" 313 | metadata: {} 314 | 315 | 316 | testIngredientNoUnits: 317 | source: | 318 | @chilli 319 | result: 320 | steps: 321 | - 322 | - type: ingredient 323 | name: "chilli" 324 | quantity: "some" 325 | units: "" 326 | metadata: {} 327 | 328 | 329 | testIngredientNoUnitsNotOnlyString: 330 | source: | 331 | @5peppers 332 | result: 333 | steps: 334 | - 335 | - type: ingredient 336 | name: "5peppers" 337 | quantity: "some" 338 | units: "" 339 | metadata: {} 340 | 341 | 342 | testIngredientWithNumbers: 343 | source: | 344 | @tipo 00 flour{250%g} 345 | result: 346 | steps: 347 | - 348 | - type: ingredient 349 | name: "tipo 00 flour" 350 | quantity: 250 351 | units: "g" 352 | metadata: {} 353 | 354 | 355 | testIngredientWithoutStopper: 356 | source: | 357 | @chilli cut into pieces 358 | result: 359 | steps: 360 | - 361 | - type: ingredient 362 | name: "chilli" 363 | quantity: "some" 364 | units: "" 365 | - type: text 366 | value: " cut into pieces" 367 | metadata: {} 368 | 369 | 370 | testMetadata: 371 | source: | 372 | --- 373 | sourced: babooshka 374 | --- 375 | result: 376 | steps: [] 377 | metadata: 378 | "sourced": babooshka 379 | 380 | 381 | testMetadataBreak: 382 | source: | 383 | hello --- 384 | sourced: babooshka 385 | --- 386 | result: 387 | steps: 388 | - 389 | - type: text 390 | value: "hello --- sourced: babooshka ---" 391 | metadata: {} 392 | 393 | 394 | testMetadataMultiwordKey: 395 | source: | 396 | --- 397 | cooking time: 30 mins 398 | --- 399 | result: 400 | steps: [] 401 | metadata: 402 | "cooking time": 30 mins 403 | 404 | 405 | testMetadataMultiwordKeyWithSpaces: 406 | source: | 407 | --- 408 | cooking time :30 mins 409 | --- 410 | result: 411 | steps: [] 412 | metadata: 413 | "cooking time": 30 mins 414 | 415 | 416 | testMultiLineDirections: 417 | source: | 418 | Add a bit of chilli 419 | 420 | Add a bit of hummus 421 | result: 422 | steps: 423 | - 424 | - type: text 425 | value: "Add a bit of chilli" 426 | - 427 | - type: text 428 | value: "Add a bit of hummus" 429 | metadata: {} 430 | 431 | 432 | testMultipleLines: 433 | source: | 434 | --- 435 | Prep Time: 15 minutes 436 | Cook Time: 30 minutes 437 | --- 438 | result: 439 | steps: [] 440 | metadata: 441 | "Prep Time": 15 minutes 442 | "Cook Time": 30 minutes 443 | 444 | 445 | testMultiWordIngredient: 446 | source: | 447 | @hot chilli{3} 448 | result: 449 | steps: 450 | - 451 | - type: ingredient 452 | name: "hot chilli" 453 | quantity: 3 454 | units: "" 455 | metadata: {} 456 | 457 | 458 | testMultiWordIngredientNoAmount: 459 | source: | 460 | @hot chilli{} 461 | result: 462 | steps: 463 | - 464 | - type: ingredient 465 | name: "hot chilli" 466 | quantity: "some" 467 | units: "" 468 | metadata: {} 469 | 470 | 471 | testMutipleIngredientsWithoutStopper: 472 | source: | 473 | @chilli cut into pieces and @garlic 474 | result: 475 | steps: 476 | - 477 | - type: ingredient 478 | name: "chilli" 479 | quantity: "some" 480 | units: "" 481 | - type: text 482 | value: " cut into pieces and " 483 | - type: ingredient 484 | name: "garlic" 485 | quantity: "some" 486 | units: "" 487 | metadata: {} 488 | 489 | 490 | testQuantityAsText: 491 | source: | 492 | @thyme{few%sprigs} 493 | result: 494 | steps: 495 | - 496 | - type: ingredient 497 | name: "thyme" 498 | quantity: few 499 | units: "sprigs" 500 | metadata: {} 501 | 502 | 503 | testQuantityDigitalString: 504 | source: | 505 | @water{7 k } 506 | result: 507 | steps: 508 | - 509 | - type: ingredient 510 | name: "water" 511 | quantity: 7 k 512 | units: "" 513 | metadata: {} 514 | 515 | 516 | testServings: 517 | source: | 518 | --- 519 | servings: 1|2|3 520 | --- 521 | result: 522 | steps: [] 523 | metadata: 524 | "servings": 1|2|3 525 | 526 | 527 | testSlashInText: 528 | source: | 529 | Preheat the oven to 200℃/Fan 180°C. 530 | result: 531 | steps: 532 | - 533 | - type: text 534 | value: "Preheat the oven to 200℃/Fan 180°C." 535 | metadata: {} 536 | 537 | 538 | testTimerDecimal: 539 | source: | 540 | Fry for ~{1.5%minutes} 541 | result: 542 | steps: 543 | - 544 | - type: text 545 | value: "Fry for " 546 | - type: timer 547 | quantity: 1.5 548 | units: "minutes" 549 | name: "" 550 | metadata: {} 551 | 552 | 553 | testTimerFractional: 554 | source: | 555 | Fry for ~{1/2%hour} 556 | result: 557 | steps: 558 | - 559 | - type: text 560 | value: "Fry for " 561 | - type: timer 562 | quantity: 0.5 563 | units: "hour" 564 | name: "" 565 | metadata: {} 566 | 567 | 568 | testTimerInteger: 569 | source: | 570 | Fry for ~{10%minutes} 571 | result: 572 | steps: 573 | - 574 | - type: text 575 | value: "Fry for " 576 | - type: timer 577 | quantity: 10 578 | units: "minutes" 579 | name: "" 580 | metadata: {} 581 | 582 | 583 | testTimerWithName: 584 | source: | 585 | Fry for ~potato{42%minutes} 586 | result: 587 | steps: 588 | - 589 | - type: text 590 | value: "Fry for " 591 | - type: timer 592 | quantity: 42 593 | units: "minutes" 594 | name: "potato" 595 | metadata: {} 596 | 597 | 598 | testSingleWordTimer: 599 | source: | 600 | Let it ~rest after plating 601 | result: 602 | steps: 603 | - 604 | - type: text 605 | value: "Let it " 606 | - type: timer 607 | quantity: "" 608 | units: "" 609 | name: "rest" 610 | - type: text 611 | value: " after plating" 612 | metadata: {} 613 | 614 | 615 | testSingleWordTimerWithPunctuation: 616 | source: | 617 | Let it ~rest, then serve 618 | result: 619 | steps: 620 | - 621 | - type: text 622 | value: "Let it " 623 | - type: timer 624 | quantity: "" 625 | units: "" 626 | name: "rest" 627 | - type: text 628 | value: ", then serve" 629 | metadata: {} 630 | 631 | 632 | testSingleWordTimerWithUnicodePunctuation: 633 | source: | 634 | Let it ~rest⸫ then serve 635 | result: 636 | steps: 637 | - 638 | - type: text 639 | value: "Let it " 640 | - type: timer 641 | quantity: "" 642 | units: "" 643 | name: "rest" 644 | - type: text 645 | value: "⸫ then serve" 646 | metadata: {} 647 | 648 | # NOTE: the space following `rest` is U+2009 649 | testTimerWithUnicodeWhitespace: 650 | source: | 651 | Let it ~rest then serve 652 | result: 653 | steps: 654 | - 655 | - type: text 656 | value: "Let it " 657 | - type: timer 658 | quantity: "" 659 | units: "" 660 | name: "rest" 661 | - type: text 662 | value: " then serve" 663 | metadata: {} 664 | 665 | 666 | testInvalidMultiWordTimer: 667 | source: | 668 | It is ~ {5} 669 | result: 670 | steps: 671 | - 672 | - type: text 673 | value: "It is ~ {5}" 674 | metadata: {} 675 | 676 | 677 | testInvalidSingleWordTimer: 678 | source: | 679 | It is ~ 5 680 | result: 681 | steps: 682 | - 683 | - type: text 684 | value: "It is ~ 5" 685 | metadata: {} 686 | 687 | 688 | testSingleWordIngredientWithPunctuation: 689 | source: | 690 | Add some @chilli, then serve 691 | result: 692 | steps: 693 | - 694 | - type: text 695 | value: "Add some " 696 | - type: ingredient 697 | quantity: "some" 698 | units: "" 699 | name: "chilli" 700 | - type: text 701 | value: ", then serve" 702 | metadata: {} 703 | 704 | 705 | testSingleWordIngredientWithUnicodePunctuation: 706 | source: | 707 | Add @chilli⸫ then bake 708 | result: 709 | steps: 710 | - 711 | - type: text 712 | value: "Add " 713 | - type: ingredient 714 | quantity: "some" 715 | units: "" 716 | name: "chilli" 717 | - type: text 718 | value: "⸫ then bake" 719 | metadata: {} 720 | 721 | 722 | # NOTE: the space following `chilli` is U+2009 723 | testIngredientWithUnicodeWhitespace: 724 | source: | 725 | Add @chilli then bake 726 | result: 727 | steps: 728 | - 729 | - type: text 730 | value: "Add " 731 | - type: ingredient 732 | quantity: "some" 733 | units: "" 734 | name: "chilli" 735 | - type: text 736 | value: " then bake" 737 | metadata: {} 738 | 739 | 740 | testInvalidMultiWordIngredient: 741 | source: | 742 | Message @ example{} 743 | result: 744 | steps: 745 | - 746 | - type: text 747 | value: "Message @ example{}" 748 | metadata: {} 749 | 750 | 751 | testInvalidSingleWordIngredient: 752 | source: | 753 | Message me @ example 754 | result: 755 | steps: 756 | - 757 | - type: text 758 | value: "Message me @ example" 759 | metadata: {} 760 | 761 | 762 | testSingleWordCookwareWithPunctuation: 763 | source: | 764 | Place in #pot, then boil 765 | result: 766 | steps: 767 | - 768 | - type: text 769 | value: "Place in " 770 | - type: cookware 771 | quantity: 1 772 | units: "" 773 | name: "pot" 774 | - type: text 775 | value: ", then boil" 776 | metadata: {} 777 | 778 | testSingleWordCookwareWithUnicodePunctuation: 779 | source: | 780 | Place in #pot⸫ then boil 781 | result: 782 | steps: 783 | - 784 | - type: text 785 | value: "Place in " 786 | - type: cookware 787 | quantity: 1 788 | units: "" 789 | name: "pot" 790 | - type: text 791 | value: "⸫ then boil" 792 | metadata: {} 793 | 794 | 795 | # NOTE: the space following `pot` is U+2009 796 | testCookwareWithUnicodeWhitespace: 797 | source: | 798 | Add to #pot then boil 799 | result: 800 | steps: 801 | - 802 | - type: text 803 | value: "Add to " 804 | - type: cookware 805 | quantity: 1 806 | units: "" 807 | name: "pot" 808 | - type: text 809 | value: " then boil" 810 | metadata: {} 811 | 812 | 813 | testInvalidMultiWordCookware: 814 | source: | 815 | Recipe # 10{} 816 | result: 817 | steps: 818 | - 819 | - type: text 820 | value: "Recipe # 10{}" 821 | metadata: {} 822 | 823 | 824 | testInvalidSingleWordCookware: 825 | source: | 826 | Recipe # 5 827 | result: 828 | steps: 829 | - 830 | - type: text 831 | value: "Recipe # 5" 832 | metadata: {} 833 | 834 | # NOTE: Unicode newlines may be impossible to test using YAML, 835 | # given how the markup uses them for semantic reasoning. 836 | 837 | # TODO add common syntax errors 838 | --------------------------------------------------------------------------------