MealMaster - Parse MealMaster format recipe files


MealMaster documentation Contained in the MealMaster distribution.

Index


Code Index:

NAME

Top

MealMaster - Parse MealMaster format recipe files

SYNOPSIS

Top

  my $mm = MealMaster->new();
  my @recipes = $mm->parse("t/31000.mmf");
  foreach my $r (@recipes) {
    print "Title: " . $r->title . "\n";
    print "Categories: " . join(", ", sort @{$r->categories}) . "\n";
    print "Yield: " . $r->yield . "\n";
    print "Directions: " . $r->directions . "\n";
    print "Ingredients:\n";
    foreach my $i (@{$r->ingredients}) {
      print "  " . $i->quantity .
             " " . $i->measure  .
             " " . $i->product . 
             "\n";
    }

DESCRIPTION

Top

People like to share food recipes on the internet. MealMaster is a popular program for collating recipes. The MealMaster module parses the MealMaster recipe export format, providing you with recipe objects. You can recognize these MealMaster format files as they generally start with "Recipe via Meal-Master".

CONSTRUCTOR

Top

new

The constructor. Takes no arguments:

  my $mm = MealMaster->new();

METHODS

Top

parse($filename)

The parse method takes a filename and recipes an array of MealMaster::Recipe objects representing the recipes in the file:

  my @recipes = $mm->parse("t/31000.mmf");

SEE ALSO

Top

MealMaster::Recipe, MealMaster::Ingredient

AUTHOR

Top

Leon Brocard, <acme@astray.com>

COPYRIGHT

Top


MealMaster documentation Contained in the MealMaster distribution.

package MealMaster;
use strict;
use MealMaster::Recipe;
use MealMaster::Ingredient;
our $VERSION = "0.28";
use base qw(Class::Accessor::Chained::Fast);

sub parse {
  my ($self, $filename) = @_;

  open(my $fh, $filename) || die $!;
  my $file = join '', <$fh>;

  return unless $file =~ /^(MMMMM|-----).+Meal-Master/;

  my @parts = split /^(?:MMMMM|-----).+Meal-Master.+$/m, $file;
  my @recipes;

  foreach my $part (@parts) {
    $part =~ s/^\s+//;
    my $recipe = MealMaster::Recipe->new;
    my $lines  = [ split /\n/, $part ];

    my $line;

    while (1) {
      $line = $self->_newline($lines);
      last unless defined $line;
      last if $line =~ /Title:/;
    }
    next unless defined $line;

    my ($title) = $line =~ /Title: (.+)$/;
    next unless $title;
    $title =~ s/^ +//;
    $title =~ s/ +$//;
    $recipe->title($title);

    $line = $self->_newline($lines);
    my ($categories) = $line =~ /Categories: (.+)$/;

    my @categories;
    @categories = split ', ', $categories if $categories;
    $recipe->categories(\@categories);

    $line = $self->_newline($lines);
    my ($yield) = $line =~ /(?:Yield|Servings): +(.+)$/;
    next unless $yield;
    $recipe->yield($yield);

    my $dflag = 0;
    my $ingredients;
    my $directions;

    while (defined($line = $self->_newline($lines))) {
      next unless $line;

      last if (!defined $line);
      next if (($dflag == 0) && ($line =~ m|^\s*$|));

      if ($line =~ /^[M-]+$/) {
        last;
      } elsif ($line =~ m(^[M|-]{4,})) {
        $line =~ s|^MMMMM||;
        $line =~ s|^\-+||;
        $line =~ s|\-+$||;
        $line =~ s|^ +||;
        $line =~ s|:$||;
        $directions .= "$line\n";
      } elsif ($line =~ m/^ *([A-Z ]+):$/) {
        $directions .= "$line\n";
      } elsif (length($line) > 12
        && (substr($line, 0, 7) =~ m|^[ 0-9\.\/]+$|)
        && (substr($line, 7, 4) =~ m|^ .. $|))
      {
        $ingredients .= "$line\n";
      } else {
        $line =~ s|^\s+||;
        if ($line) {
          $directions .= "$line\n";
          $dflag = 1;
        }
      }
    }

    $ingredients = $self->_parse_ingredients($ingredients);

    $recipe->ingredients($ingredients);
    $recipe->directions($directions);

    push @recipes, $recipe;
  }
  return @recipes;
}

sub _parse_ingredients {
  my ($self, $text) = @_;

  return [] unless $text;

  my @ingredients;
  my @ingredients2;
  foreach my $line (split "\n", $text) {

    # Two-column case:
    #    1/3 c  Instant cream of wheat

    #  1 1/3 c  Brown Sugar, Firmly Packed          1 c  Unbleached Flour
    # 12345678910121416182022242628303234363840424446485052
    if (length($line) > 40 && $line =~ /^.{40}  ...... .. ./) {
      my $quantity = substr($line, 0,  7);
      my $measure  = substr($line, 8,  2);
      my $product  = substr($line, 11, 29);
      $quantity =~ s/^ +//;
      $measure  =~ s/ ? ?$//;
      $product  =~ s/ +$//;
      my $i =
        MealMaster::Ingredient->new->quantity($quantity)->measure($measure)
        ->product($product);
      push @ingredients, $i;
      $quantity = substr($line, 41, 7);
      $measure  = substr($line, 49, 2);
      $product  = substr($line, 52);
      $quantity =~ s/^ +//;
      $measure  =~ s/ ? ?$//;
      $i =
        MealMaster::Ingredient->new->quantity($quantity)->measure($measure)
        ->product($product);
      push @ingredients2, $i;
    } else {

      # Simple case:
      #    1/4 c  Butter or margarine
      my $quantity = substr($line, 0, 7);
      my $measure  = substr($line, 8, 2);
      my $product = substr($line, 11);
      $quantity =~ s/^ +//;
      $measure  =~ s/ ? ?$//;
      $product  =~ s/ +$//;
      my $i =
        MealMaster::Ingredient->new->quantity($quantity)->measure($measure)
        ->product($product);
      push @ingredients, $i;
    }
  }

  @ingredients = (@ingredients, @ingredients2);

  # Fold
  #    1/3 c  Instant cream of wheat
  #           -(prepared)
  # to:
  #    1/3 c  Instant cream of wheat (prepared)

  my @folded_ingredients = ($ingredients[0]);
  foreach my $n (1 .. @ingredients - 1) {
    my $i = $ingredients[$n];
    if ( not defined $i->quantity
      && not defined $i->measure
      && $i->product =~ /^-/)
    {
      my $extra = $i->product;
      $extra =~ s/^--?//;
      my $old = $folded_ingredients[-1]->product;
      my $new = "$old $extra";
      $folded_ingredients[-1]->product($new);
    } else {
      push @folded_ingredients, $i;
    }
  }

  return \@folded_ingredients;
}

sub _newline {
  my ($self, $lines) = @_;
  my $line = shift @$lines;
  return unless defined $line;
  $line =~ s{\r|\n|\t}{}g;
  chomp $line;
  return $line;
}

1;

__END__