File Coverage

blib/lib/ACME/QuoteDB/LoadDB.pm
Criterion Covered Total %
statement 228 276 82.6
branch 72 136 52.9
condition 33 59 55.9
subroutine 35 38 92.1
pod 12 12 100.0
total 380 521 72.9


line stmt bran cond sub pod time code
1             #$Id: LoadDB.pm,v 1.30 2009/09/30 07:37:09 dinosau2 Exp $
2             # /* vim:et: set ts=4 sw=4 sts=4 tw=78: */
3              
4             package ACME::QuoteDB::LoadDB;
5              
6 7     7   127732 use 5.008005; # require perl 5.8.5, re: DBD::SQLite Unicode
  7         25  
  7         292  
7 7     7   42 use warnings;
  7         16  
  7         386  
8 7     7   43 use strict;
  7         12  
  7         258  
9              
10             #use criticism 'brutal'; # use critic with a ~/.perlcriticrc
11              
12 7     7   1206 use version; our $VERSION = qv('0.1.1');
  7         3139  
  7         48  
13              
14             # with Text::CSV only use 'perl csv loader'
15             # 'one time' db load performance not a concern
16 7     7   967 BEGIN {local $ENV{PERL_TEXT_CSV} = 0}
17              
18 7     7   10250 use aliased 'ACME::QuoteDB::DB::Attribution' => 'Attr';
  7         6114  
  7         44  
19 7     7   817 use aliased 'ACME::QuoteDB::DB::QuoteCatg' => 'QuoteCatg';
  7         13  
  7         45  
20 7     7   692 use aliased 'ACME::QuoteDB::DB::Category' => 'Catg';
  7         16  
  7         42  
21 7     7   912 use aliased 'ACME::QuoteDB::DB::Quote' => 'Quote';
  7         21  
  7         41  
22 7     7   1045 use aliased 'ACME::QuoteDB::DB::DBI' => 'QDBI';
  7         14  
  7         38  
23 7     7   1043 use File::Basename qw/dirname basename/;
  7         18  
  7         739  
24 7     7   45 use File::Glob qw(:globally :nocase);
  7         16  
  7         409  
25 7     7   23393 use Encode qw/is_utf8 decode/;
  7         95612  
  7         764  
26 7     7   1543 use Data::Dumper qw/Dumper/;
  7         6649  
  7         403  
27 7     7   125 use Carp qw/carp croak/;
  7         16  
  7         445  
28 7     7   8826 use Text::CSV;
  7         122143  
  7         51  
29 7     7   328 use Readonly;
  7         18  
  7         491  
30 7     7   47 use DBI;
  7         15  
  7         31498  
