Skip to content

Elixir bindings for the canonical Cooklang parser (cooklang-rs). Parse, scale, and convert Cooklang recipes with full support for extensions.

License

Notifications You must be signed in to change notification settings

nathanbegbie/cooklang_ex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CooklangEx

Hex.pm Documentation

Elixir bindings for the canonical Cooklang parser, powered by cooklang-rs via Rustler NIFs.

Features

  • 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

Installation

Add cooklang_ex to your list of dependencies in mix.exs:

def deps do
  [
    {:cooklang_ex, "~> 0.1.0"}
  ]
end

Requirements

  • Elixir 1.14+
  • Erlang/OTP 25+
  • Rust 1.70+ (for compilation)

Quick Start

# 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"}}]

Scaling Recipes

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)

Cooklang Syntax Reference

Ingredients

@ingredient           # Simple ingredient
@eggs{3}              # With quantity
@flour{200%g}         # With quantity and unit
@ground black pepper{} # Multi-word name

Cookware

#pan                  # Simple cookware
#bowl{large}          # With size/description
#mixing bowl{}        # Multi-word name

Timers

~{10%minutes}         # Duration timer
~{30%seconds}         # Another timer
~name{5%minutes}      # Named timer

Metadata

>> servings: 4
>> source: https://example.com/recipe
>> time: 30 minutes

Comments

-- This is a comment
Add @salt{} -- inline comment

Steps

Steps are separated by blank lines:

First step here.

Second step here.

Third step here.

API Reference

CooklangEx.parse/2

Parse a Cooklang recipe string.

{:ok, recipe} = CooklangEx.parse(text)
{:ok, recipe} = CooklangEx.parse(text, all_extensions: false)

CooklangEx.parse!/2

Parse a recipe, raising on error.

recipe = CooklangEx.parse!(text)

CooklangEx.parse_and_scale/3

Parse and scale a recipe to target servings.

{:ok, recipe} = CooklangEx.parse_and_scale(text, 8)

CooklangEx.ingredients/1

Extract just the ingredients list.

{:ok, ingredients} = CooklangEx.ingredients(text)

CooklangEx.cookware/1

Extract just the cookware list.

{:ok, cookware} = CooklangEx.cookware(text)

CooklangEx.metadata/1

Extract just the metadata map.

{:ok, metadata} = CooklangEx.metadata(text)

Extensions

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.

Release Process

Creating a new release is simple - just create and publish a GitHub release with a version tag:

  1. Create a new release on GitHub with a version tag (e.g., v0.1.0)
  2. 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.

Development

# 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 test

License

MIT License - see LICENSE for details.

Acknowledgments

About

Elixir bindings for the canonical Cooklang parser (cooklang-rs). Parse, scale, and convert Cooklang recipes with full support for extensions.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •