This repository has been archived on 2026-07-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
kitchen-md/SPEC.md

6.6 KiB

KitchenMD Language Specification

KitchenMD is Obsidian Flavored Markdown with three inline annotation extensions (Ingredient, Cookware, Timer) and one structural extension (Step Reference). A KitchenMD file is a valid .md file and renders correctly in Obsidian without a plugin.


Base language

Obsidian Flavored Markdown is itself a superset of CommonMark. All Obsidian Flavored Markdown constructs are valid and carry their standard meaning: headings, paragraphs, lists, blockquotes, code spans, fenced code blocks, links, wikilinks ([[page]]), transclusion (![[page]]), YAML frontmatter, and so on.

YAML frontmatter (standard --- delimiters) is passed through as-is. The format places no schema on frontmatter fields; consumers read whatever fields they need.


Annotations

Annotations are inline markers that carry structured data about ingredients, cookware, and timers. They are written as plain text and do not conflict with any CommonMark syntax.

Scope

Annotations are processed wherever CommonMark processes inline content:

  • Parsed: paragraphs, headings, list items, blockquotes
  • Ignored: code spans, fenced/indented code blocks, HTML comments, raw HTML blocks

No escape mechanism

A bare @ or $ not followed by a valid annotation pattern is passed through as plain text — no backslash escape is needed. For ~, the restricted unit vocabulary (see Timer) prevents false positives.


Ingredient

@name{} 
@name{quantity}
@name{quantity unit}

Sigil: @

Name: everything between @ and {. Valid characters: any character except {, }, @, $, ~. Multi-word names work naturally: @unsalted butter{30 g}.

Braces: mandatory. Bare @name without braces is not a valid annotation.

Quantity: optional. Valid formats:

  • Integer: 2
  • Decimal: 0.5
  • Simple fraction: 1/2
  • Mixed number: 1 1/2

Unit: optional, free-form string. When both quantity and unit are present, they are delimited by the last space inside the braces. @butter{1 1/2 tbsp} → quantity 1 1/2, unit tbsp.

Examples:

@eggs{2}
@flour{200 g}
@unsalted butter{30 g}
@butter{1 1/2 tbsp}
@black pepper{}

Cookware

$name{}
$name{quantity}
$name{quantity unit}

Sigil: $

Name: same rules as Ingredient — any character except {, }, @, $, ~.

Braces: mandatory.

Quantity: optional. Follows the same format rules as Ingredient quantity (integer, decimal, fraction, mixed number).

Unit: optional, free-form. Same last-space delimiter rule as Ingredient.

Examples:

$mixing bowl{}
$non-stick frying pan{}
$plate{3}
$cupcake liner{12 large}

Timer

~N unit
~N-N unit

Sigil: ~

Number (N): follows the same format rules as Ingredient/Cookware quantity (integer, decimal, simple fraction, mixed number).

Range: two quantities separated by -. Since negative quantities do not exist, the - is unambiguous. ~1/2-1 hr → range of 1/2 hr to 1 hr.

Unit: one word from the known set, normalised to its canonical abbreviation:

Written Canonical
sec, secs, second, seconds s
min, mins, minute, minutes min
hr, hrs, hour, hours hr

A ~ followed by a number but not a known unit word is not a valid annotation and passes through as plain text.

Termination: the annotation ends after the unit word. Trailing prose is ignored. ~1 min more → timer of 1 min; "more" is plain text.

Examples:

~1 min
~2-3 mins
~1.5 hrs
~1/2-1 hr

Recipe structure

A KitchenMD file is prose instructions only. There is no separate ingredients list section. Ingredients, cookware, and timers are derived entirely by tooling from inline annotations.


Steps

A step is the atomic unit of a recipe instruction. Steps are counted by tooling; authors write naturally without numbering them.

A step is one of:

  • A single non-empty line in a prose context (paragraph or heading body).
  • A single ordered list item.

Headings are not steps. Step numbers are 1-based and section-local: they reset at each heading and do not change when steps are added to other sections.


Step References

A Step Reference embeds a specific step from another Recipe File. It is used in Combined Recipes to sequence steps across files.

![[filename#section:N]]   (step N within a named section)
![[filename#N]]            (step N in a headingless recipe)
  • filename — the target Recipe File, without the .md extension.
  • section — the heading text of the section containing the step, exactly as written (case-sensitive).
  • N — the 1-based step number within that section.

Examples:

![[italian meatballs#rolling:2]]
![[basic brine#3]]

Combined Recipes

A Combined Recipe is a Recipe File that contains only Step References (and optionally frontmatter and prose headings for organisation). It has no annotations of its own — ingredients and timers belong to the referenced recipes. Its purpose is to define the execution order of steps across two or more recipes, such as an index card for a multi-component meal.

Compatibility note: Step References are a KitchenMD extension. Vanilla Obsidian does not recognise #section:N or #N as valid anchors and will show an "unable to find" error. Individual (non-combined) Recipe Files are unaffected.


Formal grammar (ABNF sketch)

ingredient  = "@" name "{" [quantity [SP unit]] "}"
cookware    = "$" name "{" [quantity [SP unit]] "}"
timer       = "~" quantity SP time-unit

name        = 1*(any-char)
any-char    = %x00-7E except "{" / "}" / "@" / "$" / "~"

quantity    = mixed-number / fraction / decimal / integer
integer     = 1*DIGIT
decimal     = 1*DIGIT "." 1*DIGIT
fraction    = integer "/" integer
mixed-number = integer SP fraction

time-unit   = "sec" / "secs" / "second" / "seconds"
            / "min" / "mins" / "minute" / "minutes"
            / "hr"  / "hrs"  / "hour"   / "hours"

timer-range = "~" quantity "-" quantity SP time-unit

step-ref-sectioned  = "![[" filename "#" section ":" step-num "]]"
step-ref-headingless = "![[" filename "#" step-num "]]"
filename    = 1*(any-char)
section     = 1*(any-char except ":" / "]")
step-num    = 1*DIGIT

Note: unit in ingredient/cookware is free-form and not captured in the grammar above. The last-space rule applies at parse time: everything after the last space inside {} is the unit; everything before it is the quantity.