Elixir bindings for the canonical Cooklang parser, powered by cooklang-rs via Rustler NIFs.
- Full Cooklang spec support - Parse ingredients (
@), cookware (#), timers (~), and metadata - Recipe scaling - Automatically scale ingredient quantities to different serving sizes
- Extensions - Optional syntax extensions for advanced recipe formatting
- Fast - Native Rust performance via NIF bindings
- Rich errors - Detailed parse error messages with source locations
Add cooklang_ex to your list of dependencies in mix.exs:
def deps do
[
{:cooklang_ex, "~> 0.1.0"}
]
end- Elixir 1.14+
- Erlang/OTP 25+
- Rust 1.70+ (for compilation)
# Parse a simple recipe
recipe_text = """
>> servings: 2
>> time: 15 minutes
Crack @eggs{3} into a #bowl{large} and whisk until fluffy.
Heat @butter{2%tbsp} in a #pan{non-stick} over medium heat.
Pour egg mixture into pan and cook for ~{3%minutes}, stirring gently.
Season with @salt{} and @black pepper{} to taste.
"""
{:ok, recipe} = CooklangEx.parse(recipe_text)
# Access parsed data
recipe.metadata
# => %{"servings" => "2", "time" => "15 minutes"}
recipe.ingredients
# => [
# %CooklangEx.Recipe.Ingredient{name: "eggs", quantity: %{value: 3.0, unit: nil}},
# %CooklangEx.Recipe.Ingredient{name: "butter", quantity: %{value: 2.0, unit: "tbsp"}},
# %CooklangEx.Recipe.Ingredient{name: "salt", quantity: nil},
# %CooklangEx.Recipe.Ingredient{name: "black pepper", quantity: nil}
# ]
recipe.cookware
# => [
# %CooklangEx.Recipe.Cookware{name: "bowl", quantity: %{value: "large"}},
# %CooklangEx.Recipe.Cookware{name: "pan", quantity: %{value: "non-stick"}}
# ]
recipe.timers
# => [%CooklangEx.Recipe.Timer{quantity: %{value: 3.0, unit: "minutes"}}]Scale recipes to different serving sizes:
recipe_text = """
>> servings: 4
Mix @flour{400%g} with @water{250%ml}.
Add @yeast{1%packet} and @salt{1%tsp}.
"""
# Scale from 4 servings to 8
{:ok, scaled} = CooklangEx.parse_and_scale(recipe_text, 8)
# Quantities are automatically doubled
hd(scaled.ingredients).quantity.value
# => 800.0 (was 400)@ingredient # Simple ingredient
@eggs{3} # With quantity
@flour{200%g} # With quantity and unit
@ground black pepper{} # Multi-word name
#pan # Simple cookware
#bowl{large} # With size/description
#mixing bowl{} # Multi-word name
~{10%minutes} # Duration timer
~{30%seconds} # Another timer
~name{5%minutes} # Named timer
>> servings: 4
>> source: https://example.com/recipe
>> time: 30 minutes
-- This is a comment
Add @salt{} -- inline comment
Steps are separated by blank lines:
First step here.
Second step here.
Third step here.
Parse a Cooklang recipe string.
{:ok, recipe} = CooklangEx.parse(text)
{:ok, recipe} = CooklangEx.parse(text, all_extensions: false)Parse a recipe, raising on error.
recipe = CooklangEx.parse!(text)Parse and scale a recipe to target servings.
{:ok, recipe} = CooklangEx.parse_and_scale(text, 8)Extract just the ingredients list.
{:ok, ingredients} = CooklangEx.ingredients(text)Extract just the cookware list.
{:ok, cookware} = CooklangEx.cookware(text)Extract just the metadata map.
{:ok, metadata} = CooklangEx.metadata(text)The parser supports several extensions to the base Cooklang specification. By default, all extensions are enabled. Disable them with:
CooklangEx.parse(text, all_extensions: false)Extensions include:
- Multi-line steps
- Advanced units and quantities
- Sections and notes
- And more...
See the cooklang-rs extensions documentation for details.
Creating a new release is simple - just create and publish a GitHub release with a version tag:
- Create a new release on GitHub with a version tag (e.g.,
v0.1.0) - Publish the release - The CI workflow automatically:
- Updates the VERSION file
- Runs validation and tests
- Publishes to Hex.pm
- Publishes documentation to HexDocs
The GitHub release tag is the source of truth for versioning. The VERSION file in the repository is automatically updated to match the release tag.
# Clone the repo
git clone https://github.com/yourusername/cooklang_ex
cd cooklang_ex
# Optional: if you're using asdf, set up your versioning
cp .tool-versions.example .tool-versions
# Install dependencies
mix deps.get
# Compile (this will also compile the Rust NIF)
mix compile
# Run tests
mix testMIT License - see LICENSE for details.
- Cooklang - The recipe markup language
- cooklang-rs - The canonical Rust parser
- Rustler - Safe Rust/Elixir bindings