RecipeMD Specification¶
This is version 2.4.0 of the RecipeMD specification.
RecipeMD is a Markdown-based format for writing down recipes. It defines a certain structure which a document must follow to be a RecipeMD recipe.
All RecipeMD files shall follow the commonmark specification.
Example¶
This example presents the basic format of a RecipeMD recipe
# Guacamole
Some people call it guac.
*sauce, vegan*
**4 Servings, 200g**
---
- *1* avocado
- *.5 teaspoon* salt
- *1 1/2 pinches* red pepper flakes
- lemon juice
---
Remove flesh from avocado and roughly mash with fork. Season to taste
with salt, pepper and lemon juice.
the title is noted as a first level header:
# Guacamolethe short description is noted as one or more paragraphs:
Some people call it guac.the tags are noted as paragraph containing only a comma seperated list in italics:
*sauce, vegan*the yield is noted as paragraph containing only a comma seperated list in bold face:
**4 Servings, 200g**the ingredients are noted as list items. The headings form the titles of ingredient groups. The amount of each ingredient (if given) is noted in italics. Amounts may be expressed as decimals (dividers “.” and “,”) and fractions:
- *1* avocado - *.5 teaspoon* salt - *1 1/2 pinches* red pepper flakes - lemon juice
anything following the second horizontal line is considered instructions
RecipeMD Data Types¶
Recipe¶
A valid recipe consists of:
a title
optional: a short description
0..n tags
0..n yields
ingredients:
ungrouped ingredients as 0..n ingredients
grouped ingredients as 0..n ingredient groups
optional: instructions
A recipe is represented in markdown as follows:
Title as a first level heading
Short description as zero or more paragraphs
Yield and Tags (arbitrary order, both optional):
Tags as a single paragraph which is completely in italics. Tags are a comma separated list of strings.
Yields as a single paragraph which is completely in bold. Yields are a comma separated list of amounts.
a horizontal line
the ingredients
a horizontal line – this may be omitted if there are no instructions
optional: the instructions, everything following the second line
Note that for the comma separated lists, a comma is not treated as a list divider if the characters directly before and after are numerical. This is to allow amounts with a comma as a decimal divider to appear in lists.
Amount¶
An amount consists of
a value as a number
optional a unit
An amount is represented as follows:
A number, which may have one of the following formats
improper fraction (e.g.
1 1/5)proper fraction (e.g
3/7)unicode vulgar fractions (like ½) may also be used
decimal with dividers “.” and “,” (e.g. “41.9”)
A unit which is just everything following the number
Ingredient¶
An ingredient consists of
optional an amount
a name
optional a link to a recipe for the ingredient
An ingredient is represented as follows:
The amount in italics
Everything following until the end of the list item is part of the name or the link as specified below:
If a name contains only an inline-link, the link-text represents the name of the ingredient and the link-destination specifies a resource that contains a recipe for the ingredient
Otherwise, the text is the name and the link is not set
Ingredient Group¶
An ingredient group is a group of related ingredients, e.g. the ingredients making up one component of a dish. It consists of:
a title
0..n ingredients
0..n ingredients groups
An ingredient group us represented as follows:
A heading, whose contents are the group’s title
A list of ingredients (can be skipped). The items of all lists following the heading are treated as ingredients of the group.
A group may have child groups: Groups directly following a group are considered children of that group if their initial heading has a lower level than the original group. Heading levels are determined by their corresponding HTML heading, with
<h1>being the highest heading level and<h6>the lowest.
RecipeMD Parsing Strategy¶
This section presents a parsing strategy for RecipeMD documents. This is based on the parsing algorithm used by the reference implementation.
The main algorithm is presented first.
Definitions¶
The following conventions are common to all algorithms.
Variables are represented as
codeAdvance a block
b: Setbto the block afterb. Ifbis the last block, it will be unset afterwards.Enter a block
b: Setbto the first child ofb. This is only defined for container blocks, and will error otherwise.Leave a block
b: Setbto the parent ofb. Ifbhas no parent, it will be unset afterwards.
Parsing a Recipe¶
The RecipeMD syntax is described in terms of the commonmark specification. To parse a recipe, follow this algorithm. The algorithm accepts a commonmark document.
Let
cbe the first block of the document.Parse title:
If
cit is a heading and has a level of 1:Assign
c‘s contents to the recipe’s title.Advance
c.
Else abort parsing with an error.
Let
descriptionStartbe the index of the starting line ofc, letdescriptionEndbe unsetParse the description:
If
cis a paragraph whose contents are a single emphasis or strong emphasis, go to 5.Else if
cis a thematic break, go to 5.Else set
descriptionEndto the ending line ofc, advancec.
Set the description:
If
descriptionEndis unset, keep the description unset.Else set the recipe’s description to the the inclusive range of lines between
descriptionStartanddescriptionEnd
Parse tags and yields:
If
cis a paragraph whose contents are a single emphasis:If the recipe’s tags are set, abort parsing with an error
Else set the recipe’s tags to the contents of the paragraph, split at the comma character (with commas between ASCII digits ignored) and each element stripped from whitespace.
Advance
c, go to 6.
Else if
cia a paragraph whose contents are a single strong emphasis:If the recipe’s yields are set, abort parsing with an error
Else set the recipe’s yields to the contents of the paragraph, split a the comma character (with commas between ASCII digits ignored), each element stripped from whitespace and parsed as an amount.
Advance
c, go to 6.
Else go to next step.
Find ingredient divider:
If
cis a thematic break, advancecElse abort parsing with an error.
Parse ingredients and ingredient groups:
If
cis a heading:Run “Parsing Ingredient Groups” with
c,groupsset to the recipe’s ingredient groups andparentLevelset to -1.Set
cto the returned blockGo to 8.
Else if
cis a list item (ordered or unordered)Run “Parsing an Ingredient List” with
candingredientsset to the recipe’s ingredients.Go to 8.
Find instruction divider
If
cis a thematic break, advancec
Set the recipe’s instructions to the remainder of the documents contents from
cto the end of the file.
Parsing Ingredient Groups¶
This algorithm accepts a block c, a list of ingredients groups
groups and an integer parent level parentLevel. It modifies
groups to append the ingredient groups found and returns the current
block c.
Examine
c:If
cis not a heading, return.Else
Let
lbe the heading level ofcCheck nesting:
If
lis less than or equal toparentLevel, return.
Let
gbe an ingredient group with a title equal to the contents of the headingc, empty ingredients and empty ingredient groups.Advance
cRun “Parsing an Ingredient List” with
candiset to the ingredients ofg.Run “Parsing Ingredient Groups” with
c,groupsset to the ingredient groups ofgandparentLevelset tol.Go to 1.
Parsing an Ingredient List¶
This algorithm accepts a block c and a list of ingredients
ingredients. It modifies ingredients to append the ingredients
found and returns the current block c.
Examine
c:If
cis not the start of a bulleted list of an ordered list, return.Else:
Enter
c
Collect ingredients:
If
cis a list item:Run “Parsing an Ingredient” on
cand append the result toingredients.Go to next item:
If
chas a following blockAdvance
cand go to 2.
Else leave
cand go to 1.
Parsing an Ingredient¶
This algorithm accepts a block c. It returns an ingredient i:
Examine
c:If
cis a list item, enterc-Else abort parsing with an error.
Let
abe the amount, set to unset.Let
nbe the name, set to an empty string.Let
lbe a link, set to unset.Examine
c:If
cis not a paragraph, setnto the verbatim contents ofc.Else:
Parse the amount:
If
c‘s contents start with an emphasis inline:Run “Parsing an Amount” with
sset to the emphasis’ contents, and setato the result.Let
rbe the remaining contents ofcafter the emphasis.
Else: Let
rbe the verbatim contents ofc
Parse the link:
If
cis the only child of its containing list item andc‘s contents consist only of a single inline link:Set
lto the link’s destination.Set
nto the link’s text.
Else set
ntor.
Parse the following blocks of the list item:
If
chas a block following after it:Advance
c.Append
c‘s verbatim contents ton.Go to 6.
Leave
cLet
ibe an ingredient with the amounta, namenand linkl.
Parsing an Amount¶
This algorithm accepts a string s and returns an amount or nothing.
In this algorithm the following conventions are used.
w+represents one or more whitespace charactersw*represents zero or more whitespace characters[xy]represents the set of literal characters enclosed by the brackets, in this case the character “x” or “y”
Trim all whitespace at the beginning of
sCheck for negative
If
sstarts with a"-"character, letnegativebe true. Remove the"-"and trim all whitespace at the beginning ofsElse let
negativebe false
Let
vbe a number, set it to unsetIf
sstarts with an improper fraction (aw+bw*[/]w*c, witha,b,cintegers), setvtoa+ (b/c).Else if
sstars with an improper faction using Unicode vulgar fractions (aw+b, withaintegers andba Unicode vulgar fractions). Setfto the numeric value assigned tobandvtoa+f.Else if
sstarts with a proper fraction (aw*[/]w*b, witha,bintegers), setvto (a/b).Else if
sstarts with a Unicode vulgar fractiona. Setvto the numeric value assigned toa.Else if
sstarts with a decimal number (a[.,]b, witha,bintegers), setvto a decimal number withabeing its whole andbbeing its fractional part.Else if
sstarts with an integera, setvtoa.
Let
ube the remainder ofs, stripped of whitespace. Setuto unset if it is the empty string.Return result:
If
vis set, return an amount the the valuevand the unitu.Else if
uis set, abort parsing with an error.Else return nothing.
Test Cases¶
Implementations of this specification must conform with all test
cases. There are two kinds of test cases: valid files (*.md with a
corresponding *.json) and invalid files (*.invalid.md).
The format of the JSON files is specified via a JSON schema file distributed with the testcases. When comparing the actual to the expected results property order in objects should be ignored.
Version History¶
Version 2.4.0 (2024-02-14)¶
This spec version mostly contains clarifications for previously undefined behavior and fixes of inconsistencies. Many thanks to
d-k-bo for the detailed report on these that lead to this release!
Update link to test cases to point to the new “RecipeMD” GitHub organization.
Fix test cases that included amounts with no factor.
These were always invalid according to the spec, but the reference implementation incorrectly accepted them.
Update data type definitions to align with the test cases and the reference implementation.
Add a detailed description of a RecipeMD parsing strategy.
Reference new JSON Schema for test case JSON files.
Specify the title of an ingredient group as a non-optional field.
The parsing algorithm never allowed the creation of groups without a title, but the field was marked as optional in the data type description.
This will not change the parsing behavior of any recipes. It may however be a breaking change for implementations, since it changes the interface of the ingredient group data type in a way that may not be backwards compatible.
Clarify the behavior of multiple lists following an ingredient group heading.
Clarify that the comma splitting algorithm is to be used for tags as well as for yields.
Version 2.3.5 (2022-08-14)¶
Fix test case “ingredients_multiline.md” to use valid link targets
Expand test cases to cover
ingredients with sublists
ingredients using numbered lists
ingredients partially wrapped with links
link ingredients with spaces in link targets
link ingredients with link titles
partial tag paragraphs that should not be interpreted as tags
titles using setext headings
Version 2.3.4 (2021-12-26)¶
Fixes to test cases:
Add missing ingredient groups in JSON files
Clean up formatting
Fix missing indentation spaces for multiline ingredients
Version 2.3.3 (2021-01-02)¶
Clarify that yields and tags may appear at most once.
Add test cases:
Multiple yields or tags
Tag/yield order
Version 2.3.2 (2019-12-18)¶
Add author and license information
Version 2.3.1 (2019-11-18)¶
Fix missing link
Fix wrong indentation and word wrapping
Version 2.3.0 (2019-11-18)¶
Add a full version of the example
Add version history reconstructed from git
Add reference to test suite
Version 2.2.0 (2019-04-22)¶
Allow recipes to reference other recipes
2.1.0 (2019-03-25)¶
Allow unicode vulgar fractions in ingredient amounts
2.0.1 (2018-09-17)¶
Reword specification
Clarify edge cases
2.0.0 (2018-09-13)¶
Breaking: Seperate yields and tags into own paragraphs
1.0.0 (2018-08-26)¶
Initial version
Edit on GitHub