31              
32             # if not in utf8 latin1 is assumed
33             my $FILE_ENCODING = 'iso-8859-1';
34              
35             Readonly my @QUOTE_FIELDS => qw/quote name source catg rating/;
36              
37             # XXX refactor
38             sub new {
39 13     13 1 6670 my ($class, $args) = @_;
40              
41             # TODO encapsulation
42 13         55 my $self = bless {}, $class;
43              
44             # store each record we extract - keys map to database fields
45             # TODO proper encapsulation
46 13         95 $self->{record} = {};
47 13         60 $self->{record}->{quote} = q{};
48 13         47 $self->{record}->{rating} = q{};
49 13         40 $self->{record}->{name} = q{};
50 13         50 $self->{record}->{source} = q{};
51 13         189 $self->{record}->{catg} = q{};
52              
53 13         45 $self->{file} = $args->{file};
54 13         46 $self->{dir} = $args->{dir};
55 13         39 $self->{data} = $args->{data};
56 13         40 $self->{file_format} = $args->{file_format};
57 13   33     110 $FILE_ENCODING = $args->{file_encoding} || $FILE_ENCODING;
58 13         47 $self->{delim} = $args->{delimiter};
59 13         48 $self->{verbose} = $args->{verbose};
60 13         54 $self->{category} = $args->{category};
61 13         38 $self->{rating} = $args->{rating};
62 13         39 $self->{attr_source} = $args->{attr_source};
63 13         40 $self->{orig_args} = $args;
64 13         41 $self->{success} = undef;
65              
66             # start with if set
67 13         49 $self->{record}->{rating} = $self->{rating};
68 13         40 $self->{record}->{name} = $self->{attr_source};
69 13         42 $self->{record}->{source} = $self->{attr_source};
70 13 50       64 if (ref $self->{category} eq 'ARRAY') {
71 0         0 $self->{record}->{catg} = ();
72 0         0 foreach my $c (@{$self->{category}}){
  0         0  
73 0         0 push @{$self->{record}->{catg}}, $c;
  0         0  
74             }
75             }
76             else {
77 13         50 $self->{record}->{catg} = $self->{category};
78             }
79              
80             # db connection info
81 13 100       68 if ($ENV{ACME_QUOTEDB_DB}) {
82 1         5 $self->{db} = $ENV{ACME_QUOTEDB_DB};
83 1         4 $self->{host} = $ENV{ACME_QUOTEDB_HOST};
84 1         4 $self->{user} = $ENV{ACME_QUOTEDB_USER};
85 1         6 $self->{pass} = $ENV{ACME_QUOTEDB_PASS};
86             }
87              
88 13 100       60 if (!$args->{dry_run}){$self->{write_db} = 1};
  12         37  
89             #if ($args->{create_db}) {$self->create_db};
90 13 100       60 if ($args->{create_db}) {$self->create_db_tables};
  10         64  
91              
92 12         100 return $self;
93             }
94              
95             sub set_record {
96 1569     1569 1 157431 my ($self, $field, $value) = @_;
97              
98             # TODO support mult-field simultanous loading
99              
100 1569 100       6139 if ($value) {
101 1502         5237 $self->{record}->{$field} = $value;
102             }
103              
104 1569         5250 return $self;
105             }
106              
107             sub debug_record {
108 0     0 1 0 my ($self) = @_;
109              
110 0         0 print Dumper $self->{record};
111              
112 0         0 return;
113             }
114              
115             sub get_record {
116 5082     5082 1 345401 my ($self, $field) = @_;
117              
118 5082 50       17440 if (not $field){return $self}
  0         0  
119              
120 5082         50042 return $self->{record}->{$field};
121             }
122              
123             sub data_to_db {
124 9     9 1 13282 my ($self) = @_;
125              
126 9 50 66     231 if ($self->{file} and $self->{data} and $self->{dir}){
    50 33        
      66        
127 0         0 croak 'only file, data or dir as arg but not both'
128             }
129             elsif (! ($self->{file} or $self->{data} or $self->{dir})) {
130 0         0 croak 'file, data or dir needed as arg'
131             }
132              
133 9 100       60 if ($self->{file}) {
    50          
    50          
134 8         52 $self->_parse_file($self->{file});
135             }
136             elsif ($self->{data}) {
137 0         0 $self->_parse_data($self->{data});
138             }
139             elsif ($self->{dir}) {
140 1         5 my $dir = $self->{dir};
141 1         4 my $e = q{};
142 1         451 foreach my $f (<$dir*>) {
143             #if (! (-e $f) || -z $f) # no worky - need path info
144 4         109 $self->_parse_file($f);
145 4         16 $e++;
146             }
147 1 50       12 if (! $e){croak 'no files to parse in: ', Dumper $dir;};
  0         0  
148             }
149             else {
150 0         0 croak 'no file source in args!', Dumper $self;
151             }
152              
153 9         44 return;
154             }
155              
156             sub _parse_file {
157 12     12   44 my ($self, $file) = @_;
158              
159 12 50       483 if (!-f $file) { croak "file not found: $file" }
  0         0  
160              
161 12 50       75 if ($self->{verbose}){warn "processing file: $file\n"};
  0         0  
162              
163 12 100 100     430 if (($self->{file_format} eq 'csv') || ($self->{file_format} eq 'tsv')){
    50 66        
164 7         47 $self->dbload_from_csv($file);
165             }
166             elsif (($self->{file_format} eq 'html') || ($self->{file_format} eq 'custom')){
167             # not supported, too many possibilities
168             # supply your own
169 5         33 $self->dbload($file);
170             }
171             else {
172 0         0 croak 'unsupported file format requested, format must be csv or tsv';
173             }
174              
175 12         2661 return;
176             }
177              
178             sub _parse_data {
179 0     0   0 my ($self, $data) = @_;
180              
181 0 0       0 if (!$data) {croak "data empty $data"}
  0         0  
182              
183 0 0       0 if ($self->{verbose}){carp 'processing data:'};
  0         0  
184              
185 0 0 0     0 if ($self->{file_format} =~ /(?:csv|tsv)/sm) {
    0          
186 0         0 croak 'TODO: not yet supported';
187             #$self->dbload_from_csv($data);
188             }
189             elsif (($self->{file_format} eq 'html') || ($self->{file_format} eq 'custom')){
190             # not supported, too many possibilities
191             # supply your own
192 0         0 $self->dbload($data);
193             }
194             else {
195 0         0 croak 'unsupported file format requested, '
196             .'format must be csv, tsv. html, custom also possible';
197             }
198              
199 0         0 return $self;
200             }
201              
202             sub _confirm_header_order {
203 7     7   66227 my ($hr) = @_;
204              
205 7 0 33     178 return ($hr->{quote} eq 'Quote'
206             and $hr->{name} eq 'Attribution Name',
207             and $hr->{source} eq 'Attribution Source',
208             and $hr->{catg} eq 'Category',
209             and $hr->{rating} eq 'Rating')
210             or croak 'incorrect headers or header order';
211             }
212              
213             sub dbload_from_csv {
214 7     7 1 22 my ($self, $file) = @_;
215              
216 7   100     55 my $delim = $self->{delim} || ',';
217 7         175 my $csv = Text::CSV->new({
218             sep_char => $delim,
219             binary => 1
220             });
221 7         1374 $csv->column_names (@QUOTE_FIELDS);
222              
223 7   33     4627 open my $source, '<:encoding(utf8)', $file || croak $!;
224              
225 7         733 _confirm_header_order($csv->getline_hr($source));
226              
227 7         50 while (my $hr = $csv->getline_hr($source)) {
228 182 50 33     177223 next unless $hr->{quote} and $hr->{name};
229              
230 182 50       752 if ($self->{verbose}){
231 0         0 print "\n",
232             'Quote: ', $hr->{quote},"\n",
233             'Name: ', $hr->{name},"\n",
234             'Source: ', $hr->{source},"\n",
235             'Category:', $hr->{catg},"\n",
236             'Rating: ', $hr->{rating},"\n\n";
237             };
238              
239 182         1110 $self->set_record(quote => $hr->{quote});
240 182         1383 $self->set_record(name => $hr->{name});
241 182   100     1950 $self->set_record(source => ($self->{attr_source} || $hr->{source}));
242             # take user defined first
243             # TODO support multi categories
244 182   66     1649 $self->set_record(catg => ($self->{category} || $hr->{catg}));
245 182   100     1181 $self->set_record(rating => ($self->{rating} || $hr->{rating}));
246 182         6120 $self->write_record;
247             }
248 7 50       1519 close $source or carp $!;
249              
250 7         246 return $self;
251             }
252              
253             # sub class this - i.e. provide this method in your code (see test
254             # 01-load_quotes.t)
255             sub dbload {
256 0     0 1 0 croak 'Override this. Provide this method in a sub class (child) of this object';
257             # see tests: t/01-load_quotes.t for examples
258             }
259              
260             sub _to_utf8 {
261 312     312   977 my ($self) = @_;
262              
263             RECORD:
264 312         3195 foreach my $r (@QUOTE_FIELDS){
265 1560         70965 my $val = $self->{record}->{$r};
266 1560 100       15644 if (ref $val eq 'ARRAY'){
267 1         3 foreach my $v (@{$val}){
  1         5  
268 12 100       238 if (!is_utf8($v)){
269 6         7 push @{$self->{record}->{$r}}, decode($FILE_ENCODING, $v);
  6         26  
270             }
271             }
272             }
273             else {
274 1559 100       8293 if (!is_utf8($val)){
275 1509         5651 $self->{record}->{$r} = decode($FILE_ENCODING, $val);
276             }
277             }
278             }
279              
280 312         11082 return $self;
281             }
282              
283             # XXX refactor (the following 3 methods)
284              
285             # one person can have many quotes, is this person in our attribution table
286             # already?
287             sub _get_id_if_attr_name_exist {
288 312     312   743 my ($self) = @_;
289              
290 312         730 my $attr_id = q{};
291              
292             RECS:
293 312         4973 foreach my $c_obj (Attr->retrieve_all){
294 3374 50       1199666 next RECS if not $c_obj->name;
295 3374 100       3728036 if ($c_obj->name eq $self->get_record('name')){
296             # use attribution id if already exists
297 190         1486 $attr_id = $c_obj->attr_id;
298             }
299             }
300 312         100614 return $attr_id;
301             }
302              
303             sub _get_id_if_catg_exist {
304 294     294   991 my ($self, $ctg) = @_;
305              
306 294         713 my $catg_id = q{};
307             # get category id
308             RECS:
309 294         3805 foreach my $c_obj (Catg->retrieve_all){
310 338 50       307713 next RECS if not $c_obj->catg;
311 338 100       343023 if ($c_obj->catg eq $ctg){
312             # use cat_id if already exists
313 280         35572 $catg_id = $c_obj->catg_id;
314             }
315             }
316 294         39342 return $catg_id;
317             }
318              
319             #TODO : refactor
320             sub write_record {
321 312     312 1 1384 my ($self) = @_;
322              
323 312         1905 $self->_to_utf8;
324              
325 312 50 33     1749 if ($self->{verbose} and $self->get_record('name')){
326 0         0 print 'Attribution Name: ',$self->get_record('name'),"\n";
327             };
328              
329 312         1549 my $attr_id = $self->_get_id_if_attr_name_exist;
330             # nope, ok, add them
331 312 100       67453 if (not $attr_id) { # attribution record does not already exist,
332             # create new entry
333 122 100       660 if ($self->{write_db}) {
334 93         449 $attr_id = Attr->insert({
335             name => $self->get_record('name'),
336             });
337             }
338             }
339              
340 312         4801902 my $catg_ids = ();
341 312 100       1771 if ($self->{write_db}) {
342 283         2064 my ($catg) = $self->get_record('catg');
343 283 100       1501 if (! ref $catg){ # 'single' value
    50          
344 282         1519 my $catg_id = $self->_get_id_if_catg_exist($catg);
345 282 100       13883 if (!$catg_id) {
346             # category does not already exist,
347             # create new entry
348 9         112 $catg_id = Catg->insert({catg => $catg});
349             }
350 282         490990 push @{$catg_ids}, $catg_id;
  282         1502  
351             } # support multi catg
352             elsif (ref $catg eq 'ARRAY'){
353 1         2 foreach my $c (@{$catg}){
  1         5  
354 12         56 my $catg_id = $self->_get_id_if_catg_exist($c);
355 12 100       706 if (!$catg_id) { # category does not already exist,
356             # create new entry
357 5         40 $catg_id = Catg->insert({catg => $c});
358             }
359 12         330364 push @{$catg_ids}, $catg_id;
  12         59  
360             }
361             }
362             }
363              
364 312         1745 $self->_display_vals_if_verbose;
365              
366 312 100       1231 if ($self->{write_db}) {
367 283 50       1578 my $qid = Quote->insert({
368             attr_id => $attr_id,
369             quote => $self->get_record('quote'),
370             source => $self->get_record('source'),
371             rating => $self->get_record('rating')
372             }) or croak $!;
373              
374 283 50       13464487 if ($qid) {
375 283         33737 my $id;
376 283         844 foreach my $cid (@{$catg_ids}){
  283         2808  
377 294 50       1480491 $id = QuoteCatg->insert({
378             quot_id => $qid,
379             catg_id => $cid,
380             }) or croak $!;
381             }
382             }
383             }
384             # confirmation?
385             # TODO add a test for failure
386 312 50 66     11272789 if ($self->{write_db} and not $attr_id) {croak 'db write not successful'}
  0         0  
387              
388             #$self->set_record(undef);
389 312         71945 $self->{record} = {};
390 312         8072 $self->_reset_orig_args;
391              
392 312 100       1457 if ($self->{write_db}) {
393 283         1535 $self->success(1);
394             }
395              
396 312         1375 return $self->success;
397             }
398              
399             sub _reset_orig_args {
400 312     312   940 my ($self) = @_;
401              
402 312         2444 $self->{record}->{rating} = $self->{orig_args}->{rating};
403 312         1700 $self->{record}->{name} = $self->{orig_args}->{attr_source};
404 312         1424 $self->{record}->{source} = $self->{orig_args}->{attr_source};
405 312 50       1930 if (ref $self->{orig_args}->{category} eq 'ARRAY') {
406 0         0 foreach my $c (@{$self->{orig_args}->{category}}){
  0         0  
407 0         0 push @{$self->{record}->{catg}}, $c;
  0         0  
408             }
409             }
410             else {
411 312         2231 $self->{record}->{catg} = $self->{orig_args}->{category};
412             }
413              
414             }
415              
416             sub success {
417 608     608 1 2512 my ($self, $flag) = @_;
418              
419 608   100     2325 $self->{success} ||= $flag;
420              
421 608         8800 return $self->{success};
422             };
423              
424             sub _display_vals_if_verbose {
425 312     312   985 my ($self) = @_;
426              
427 312 50       1698 if ($self->{verbose}){
428             #print 'Quote: ', $self->get_record('quote'),"\n";
429             #print 'Source: ', $self->get_record('source'),"\n";
430             #print 'Category: ',$self->get_record('catg'),"\n";
431             #print 'Rating: ', $self->get_record('rating'),"\n";
432 0         0 print Dumper $self->{record};
433             }
434              
435 312         710 return $self;
436             }
437              
438             #sub create_db {
439             # my ($self) = @_;
440             #
441             # if ($self->{db} and $self->{host}) {
442             # $self->create_db_mysql();
443             # }
444             #}
445              
446             sub create_db_tables {
447 10     10 1 26 my ($self) = @_;
448              
449 10 100 66     71 if ($self->{db} and $self->{host}) {
450             #$self->create_db_mysql();
451 1         6 $self->create_db_tables_mysql();
452             }
453             else {
454 9         36 create_db_tables_sqlite();
455             }
456              
457 9         44 return $self;
458              
459             }
460              
461             # XXX we want the user to supply a pre created database.
462             # created as such 'CREATE DATABASE $dbn CHARACTER SET utf8 COLLATE utf8_general_ci'
463             # this get's into too many isseuwith privs and database creation
464             #Sat Aug 22 13:42:37 PDT 2009
465             # did this:
466             #mysql> CREATE DATABASE acme_quotedb CHARACTER SET utf8 COLLATE utf8_general_ci;
467             #mysql> grant usage on *.* to acme_user@localhost identified by 'acme';
468             #mysql> grant all privileges on acme_quotedb.* to acme_user@localhost ;
469              
470             #sub create_db_mysql {
471             # my ($self) = @_;
472             #
473             # # hmmmm, what about priv's access, etc
474             # # maybe user need to supply a db, they have
475             # # access to, already created (just the db though)
476             # ## create our db
477             # #my $dbhc = DBI->connect('DBI:mysql:database=mysql;host='
478             # # .$self->{host}, $self->{user}, $self->{pass})
479             # # || croak "db cannot be accessed $! $DBI::errstr";
480             #
481             # #my $dbn = $self->{db};
482             # #my $db = qq(CREATE DATABASE $dbn CHARACTER SET utf8 COLLATE utf8_general_ci);
483             # # eval {
484             # # $dbhc->do($db) or croak $dbhc->errstr;
485             # # };
486             # # $@ and croak 'Cannot create database!';
487             # # $dbhc->disconnect; $dbhc = undef;
488             #
489             # my $drh = DBI->install_driver('mysql');
490             # my $rc = $drh->func("dropdb", $self->{db},
491             # [$self->{host}, $self->{user}, $self->{password}],
492             # 'admin'
493             # );
494             #
495             # $rc = $drh->func("createdb", $self->{db},
496             # [$self->{host}, $self->{user}, $self->{password}],
497             # 'admin'
498             # );
499             #}
500              
501             # XXX refactor with sqlite
502             sub create_db_tables_mysql {
503 1     1 1 3 my ($self) = @_;
504              
505             # connect to our db
506 1         5 my $c = $self->{db}.';host='.$self->{host};
507 1   0     17 my $dbh = DBI->connect(
508             "DBI:mysql:database=$c", $self->{user}, $self->{pass})
509             || croak "db cannot be accessed $! $DBI::errstr";
510              
511 0         0 eval {
512 0 0       0 $dbh->do('DROP TABLE IF EXISTS quote;') or croak $dbh->errstr;
513              
514 0 0       0 $dbh->do('CREATE TABLE IF NOT EXISTS quote (
515             quot_id INTEGER NOT NULL AUTO_INCREMENT,
516             attr_id INTEGER,
517             quote TEXT,
518             source TEXT,
519             rating REAL,
520             PRIMARY KEY(quot_id)
521             );')
522             #)CHARACTER SET utf8 COLLATE utf8_general_ci;
523             #) ENGINE = MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
524             or croak $dbh->errstr;
525              
526 0 0       0 $dbh->do('DROP TABLE IF EXISTS attribution;') or croak $dbh->errstr;
527              
528 0 0       0 $dbh->do('CREATE TABLE IF NOT EXISTS attribution (
529             attr_id INTEGER NOT NULL AUTO_INCREMENT,
530             name TEXT,
531             PRIMARY KEY(attr_id)
532             );') or croak $dbh->errstr;
533              
534 0 0       0 $dbh->do('DROP TABLE IF EXISTS category;') or croak $dbh->errstr;
535              
536 0 0       0 $dbh->do('CREATE TABLE IF NOT EXISTS category (
537             catg_id INTEGER NOT NULL AUTO_INCREMENT,
538             catg TEXT,
539             PRIMARY KEY(catg_id)
540             );') or croak $dbh->errstr;
541              
542 0 0       0 $dbh->do('DROP TABLE IF EXISTS quote_catg;') or croak $dbh->errstr;
543              
544 0 0       0 $dbh->do('CREATE TABLE IF NOT EXISTS quote_catg (
545             id INTEGER NOT NULL AUTO_INCREMENT,
546             catg_id INTEGER,
547             quot_id INTEGER,
548             PRIMARY KEY(id)
549             );') or croak $dbh->errstr;
550              
551              
552 0 0       0 $dbh->disconnect or warn $dbh->errstr;
553              
554 0         0 $dbh = undef;
555             };
556              
557 0 0       0 return $@ and croak 'Cannot create database tables!';
558              
559             }
560              
561             sub create_db_tables_sqlite {
562              
563 9     9 1 75 my $db = QDBI->get_current_db_path;
564              
565             #XXX is there really no way to do this with the existing
566             # connection?!(class dbi)
567 9   33     231 my $dbh = DBI->connect('dbi:SQLite:dbname='.$db, '', '')
568             || croak "$db cannot be accessed $! $DBI::errstr";
569              
570             #-- sqlite does not have a varchar datatype: VARCHAR(255)
571             #-- A column declared INTEGER PRIMARY KEY will autoincrement.
572 9         13892 eval {
573 9 50       82 $dbh->do('DROP TABLE IF EXISTS quote;') or croak $dbh->errstr;
574              
575 9 50       1209740 $dbh->do('CREATE TABLE IF NOT EXISTS quote (
576             quot_id INTEGER PRIMARY KEY,
577             attr_id INTEGER,
578             quote TEXT,
579             source TEXT,
580             rating REAL
581             );')
582             or croak $dbh->errstr;
583              
584 9 50       279795 $dbh->do('DROP TABLE IF EXISTS attribution;') or croak $dbh->errstr;
585              
586 9 50       135639 $dbh->do('CREATE TABLE IF NOT EXISTS attribution (
587             attr_id INTEGER PRIMARY KEY,
588             name TEXT
589             );') or croak $dbh->errstr;
590              
591 9 50       192421 $dbh->do('DROP TABLE IF EXISTS category;') or croak $dbh->errstr;
592              
593 9 50       538262 $dbh->do('CREATE TABLE IF NOT EXISTS category (
594             catg_id INTEGER PRIMARY KEY,
595             catg TEXT
596             );') or croak $dbh->errstr;
597              
598 9 50       169418 $dbh->do('DROP TABLE IF EXISTS quote_catg;') or croak $dbh->errstr;
599              
600 9 50       224002 $dbh->do('CREATE TABLE IF NOT EXISTS quote_catg (
601             id INTEGER PRIMARY KEY,
602             catg_id INTEGER,
603             quot_id INTEGER
604             );') or croak $dbh->errstr;
605              
606 9 50       370744 $dbh->disconnect or carp $dbh->errstr;
607              
608 9         59 $dbh = undef;
609             };
610              
611 9 0       868 return $@ and croak 'Cannot create database tables!';
612              
613             }
614              
615             q(My cat's breath smells like cat food. --Ralph Wiggum);
616              
617              
618             __END__