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:
# Guacamole
the 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
code
Advance a block
b
: Setb
to the block afterb
. Ifb
is the last block, it will be unset afterwards.Enter a block
b
: Setb
to the first child ofb
. This is only defined for container blocks, and will error otherwise.Leave a block
b
: Setb
to the parent ofb
. Ifb
has 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
c
be the first block of the document.Parse title:
If
c
it 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
descriptionStart
be the index of the starting line ofc
, letdescriptionEnd
be unsetParse the description:
If
c
is a paragraph whose contents are a single emphasis or strong emphasis, go to 5.Else if
c
is a thematic break, go to 5.Else set
descriptionEnd
to the ending line ofc
, advancec
.
Set the description:
If
descriptionEnd
is unset, keep the description unset.Else set the recipe’s description to the the inclusive range of lines between
descriptionStart
anddescriptionEnd
Parse tags and yields:
If
c
is 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
c
ia 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
c
is a thematic break, advancec
Else abort parsing with an error.
Parse ingredients and ingredient groups:
If
c
is a heading:Run “Parsing Ingredient Groups” with
c
,groups
set to the recipe’s ingredient groups andparentLevel
set to -1.Set
c
to the returned blockGo to 8.
Else if
c
is a list item (ordered or unordered)Run “Parsing an Ingredient List” with
c
andingredients
set to the recipe’s ingredients.Go to 8.
Find instruction divider
If
c
is a thematic break, advancec
Set the recipe’s instructions to the remainder of the documents contents from
c
to 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
c
is not a heading, return.Else
Let
l
be the heading level ofc
Check nesting:
If
l
is less than or equal toparentLevel
, return.
Let
g
be an ingredient group with a title equal to the contents of the headingc
, empty ingredients and empty ingredient groups.Advance
c
Run “Parsing an Ingredient List” with
c
andi
set to the ingredients ofg
.Run “Parsing Ingredient Groups” with
c
,groups
set to the ingredient groups ofg
andparentLevel
set 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
c
is not the start of a bulleted list of an ordered list, return.Else:
Enter
c
Collect ingredients:
If
c
is a list item:Run “Parsing an Ingredient” on
c
and append the result toingredients
.Go to next item:
If
c
has a following blockAdvance
c
and go to 2.
Else leave
c
and go to 1.
Parsing an Ingredient¶
This algorithm accepts a block c
. It returns an ingredient i
:
Examine
c
:If
c
is a list item, enterc
-Else abort parsing with an error.
Let
a
be the amount, set to unset.Let
n
be the name, set to an empty string.Let
l
be a link, set to unset.Examine
c
:If
c
is not a paragraph, setn
to the verbatim contents ofc
.Else:
Parse the amount:
If
c
‘s contents start with an emphasis inline:Run “Parsing an Amount” with
s
set to the emphasis’ contents, and seta
to the result.Let
r
be the remaining contents ofc
after the emphasis.
Else: Let
r
be the verbatim contents ofc
Parse the link:
If
c
is the only child of its containing list item andc
‘s contents consist only of a single inline link:Set
l
to the link’s destination.Set
n
to the link’s text.
Else set
n
tor
.
Parse the following blocks of the list item:
If
c
has a block following after it:Advance
c
.Append
c
‘s verbatim contents ton
.Go to 6.
Leave
c
Let
i
be an ingredient with the amounta
, namen
and 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
s
Check for negative
If
s
starts with a"-"
character, letnegative
be true. Remove the"-"
and trim all whitespace at the beginning ofs
Else let
negative
be false
Let
v
be a number, set it to unsetIf
s
starts with an improper fraction (a
w+
b
w*
[/]
w*
c
, witha
,b
,c
integers), setv
toa
+ (b
/c
).Else if
s
stars with an improper faction using Unicode vulgar fractions (a
w+
b
, witha
integers andb
a Unicode vulgar fractions). Setf
to the numeric value assigned tob
andv
toa
+f
.Else if
s
starts with a proper fraction (a
w*
[/]
w*
b
, witha
,b
integers), setv
to (a
/b
).Else if
s
starts with a Unicode vulgar fractiona
. Setv
to the numeric value assigned toa
.Else if
s
starts with a decimal number (a
[.,]
b
, witha
,b
integers), setv
to a decimal number witha
being its whole andb
being its fractional part.Else if
s
starts with an integera
, setv
toa
.
Let
u
be the remainder ofs
, stripped of whitespace. Setu
to unset if it is the empty string.Return result:
If
v
is set, return an amount the the valuev
and the unitu
.Else if
u
is 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