Browse files

Add RecipeParser

  • Loading branch information...
1 parent 0c138b9 commit 5167034bd2544c033d5447434fcce77434ea244d @frioux committed Oct 4, 2012
Showing with 178 additions and 0 deletions.
  1. +123 −0 lib/DU/RecipeParser.pm
  2. +55 −0 t/parser.t
View
123 lib/DU/RecipeParser.pm
@@ -0,0 +1,123 @@
+package DU::RecipeParser;
+
+use 5.16.1;
+use warnings;
+
+use Pegex::Compiler;
+
+my $grammar = <<'PEGEX';
+%grammar drinkup
+%version 0.0.1
+
+recipe:
+ cocktail
+ description
+ ingredients
+ instructions?
+ footers
+
+cocktail: /
+ (: ~ <EOL> )? # Leading whitespace?
+ (<NS> <ANY>*?) # Cocktail name. Capture it.
+ ~ <EOL> # Trailing whitespace
+/
+
+description: /
+ (<ALL>*?) # Description text
+ (= <EOL> <BLANK>* <STAR> ) # Not the ingredients
+ ~ # Remaining whitespace
+/
+
+# Ingredient parts
+ingredients: ingredient+
+
+ingredient:
+ / <SPACE>* <STAR> <SPACE>* /
+ number
+ / <SPACE>* /
+ unit
+ / <SPACE>* /
+ name
+ / <SPACE>* <EOL> /
+ note?
+
+# probably need to attempt to parse 1 1/2 or 1 + 1/2
+# cribbed from json-pgx, minus the exponent version because if you use that
+# in a recipe you should change your units!
+number: /(
+ (: 0 | [1-9] <DIGIT>* )?
+ (: <DOT> <DIGIT>+ )?
+ (: ~ <PLUS>? ~ <DIGIT>+ ~ <SLASH> ~ <DIGIT>+ )?
+)/
+
+# this will probably need a hardcoded list at some point :(
+# BLKB: Or maybe use a validator? depends on the size of the list
+unit: /(<WORD>+)(: ~ of)?/
+
+name: / (<ANY>+?) ~ (= <EOL>) /
+
+note: / <HASH> <SPACE>* (<ANY>+?) <SPACE>* <EOL> /
+
+instructions: /
+ (<ALL>*?) # instruction text
+ (= <EOL> <footer_name> <COLON> ) # Not the footers
+ ~
+/
+
+footers: metadata+
+
+metadata: / (<footer_name>) <COLON> <SPACE>* (<ANY>+?) ~ <EOL> /
+
+footer_name: / [<WORDS>-]+ /
+PEGEX
+
+sub parse {
+ Pegex::Parser->new(
+ grammar => Pegex::Grammar->new(
+ tree => Pegex::Compiler->new->compile($grammar)->tree,
+ ),
+ receiver => DU::RecipeParser::Data->new(),
+ )->parse($_[1]);
+}
+
+package DU::RecipeParser::Data;
+use base 'Pegex::Receiver';
+
+my $data;
+
+sub initial {
+ $data = {};
+}
+
+sub got_cocktail {
+ $data->{name} = $_[1];
+}
+
+sub got_description {
+ $data->{description} = $_[1];
+}
+
+sub got_instructions {
+ $data->{instructions} = $_[1];
+}
+
+sub got_ingredients {
+ $data->{ingredients} = [
+ map +{
+ amount => 0 + $_->[0],
+ unit => $_->[1],
+ ingredient => $_->[2],
+ ($_->[3] ? (note => $_->[3]) : ()),
+ }, @{$_[1]}
+ ];
+}
+
+sub got_metadata {
+ $data->{lc $_[1]->[0]} = $_[1]->[1]
+}
+
+sub final {
+ return $data;
+}
+
+1;
View
55 t/parser.t
@@ -0,0 +1,55 @@
+#!/usr/bin/env perl
+
+use 5.16.1;
+use warnings;
+
+use Test::More;
+use Test::Deep;
+
+use DU::RecipeParser;
+use Devel::Dwarn;
+
+my $tom_collins = <<'RECIPE';
+Tom Collins
+
+This is a delicious beverage for a hot day.
+Refreshing.
+
+Drink it at a wedding.
+
+ * 4 ounces of Club Soda
+*2 ounce Gin
+* 1 Ounce of Lemon Juice
+# fresh is good
+*1 tbsp of Simple Syrup
+
+Source: 500 Cocktails, p27
+RECIPE
+
+cmp_deeply(
+ DU::RecipeParser->parse($tom_collins), {
+ description => "This is a delicious beverage for a hot day.\nRefreshing.\n\nDrink it at a wedding.\n",
+ ingredients => [{
+ amount => 4,
+ ingredient => "Club Soda",
+ unit => "ounces"
+ }, {
+ amount => 2,
+ ingredient => "Gin",
+ unit => "ounce"
+ }, {
+ amount => 1,
+ ingredient => "Lemon Juice",
+ note => "fresh is good",
+ unit => "Ounce"
+ }, {
+ amount => 1,
+ ingredient => "Simple Syrup",
+ unit => "tbsp"
+ }],
+ name => "Tom Collins",
+ source => "500 Cocktails, p27"
+ },
+ 'Tom Collins',
+);
+done_testing;

0 comments on commit 5167034

Please sign in to comment.