Project

cooklang

0.0
A long-lived project that still receives updates
Cooklang is a markup language for recipes that allows you to define ingredients, cookware, timers, and metadata in a structured way. This gem provides a Ruby parser for Cooklang files.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

>= 2.1
~> 13.0
~> 3.13
~> 1.80
~> 0.22.0
 Project Readme

Cooklang

Gem Version Test Maintainability Code Coverage

A Ruby parser for the Cooklang recipe markup language.

Requirements

  • Ruby 2.7 or higher

Installation

Add to your Gemfile:

gem 'cooklang'

Or install directly:

gem install cooklang

Usage

require 'cooklang'

recipe_text = <<~RECIPE
  >> title: Pancakes
  >> servings: 4

  Crack @eggs{3} into a bowl, add @flour{125%g} and @milk{250%ml}.

  Heat #frying pan over medium heat for ~{5%minutes}.
  Pour batter and cook until golden.
RECIPE

recipe = Cooklang.parse(recipe_text)

# Access metadata
recipe.metadata['title']        # => "Pancakes"
recipe.metadata['servings']     # => 4

# Access ingredients
recipe.ingredients.each do |ingredient|
  puts "#{ingredient.name}: #{ingredient.quantity} #{ingredient.unit}"
end
# => eggs: 3
# => flour: 125 g
# => milk: 250 ml

# Parse from file
recipe = Cooklang.parse_file('pancakes.cook')

Formatters

The gem provides three built-in formatters for different output needs. Here's an example using Uncle Roger's Fried Rice recipe:

>> title: Uncle Roger's Fried Rice
>> servings: 2
>> prep_time: 10 minutes
>> cook_time: 8 minutes

Heat @peanut oil{1%tbsp} in #wok over high heat until smoking.

Add @garlic{3%cloves}(minced) and @shallots{2}(diced), stir-fry for ~{30%seconds}.

Add @eggs{3}(beaten) and scramble until almost set.

Add @day-old rice{3%cups} and stir-fry, breaking up clumps for ~{2%minutes}.

Season with @soy sauce{2%tbsp}, @sesame oil{1%tbsp}, @msg{1%tsp}, and @white pepper{0.5%tsp}.

Add @spring onions{3%stalks}(chopped) and stir for ~{30%seconds} more.

Serve immediately while hot!

Text Formatter

Human-readable format with ingredients list and numbered steps:

Usage: Cooklang::Formatters::Text.new(recipe).generate

Ingredients:
    peanut oil       1 tbsp
    garlic           3 cloves
    shallots         2
    eggs             3
    day-old rice     3 cups
    soy sauce        2 tbsp
    sesame oil       1 tbsp
    msg              1 tsp
    white pepper     0.5 tsp
    spring onions    3 stalks

Steps:
    1. Heat peanut oil in wok over high heat until smoking.
    2. Add garlic and shallots, stirfry for for 30 seconds.
    3. Add eggs and scramble until almost set.
    4. Add day-old rice and stirfry, breaking up clumps for for 2 minutes.
    5. Season with soy sauce, sesame oil, msg, and white pepper.
    6. Add spring onions and stir for for 30 seconds more.
    7. Serve immediately while hot!

Hash Formatter

Structured Ruby hash format compatible with Cook CLI:

Usage: Cooklang::Formatters::Hash.new(recipe).generate

