File Coverage

blib/lib/Acme/Chef.pm
Criterion Covered Total %
statement 99 110 90.0
branch 18 34 52.9
condition 2 6 33.3
subroutine 13 13 100.0
pod 3 3 100.0
total 135 166 81.3


line stmt bran cond sub pod time code
1             # Please read the pod documentation in this file for
2             # details on how to reach the author and copyright issues.
3              
4             package Acme::Chef;
5              
6 10     10   232348 use 5.006;
  10         38  
7 10     10   62 use strict;
  10         15  
  10         287  
8 10     10   50 use warnings;
  10         16  
  10         395  
9              
10 10     10   63 use Carp;
  10         15  
  10         1044  
11              
12 10     10   65 use vars qw/$VERSION/;
  10         17  
  10         790  
13             $VERSION = '1.03';
14              
15 10     10   4468 use Acme::Chef::Recipe;
  10         31  
  10         509  
16 10     10   90 use Acme::Chef::Container;
  10         16  
  10         260  
17 10     10   54 use Acme::Chef::Ingredient;
  10         15  
  10         13111  
18              
19             =head1 NAME
20              
21             Acme::Chef - An interpreter for the Chef programming language
22              
23             =head1 SYNOPSIS
24              
25             # Using the script that comes with the distribution.
26             chef.pl file.chef
27            
28             # Using the module
29             use Acme::Chef;
30            
31             my $compiled = Acme::Chef->compile($code_string);
32             print $compiled->execute();
33            
34             my $string = $compiled->dump(); # requires Data::Dumper
35             # Save it to disk, send it over the web, whatever.
36             my $reconstructed_object = eval $string;
37            
38             # or:
39             $string = $compiled->dump('autorun'); # requires Data::Dumper
40             # Save it to disk, send it over the web, whatever.
41             my $output_of_chef_program = eval $string;
42              
43             =head1 DESCRIPTION
44              
45             Chef is an esoteric programming language in which programs look like
46             recipes. I needn't mention that using it in
47             production environment, heck, using it for anything but entertainment
48             ought to result in bugs and chaos in reverse order.
49              
50             All methods provided by Acme::Chef are adequately described in the
51             synopsis. If you don't think so, you need to read the source code.
52              
53             There has been an update to the Chef specification. I have implemented
54             the changes and marked them in the following documentation with
55             "I".
56              
57             With that out of the way, I would like to present a pod-formatted
58             copy of the Chef specification from David Morgan-Mar's homepage
59             (L).
60              
61             =head2 METHODS
62              
63             This is a list of methods in this package.
64              
65             =over 2
66              
67             =item compile
68              
69             Takes Chef source code as first argument and compiles a Chef program from it.
70             This method doesn't run the code, but returns a program object.
71              
72             =cut
73              
74             sub compile {
75 9     9 1 7328 my $proto = shift;
76 9   33     81 my $class = ref $proto || $proto;
77              
78 9         22 my $code = shift;
79 9 50       38 defined $code or croak "compile takes one argument: a code string.";
80              
81 9         21 my $self = {};
82              
83 9         22 bless $self => $class;
84              
85 9         42 my @paragraphs = $self->_get_paragraphs( $code );
86 9         52 my @recipes = $self->_paragraphsToRecipes(\@paragraphs);
87              
88 9         71 $_->compile() foreach @recipes;
89              
90 9         42 $self->{start_recipe} = $recipes[0]->recipe_name();
91              
92             $self->{recipes} = {
93 9         25 map { ($_->recipe_name(), $_) } @recipes
  10         30  
94             };
95              
96 9         38 return $self;
97             }
98              
99              
100             =item execute
101            
102             Takes no arguments. Runs the program and returns its output.
103              
104             =cut
105              
106             sub execute {
107 26     26 1 25930 my $self = shift;
108              
109 26         147 my $start_recipe = $self->{recipes}->{ $self->{start_recipe} }->new();
110              
111 26         107 $start_recipe->execute($self->{recipes});
112              
113 26         84 return $start_recipe->output();
114             }
115              
116              
117             =item dump
118            
119             Takes one optional argument. If it equals 'autorun',
120             dump returns a string that, when evaluated, executes
121             the program and returns the output.
122              
123             If the argument does not equal 'autorun', a different
124             string is returned that reconstructs the Acme::Chef
125             object.
126              
127             =cut
128              
129             sub dump {
130 18     18 1 8760 my $self = shift;
131 18         49 my $type = shift;
132 18 100       71 $type = '' if not defined $type;
133              
134 18         36 local $@ = undef;
135 18         8323 require Data::Dumper;
136              
137 18         56361 my $dumper = Data::Dumper->new([$self], ['self']);
138 18         529 $dumper->Indent(0);
139 18         225 $dumper->Purity(1);
140              
141 18         100 my $dump = $dumper->Dump();
142              
143 18 100       8078 if ($type =~ /^autorun$/) {
144 9         86 $dump = 'do{my ' . $dump . ' bless $self => "' . (__PACKAGE__) . '"; $self->execute();} ';
145             } else {
146 9         64 $dump = 'do{my ' . $dump . ' bless $self => "' . (__PACKAGE__) . '";} ';
147             }
148              
149 18         769 return $dump;
150             }
151              
152              
153             # private function _get_paragraphs
154              
155             sub _get_paragraphs {
156 9     9   21 my $self = shift;
157              
158 9         19 my $string = shift;
159              
160 9         65 $string =~ s/^\s+//;
161 9         319 $string =~ s/\s+$//;
162              
163 9         100 return split /\n{2,}/, $string;
164             }
165              
166              
167             # private function _paragraphsToRecipes
168             #
169             # Constructs recipes from an array ref of paragraphs.
170              
171             sub _paragraphsToRecipes {
172 9     9   20 my $self = shift;
173              
174 9         19 my $paragraphs = shift;
175 9 50 33     92 $paragraphs = shift if not defined $paragraphs or not ref $paragraphs;
176              
177 9         50 while (chomp @$paragraphs) {}
178              
179 9         17 my @recipes;
180              
181 9         50 my $paragraph_no = 0;
182 9         41 while (@$paragraphs) {
183 10         23 my $recipe_name = shift @$paragraphs;
184 10         17 $paragraph_no++;
185 10 50       145 $recipe_name =~ /^[ ]*([\-\w][\- \w]*)\.[ ]*$/
186             or croak "Invalid recipe name specifier in paragraph no. $paragraph_no.";
187 10         47 $recipe_name = lc($1);
188              
189 10 50       42 last unless @$paragraphs;
190 10         31 my $comments = shift @$paragraphs;
191 10         19 $paragraph_no++;
192 10         20 my $ingredients;
193 10 100       55 if ( $comments =~ /^[ ]*Ingredients\.[ ]*\n/ ) {
194 5         9 $ingredients = $comments;
195 5         8 $comments = '';
196             } else {
197 5 50       23 last unless @$paragraphs;
198 5         12 $ingredients = shift @$paragraphs;
199 5         13 $paragraph_no++;
200             }
201              
202 10 50       35 last unless @$paragraphs;
203 10         26 my $cooking_time = shift @$paragraphs;
204 10         18 $paragraph_no++;
205 10         16 my $temperature;
206              
207 10 50       46 if ($cooking_time =~ /^[ ]*Cooking time:[ ]*(\d+)(?: hours?| minutes?)\.[ ]*$/) {
208 0         0 $cooking_time = $1;
209 0 0       0 last unless @$paragraphs;
210 0         0 $temperature = shift @$paragraphs;
211 0         0 $paragraph_no++;
212             } else {
213 10         28 $temperature = $cooking_time;
214 10         23 $cooking_time = '';
215             }
216              
217 10         15 my $method;
218 10 50       45 if ($temperature =~ /^[ ]*Pre-heat oven to (\d+) degrees Celsius(?: gas mark (\d+))?\.[ ]*$/) {
219 0         0 $temperature = $1;
220 0 0       0 $temperature .= ",$2" if defined $2;
221 0 0       0 last unless @$paragraphs;
222 0         0 $method = shift @$paragraphs;
223 0         0 $paragraph_no++;
224             } else {
225 10         26 $method = $temperature;
226 10         28 $temperature = '';
227             }
228              
229 10 50       59 $method =~ /^[ ]*Method\.[ ]*\n/
230             or croak "Invalid method specifier in paragraph no. $paragraph_no.";
231            
232 10         22 my $serves = '';
233 10 100       42 if (@$paragraphs) {
234 7         12 $serves = shift @$paragraphs;
235 7 50       40 if ($serves =~ /^[ ]*Serves (\d+)\.[ ]*$/) {
236 7         25 $serves = $1;
237 7         22 $paragraph_no++;
238             } else {
239 0         0 unshift @$paragraphs, $serves;
240 0         0 $serves = '';
241             }
242             }
243              
244 10         119 push @recipes, Acme::Chef::Recipe->new(
245             name => $recipe_name,
246             comments => $comments,
247             ingredients => $ingredients,
248             cooking_time => $cooking_time,
249             temperature => $temperature,
250             method => $method,
251             serves => $serves,
252             );
253            
254             }
255            
256 9         36 return @recipes;
257             }
258              
259              
260             1;
261             __END__