devapeu / cooklang
A Cooklang recipe parser for PHP.
Requires
- php: ^8.0
- symfony/yaml: ^5.4
Requires (Dev)
- pestphp/pest: ^4.7
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Cooklang recipe parser for PHP.
Installation
composer require devapeu/cooklang
Requires PHP 8.0 or higher.
Usage
use Devapeu\Cooklang\CooklangParser; $recipe = CooklangParser::parse(<<<COOKLANG >> servings: 4 Crack the @eggs{3}(any type) into a blender, then add the @flour{125%g}, @milk{250%ml} and @sea salt{1%pinch}, and blitz until smooth. Pour into a #bowl and leave to stand for ~{15%minutes}. COOKLANG); $recipe->meta; // ['servings' => '4'] $recipe->ingredients; // Ingredient[] $recipe->cookware; // ['bowl'] $recipe->instructions; // Instruction[], ingredients wrapped in <ingredient> tags by default $recipe->sections; // Section[]
CooklangParser::parse() returns a Devapeu\Cooklang\Models\Recipe:
| Property | Type | Description |
|---|---|---|
meta |
array |
Key/value metadata pairs (>> key: value) |
ingredients |
Ingredient[] |
Top-level ingredients (@ingredient) |
cookware |
string[] |
Top-level cookware (#cookware) |
instructions |
Instruction[] |
Top-level instruction sentences |
sections |
Section[] |
Named sections (= Section Name), each with its own ingredients, cookware and instructions |
Each Ingredient has name, quantity, measure, note, and optional.
Each Instruction implements Stringable (so it works directly in string
interpolation, concatenation, echo, etc. with no cast needed), and also exposes
text() for the raw tagged string explicitly and plain() to strip ingredient tags.
Formatting ingredients, cookware, and timers
By default, @ingredient, #cookware, and ~{timer} references inside
instructions are each wrapped in a matching tag — <ingredient>, <cookware>,
and <timer> respectively (e.g. <ingredient>2 cups of flour</ingredient>) — so
you can style or target them from HTML/CSS/JS.
If you don't want any markup, call plainInstructions() on the Recipe (or Section)
to get all instructions stripped at once, or plain() on an individual Instruction:
echo $recipe->instructions[0]; // "Add <ingredient>2 cups of flour</ingredient> to the mix." (implicit __toString) $recipe->instructions[0]->text(); // "Add <ingredient>2 cups of flour</ingredient> to the mix." (explicit) $recipe->instructions[0]->plain(); // "Add 2 cups of flour to the mix." $recipe->plainInstructions()[0]; // "Add 2 cups of flour to the mix."
Serializing
Recipe, Section, Ingredient, and Instruction all expose toArray(), and
implement JsonSerializable, so you can convert a parsed recipe (recursively,
including its sections) to a plain array or JSON:
$recipe->toArray(); // ['meta' => [...], 'ingredients' => [...], ..., 'sections' => [...]] json_encode($recipe);
Or customize/disable each tag independently at parse time via ParserOptions:
use Devapeu\Cooklang\ParserOptions; CooklangParser::parse($source, new ParserOptions( ingredientTag: 'span', cookwareTag: 'span', timerTag: null, // disables timer tagging entirely ));
Supported syntax
- Ingredients:
@name,@name{quantity%measure},@*optional ingredient{} - Cookware:
#cookware - Timers:
~{quantity%measure} - Notes:
@ingredient{}(note) - Sections:
= Section Nameor== Section Name == - Comments:
-- line commentand inline-- trailing comment - Metadata:
>> key: value, or a YAML frontmatter block delimited by---lines (parsed withsymfony/yaml, so nested maps and lists are supported)
Testing
composer test