├── .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 |
--------------------------------------------------------------------------------