{:metadata=>
  {:map=>
    {"title"=>"Uncle Roger's Fried Rice",
     "servings"=>"2",
     "prep_time"=>"10 minutes",
     "cook_time"=>"8 minutes"}},
 :sections=>
  [{:name=>nil,
    :content=>
     [{:type=>"step",
       :value=>
        {:items=>
          [{:type=>"text", :value=>"Heat "},
           {:type=>"ingredient", :index=>0},
           {:type=>"text", :value=>" in "},
           {:type=>"cookware", :index=>0},
           {:type=>"text", :value=>" over high heat until smoking."}],
         :number=>1}},
      {:type=>"step",
       :value=>
        {:items=>
          [{:type=>"text", :value=>"Add "},
           {:type=>"ingredient", :index=>1},
           {:type=>"text", :value=>" and "},
           {:type=>"ingredient", :index=>2},
           {:type=>"text", :value=>" and stir for "},
           {:type=>"timer", :index=>0}],
         :number=>2}}]}],
 :ingredients=>
  [{:name=>"peanut oil",
    :quantity=>
     {:value=>{:type=>"number", :value=>{:type=>"regular", :value=>1.0}},
      :unit=>"tbsp"}},
   {:name=>"garlic",
    :quantity=>
     {:value=>{:type=>"number", :value=>{:type=>"regular", :value=>3.0}},
      :unit=>"cloves"},
    :note=>"minced"},
   {:name=>"spring onions",
    :quantity=>
     {:value=>{:type=>"number", :value=>{:type=>"regular", :value=>3.0}},
      :unit=>"stalks"},
    :note=>"chopped"}],
 :cookware=>[{:name=>"wok"}],
 :timers=>
  [{:quantity=>
     {:value=>{:type=>"number", :value=>{:type=>"regular", :value=>30.0}},
      :unit=>"seconds"}},
   {:quantity=>
     {:value=>{:type=>"number", :value=>{:type=>"regular", :value=>2.0}},
      :unit=>"minutes"}}]}

JSON Formatter

JSON string format for APIs and data interchange:

Usage: Cooklang::Formatters::Json.new(recipe).generate

{
  "metadata": {
    "map": {
      "title": "Uncle Roger's Fried Rice",
      "servings": "2",
      "prep_time": "10 minutes",
      "cook_time": "8 minutes"
    }
  },
  "sections": [
    {
      "name": null,
      "content": [
        {
          "type": "step",
          "value": {
            "items": [
              {"type": "text", "value": "Heat "},
              {"type": "ingredient", "index": 0},
              {"type": "text", "value": " in "},
              {"type": "cookware", "index": 0},
              {"type": "text", "value": " over high heat until smoking."}
            ],
            "number": 1
          }
        },
        {
          "type": "step",
          "value": {
            "items": [
              {"type": "text", "value": "Add "},
              {"type": "ingredient", "index": 1},
              {"type": "text", "value": " and stir for "},
              {"type": "timer", "index": 0}
            ],
            "number": 2
          }
        }
      ]
    }
  ],
  "ingredients": [
    {
      "name": "peanut oil",
      "quantity": {
        "value": {"type": "number", "value": {"type": "regular", "value": 1.0}},
        "unit": "tbsp"
      }
    },
    {
      "name": "garlic",
      "quantity": {
        "value": {"type": "number", "value": {"type": "regular", "value": 3.0}},
        "unit": "cloves"
      },
      "note": "minced"
    }
  ],
  "cookware": [{"name": "wok"}],
  "timers": [
    {
      "quantity": {
        "value": {"type": "number", "value": {"type": "regular", "value": 30.0}},
        "unit": "seconds"
      }
    }
  ]
}

API

# Recipe object
recipe.metadata         # Hash of metadata
recipe.ingredients      # Array of Ingredient objects
recipe.cookware         # Array of Cookware objects
recipe.timers           # Array of Timer objects
recipe.steps            # Array of Step objects
recipe.steps_text       # Array of plain text steps

# Ingredient
ingredient.name         # "flour"
ingredient.quantity     # 125
ingredient.unit         # "g"
ingredient.notes        # "sifted"

# Cookware
cookware.name          # "frying pan"
cookware.quantity      # 1

# Timer
timer.name            # "pasta"
timer.duration        # 10
timer.unit            # "minutes"

Cooklang Syntax

  • Ingredients: @salt, @flour{125%g}, @onion{1}(diced)
  • Cookware: #pan, #mixing bowl{}
  • Timers: ~{5%minutes}, ~pasta{10%minutes}
  • Comments: -- line comment, [- block comment -]
  • Metadata: >> key: value or YAML front matter

Development

# Install dependencies
bundle install

# Run tests
bundle exec rspec

# Run linter
bundle exec rubocop

Resources

Contributing

Bug reports and pull requests welcome on GitHub.

License

MIT License