File Coverage

blib/lib/FlatFile.pm
Criterion Covered Total %
statement 235 245 95.9
branch 45 64 70.3
condition 12 18 66.6
subroutine 47 48 97.9
pod 9 14 64.2
total 348 389 89.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #
3             # Home filesystems are listed in @HOMEDEVS
4             # Minimal values are listed in @NO_QUOTA
5             #
6             # $ID: $
7             # $Author: mjd $
8             #
9              
10             package FlatFile;
11 16     16   429762 use Tie::File;
  16         400635  
  16         684  
12             $VERSION = "0.11";
13 16     16   201 use strict;
  16         29  
  16         674  
14 16     16   103 use Carp 'croak';
  16         38  
  16         2480  
15              
16             =head1 NAME
17              
18             FlatFile - Manipulate flat-file databases
19              
20             =head1 SYNOPSIS
21              
22             # Usage pattern A: direct use
23             use FlatFile;
24              
25             my $password = FlatFile->new(FILE => $filename,
26             FIELDS => [qw(username password uid gid gecos home shell)],
27             MODE => "+<", # "<" for read-only access
28             RECSEP => "\n", FIELDSEP => ":");
29              
30             my ($mjd) = $file->lookup(username => "mjd");
31             print "mjd: ", $mjd->uid, "\n";
32              
33             # Look up all records for which function returns true
34             sub is_chen { $_{gecos} =~ /\bChen$/ }
35             my @chens = $file->c_lookup(\&is_chen);
36             for (@chens) { $_->set_shell("/bin/false") }
37              
38             $mjd->delete; # delete MJD from file
39              
40             $password->flush; # write out changes to file
41              
42             # Usage pattern B: subclass
43             # PasswordFile.pm:
44             package PasswordFile;
45             use base FlatFile;
46             our @ISA = 'FlatFile';
47             our @FIELDS = qw(username password uid gid gecos home shell);
48             our $RECSEP = "\n";
49             our $FIELDSEP = ":";
50             our $MODE = "<";
51             our $FILE = "/etc/passwd";
52              
53             # Main program uses subclass:
54             package main;
55             use PasswordFile;
56             my $password = PasswordFile->new;
57              
58             ... the rest as above ...
59              
60             =head1 DESCRIPTION
61              
62             C is a class for manipulating flat-file (plain text)
63             databases. One first opens the database, obtaining a database object.
64             Queries may be perfomed on the database object, yielding record
65             objects, which can be queried to retrieve information from the
66             database. If the database is writable, the objects can be updated,
67             and the updates written back to the file.
68              
69             Subclasses of this module can be created to represent specific files,
70             such as the Unix password file or the Account Management C file.
71              
72             =cut
73              
74             my %default_default =
75             (FILE => undef,
76             TMPFILE => "", # overwritten later
77             MODE => "<",
78             FIELDS => undef,
79             RECSEP => "\n",
80             FIELDSEP => qr/\s+/,
81             FIELDSEPSTR => " ",
82             RECBASECLASS => "FlatFile::Rec",
83             RECCLASS => "", # Will be overwritten in ->new method
84             DEFAULTS => {},
85             );
86              
87             sub _classvars {
88 43     43   72 my $class = shift;
89 43 100       175 return {} if $class eq __PACKAGE__;
90 20         25 my %cv;
91 20         85 for my $k (keys %default_default) {
92 16     16   87 my $val = do { no strict 'refs'; $ {"$class\::$k"} };
  16         29  
  16         25166  
  200         167  
  200         190  
  200         416  
93 200 100       465 $cv{$k} = $val if defined $val;
94             }
95 20         64 \%cv;
96             }
97              
98             =head1 Methods
99              
100             =head2 C<< $db = FlatFile->new(FILE => $filename, FIELDS => [...], ...); >>
101              
102             The C method opens the database. At least two arguments are
103             required: the C argument that gives the path at which the data
104             can be accessed, and the C argument that names the fields, in
105             order.
106              
107             By default, the file will be opened for reading only. To override
108             this, supply a C argument whose value is a mode string like the
109             one given as the second argument to the Perl built-in C
110             function. For read-write access, you should probably use C<< MODE => "+<" >>. As of version 0.10, only C<< < >>, C<< +< >>, and C<< +> >> are supported.
111              
112             The file will be assumed to contain "records" that are divided into
113             "fields". By default, records are assumed to be terminated with a
114             newline character; to override this, use C<< RECSEP => $separator >>.
115             Fields are assumed to be separated by whitespace; to override, use
116             C<< FIELDSEP => $pattern >>. C<$pattern> may be a compiled regex
117             object or a literal string. If it is a pattern, you must also supply
118             an example string with C<> that will be used when writing
119             out records. For example, for the Unix password file, whose fields
120             are separated by colons, use:
121              
122             FIELDSEP => ":"
123              
124             but for a file whose fields are separated by one or more space
125             characters, use:
126              
127             FIELDSEP => qr/ +/, FIELDSEPSTR => " "
128              
129             The C argument tells the module to use two spaces between
130             fields when writing out new records.
131              
132             You may supply a
133              
134             DEFAULTS => { field => default_value, ... }
135              
136             argument that specifies default values for some or all of the fields. Fields for which no default value
137              
138             When changes are written to the disk, the module first copies the
139             modified data to a temporary file, then atomically replaces the old
140             file with the temporary file. To specify a temporary filename, use
141             C<< TMPFILE => $filename >>. Otherwise, it will default to the name
142             of the main file with C<".tmp"> appended.
143              
144             Record objects will be allocated in dynamically generated classes
145             named C,
146             C, and so on, which inherit from common
147             base class C. To override this choice of
148             class, supply a class name with C<< RECCLASS => $classname >>. You
149             may want your custom class to inherit from
150             C.
151              
152             =cut
153              
154             my $classid = "A";
155             sub new {
156 43     43 1 18279 my ($class, %opts) = @_;
157 43         194 my $self = {recno => 0};
158              
159 43         96 bless $self => $class;
160              
161             # acquire object properties from argument list (%opts)
162             # or from class defaults or default defaults, as appropriate.
163             # _default will detect missing required values
164             # and unknown key names
165 43         183 for my $source (\%opts, $class->_classvars) {
166 86         239 $self->_acquire_settings($source, check_keys => 1);
167             }
168              
169             # TODO: TESTS for this logic
170 43 100       300 if (exists $self->{FIELDSEP}) {
171 15 50       54 if (ref $self->{FIELDSEP}) {
172 0 0       0 defined($self->{FIELDSEPSTR})
173             or croak "FIELDSEPSTR required in conjunction with FIELDSEP";
174             } else {
175             # literal string; compile it to a pattern
176 15         35 my $str = $self->{FIELDSEP};
177 15         28 $self->{FIELDSEPSTR} = $str;
178 15         53 $self->{FIELDSEP} = "\Q$str";
179             }
180             }
181              
182 43         119 $self->_acquire_settings(\%default_default, mandatory => 1);
183              
184 43 100       263 $self->{RECCLASS} = join "::", $self->{RECBASECLASS}, $classid++
185             unless $self->{RECCLASS};
186              
187 43 50       232 $self->{TMPFILE} = $self->{FILE} . ".tmp"
188             unless exists $opts{TMPFILE};
189              
190 43         157 $self->_calculate_field_offsets;
191              
192 43         125 $self->_generate_record_class;
193              
194              
195 43 50       147 return $self->_open_file ? $self : ();
196             }
197              
198             sub _acquire_settings {
199 129     129   273 my ($self, $settings, %opt) = @_;
200 129         509 for my $k (keys %$settings) {
201 578 50 66     1680 if ($opt{check_keys} && not exists $default_default{$k}) {
202 0         0 croak "unknown key '$k'";
203             }
204 578 100 66     2079 if (! exists $self->{$k} && exists $settings->{$k}) {
205 415 50 66     1759 if ($opt{mandatory} && not defined $settings->{$k}) {
206 0         0 croak "Required key '$k' unspecified";
207             }
208 415         987 $self->{$k} = $settings->{$k};
209             }
210             }
211             }
212              
213 16     16   8912 use Fcntl qw(O_RDONLY O_RDWR O_TRUNC);
  16         2581  
  16         12033  
214             my %MODE_OK = ('<', O_RDONLY, '+<', O_RDWR,
215             '+>', O_RDWR|O_TRUNC);
216             sub _mode_flags {
217 43     43   64 my $self = shift;
218 43         116 $MODE_OK{$self->{MODE}};
219             }
220              
221             sub _writable {
222 83     83   118 my $self = shift;
223 83         423 $self->{MODE} ne "<"; # "<" is the only read-only mode
224             }
225              
226             sub _open_file {
227 43     43   78 my $self = shift;
228 43         103 my $file = $self->{FILE};
229 43         73 my $mode = $self->{MODE};
230 43         176 my $flags = $self->_mode_flags();
231 43 50       119 defined $flags or croak "Invalid mode '$mode'";
232              
233 43 50       318 tie my(@file), "Tie::File", $file, mode => $flags,
234             recsep => $self->{RECSEP}, autochomp => 1,
235             or return;
236 43         6991 $self->{file} = \@file;
237 43         271 return 1;
238             }
239              
240              
241             sub _calculate_field_offsets {
242 43     43   63 my $self = shift;
243 43         61 my @f = @{$self->{FIELDS}};
  43         266  
244 43         64 my %off;
245 43         116 for my $i (0 .. $#f) {
246 99 50       201 if (exists $off{$f[$i]}) {
247 0         0 croak "duplicate field name '$f[$i]'";
248             } else {
249 99         246 $off{$f[$i]} = $i;
250             }
251             }
252 43         116 $self->{OFF} = \%off;
253 43         94 return 1;
254             }
255              
256             sub _generate_record_class {
257 43     43   73 my ($self) = shift;
258 43         76 my $classname = $self->{RECCLASS};
259              
260             # create 'get' methods
261 43         101 for my $field (@{$self->{FIELDS}}) {
  43         91  
262 99         236 my $ff = $field;
263             my $code = sub {
264 273     273   13373 return $_[0]{data}{$ff};
265 99         308 };
266 16     16   89 no strict 'refs';
  16         23  
  16         2258  
267 99         118 *{"$classname\::$field"} = $code;
  99         584  
268 99         115 *{"$classname\::get_$field"} = $code;
  99         524  
269             }
270              
271             # create 'set' methods
272 43 100       148 if ($self->_writable) {
273 27         41 for my $field (@{$self->{FIELDS}}) {
  27         63  
274 54         73 my $ff = $field;
275             my $code = sub {
276 5     5   12 my ($rec, $val) = @_;
277 5         12 $rec->{data}{$ff} = $val;
278 5         30 $rec->db->_update($rec);
279 54         183 };
280 16     16   86 no strict 'refs';
  16         22  
  16         1139  
281 54         69 *{"$classname\::set_$field"} = $code;
  54         281  
282             }
283             }
284              
285 16     16   72 no strict 'refs';
  16         29  
  16         19863  
286 43         91 @{"$classname\::ISA"} = ($self->{RECBASECLASS});
  43         817  
287 43         86 *{"$classname\::FIELD"} = $self->{OFF}; # create %FIELD hash
  43         234  
288 43         69 *{"$classname\::FIELD"} = $self->{FIELDS}; # create @FIELD hash
  43         137  
289 43         63 *{"$classname\::DEFAULT"} = $self->{DEFAULTS}; # create %DEFAULT hash
  43         214  
290 43         154 return 1;
291             }
292              
293             =head2 C<< $db->lookup($field, $value) >>
294              
295             Returns an array of all records in the database for which the field
296             C<$field> contains the value C<$value>. For information about record
297             objects, see L<"Record objects"> below.
298              
299             Field contents are always compared stringwise. For numeric or other
300             comparisons, use C instead.
301              
302             The behavior in scalar context is undefined.
303              
304             =cut
305              
306             # Locate records for which field $f contains value $v
307             # return all such
308             # TODO: iterator interface?
309             sub lookup {
310 42     42 1 11046 my ($self, $f, $v) = @_;
311              
312             # If called as a class method, try to instantiate the database
313             # for the duration of a single query
314             # Note that since we don't give the new call the required FILE and FIELD
315             # arguments, this will only work if $self is actually the name of a subclass
316             # in which those things are predefined
317 42 100       161 $self = $self->new if not ref $self;
318              
319 42         53 my @result;
320 42 50       145 $self->rewind or croak "Couldn't rewind handle";
321 42         147 while (my $rec = $self->nextrec) {
322 220 100       486 if ($rec->$f eq $v) {
323 53 100       118 return $rec unless wantarray();
324 51         157 push @result, $rec;
325             }
326             }
327 40         166 return @result;
328             }
329              
330             =head2 C<< $db->c_lookup($predicate) >>
331              
332             Returns an array of all records in the database for which the
333             predicate function C<$predicate> returns true. For information about
334             record objects, see L<"Record objects"> below.
335              
336             The predicate function will be called repeatedly, once for each record
337             in the database.
338              
339             Each record will be passed to the predicate function as a hash, with
340             field names as the hash keys and record data as the hash values. The
341             global variable C<%_> will also be initialized to contain the current
342             record hash. For example, if C<$db> is the Unix password file, then
343             we can search for people named "Chen" like this:
344              
345             sub is_chen {
346             my %data = @_;
347             $data{gecos} =~ /\bChen$/;
348             }
349              
350             @chens = $db->c_lookup(\&is_chen);
351              
352             Or, using the C<%_> variable, like this:
353              
354             sub is_chen { $_{gecos} =~ /\bChen$/ }
355              
356             @chens = $db->c_lookup(\&is_chen);
357              
358             The behavior in scalar context is undefined.
359              
360             =cut
361              
362             # return all records for which some callback yields true
363             sub c_lookup {
364 13     13 1 1662 my ($self, $cb) = @_;
365 13         17 my @result;
366              
367             # If called as a class method, try to instantiate the database
368             # for the duration of a single query
369             # Note that since we don't give the new call the required FILE and FIELD
370             # arguments, this will only work if $self is actually the name of a subclass
371             # in which those things are predefined
372 13 100       44 $self = $self->new if not ref $self;
373              
374 13 50       31 $self->rewind or croak "Couldn't rewind handle";
375 13         62 while (my $rec = $self->nextrec) {
376 64         198 local %_ = $rec->as_hash;
377 64 100       218 push @result, $rec if $cb->(%_);
378             }
379 13         104 return @result;
380             }
381              
382             sub rewind {
383 55     55 0 100 my $self = shift;
384 55         113 $self->{recno} = 0;
385 55         177 return 1;
386             }
387              
388             =head2 C<< $db->rec_count >>
389              
390             Return a count of the number of records in the database.
391              
392             =cut
393              
394             sub rec_count {
395 2     2 1 257 my $self = shift;
396              
397 2         4 return scalar(@{$self->{file}});
  2         10  
398             }
399              
400             sub save_position {
401 6     6 0 9 my $self = shift;
402 6         40 FlatFile::Position->new(\($self->{recno}));
403             }
404              
405             =head2 C<< my $record = $db->nextrec >>
406              
407             Get the next record from the database and return a record object
408             representing it. Each call to C returns a different record.
409             Returns an undefined value when there are no more records left.
410              
411             For information about record objects, see L<"Record objects"> below.
412              
413             To rewind the database so that C will start at the beginning,
414             use the C method.
415              
416             The following code will scan all the records in the database:
417              
418             $db->rewind;
419             while (my $rec = $db->nextrec) {
420             ... do something with $rec...
421             }
422              
423             =cut
424              
425              
426              
427             sub nextrec {
428 340     340 1 1465 my $self = shift;
429 340         452 my $recno = $self->{recno};
430              
431 340         993 $recno++ while $self->{DELETE}{$recno};
432              
433             # Someone may have done an in-memory update of the record
434             # we just read. If so, discard the disk data and
435             # return the in-memory version of the record instead.
436 340 50       760 return $self->{UPDATE}{$recno}
437             if exists $self->{UPDATE}{$recno};
438              
439             # if it wasn't updated, the continue processing
440             # with the disk data
441 340         1286 my $line = $self->{file}[$recno];
442 340 100       33914 return unless defined $line;
443 287         1406 my @data = split $self->{FIELDSEP}, $line, -1;
444 287         481 $self->{recno} = $recno+1;
445 287         595 return $self->make_rec($recno, @data);
446             }
447              
448             sub make_rec {
449 293     293 0 667 my ($self, $recno, @data) = @_;
450 293         1127 return $self->{RECCLASS}->new($self, $recno, @data);
451             }
452              
453             =head2 C<< $db->append(@data) >>
454              
455             Create a new record and add it to the database. New records may not be
456             written out until the C<< ->flush >> method is called. The new
457             records will be added at the end of the file.
458              
459             C<@data> is a complete set of data values for the new record, in the
460             appropriate order. It is a fatal error to pass too many or too few
461             values.
462              
463             =cut
464              
465              
466             # TODO: fail unless ->_writable
467             sub append {
468 6     6 1 2575 my ($self, @data) = @_;
469 6         24 my $pos = $self->save_position;
470 6 50       10 push @{$self->{file}}, $self->make_rec(0, @data)->as_string or return;
  6         33  
471 6         2232 return 1;
472             }
473              
474             sub _update {
475 5     5   5 my ($self, $new_rec) = @_;
476 5         27 my $id = $new_rec->id;
477 5 50       17 return if $self->{DELETE}{$id};
478 5         77 $self->{UPDATE}{$id} = $new_rec->as_string;
479             }
480              
481             =head2 C<< $db->delete_rec($record) >>
482              
483             Delete a record from the database. C<$record> should be a record
484             object, returned from a previous call to C, C, or
485             some similar function. The record will be removed from the disk file
486             when the C method is called.
487              
488             Returns true on success, false on failure.
489              
490             =cut
491              
492             sub delete_rec {
493 6     6 1 30 my ($self, $rec) = @_;
494 6         40 my $id = $rec->id;
495 6         17 delete $self->{UPDATE}{$id};
496 6         22 $self->{DELETE}{$id} = 1;
497             }
498              
499             =head2 C<< $db->flush >>
500              
501             Adding new records, deleting and modifying old records is performed
502             in-memory only until C is called. At this point, the program
503             will copy the original data file, making all requested modifications,
504             and then atomically replace the original file with the new copy.
505              
506             Returns true on success, false if the update was not performed.
507              
508             C is also called automatically when the program exits.
509              
510             =cut
511              
512             # Old behavior was lost:
513             ### copy input file, writing out updated records
514             ### then atomically replace input file with updated copy
515             # Fix this XXX
516             sub flush {
517 40     40 1 2404 my $self = shift;
518              
519             # Quick return if there's nothing to do
520 40 100       101 return unless $self->_writable;
521 27         304 return if keys %{$self->{UPDATE}} == 0
  22         158  
522 27 100 100     41 && keys %{$self->{DELETE}} == 0;
523              
524 11         17 my $f = tied(@{$self->{file}});
  11         26  
525 11         50 $f->defer;
526              
527 11         121 for my $k (keys %{$self->{UPDATE}}) {
  11         35  
528 5         34 $self->{file}[$k] = $self->{UPDATE}{$k};
529             }
530 11         476 for my $k (sort {$b <=> $a} keys %{$self->{DELETE}}) {
  0         0  
  11         44  
531 6         10 splice @{$self->{file}}, $k, 1;
  6         33  
532             }
533              
534 11         2856 %{$self->{UPDATE}} = %{$self->{DELETE}} = ();
  11         27  
  11         28  
535 11         45 $f->flush;
536 11         1260 return 1;
537             }
538              
539             sub DESTROY {
540 30     30   6483 my $self = shift;
541 30         97 $self->flush('DESTROY');
542             }
543              
544 14     14 0 858 sub field_separator_string { $_[0]->{FIELDSEPSTR} }
545 12     12 0 34 sub record_separator { $_[0]{RECSEP} }
546              
547             =head2 C<< $db->has_field($fieldname) >>
548              
549             Returns true if the database contains a field with the specified name.
550              
551             =cut
552              
553             sub has_field {
554 0     0 1 0 my ($self, $field) = @_;
555 0         0 exists $self->{OFF}{$field};
556             }
557              
558             =head1 Record objects
559              
560             Certain methods return "record objects", each of which represents a
561             single record. The data can be accessed and the database can be
562             modified by operating on these record objects.
563              
564             Each object supports a series of accessor methods that are named after
565             the fields in the database. If the database contains a field "color",
566             for example, record objects resulting from queries on that database
567             will support a C method to retrieve the color value from a
568             record, and a synonymous method that does the exact same
569             thing. If the database was opened for writing, the record objects will
570             also support a C method to modify the color in a record.
571             The effects of the C methods will be propagated to the file
572             when the database is flushed.
573              
574             Other methods follow.
575              
576             =cut
577              
578             package FlatFile::Rec;
579 16     16   104 use Carp 'croak';
  16         25  
  16         1365  
580              
581             sub default {
582 10     10   12 my $self = shift;
583 10   33     71 my $class = ref($self) || $self;
584 10         18 my $field = shift;
585 16     16   237 no strict 'refs';
  16         221  
  16         2183  
586 10         19 my $d = \%{"$class\::DEFAULT"};
  10         33  
587 10 50       56 return wantarray ? (exists $d->{$field}, $d->{$field}) : $d->{$field};
588             }
589              
590             =head2 C<< $record->fields >>
591              
592             Returns a list of the fields in the object, in order.
593              
594             =cut
595              
596             sub fields {
597 304     304   340 my $self = shift;
598 304   66     1017 my $class = ref($self) || $self;
599 16     16   76 no strict 'refs';
  16         25  
  16         9188  
600 304         555 return @{"$class\::FIELD"};
  304         1376  
601             }
602              
603             sub new {
604 293     293   633 my ($class, $db, $id, @data) = @_;
605 293         429 my $self = {};
606 293         334 my %data;
607              
608 293         678 my @f = $class->fields;
609 293         1027 @data{@f} = @data;
610              
611             # set default values in data hash
612 293         444 for my $f (@f) {
613 886 100       2019 if (not defined $data{$f}) {
614 10         39 my ($has_default, $default_value) = $class->default($f);
615 10 50       28 if ($has_default) {
616 10         35 $data{$f} = $default_value;
617             } else {
618 0         0 my $msg = "required field '$f' missing from record";
619 0 0       0 $msg .= " $id" if $id;
620 0         0 croak $msg;
621             }
622             }
623             }
624              
625 293         627 $self->{data} = \%data;
626 293         415 $self->{db} = $db;
627 293         455 $self->{id} = $id;
628 293         1924 bless $self => $class;
629             }
630              
631             =head2 C<< $record->db >>
632              
633             Returns the database object from which the record was originally selected.
634             This example shows how one might modify a record and then write the
635             change to disk, even if the original database object was unavailable:
636              
637             $employee->set_salary(1.06 * $employee->salary);
638             $employee->db->flush;
639              
640             =cut
641              
642             sub db {
643 30     30   154 $_[0]{db};
644             }
645              
646             sub id {
647 11     11   34 $_[0]{id};
648             }
649              
650             =head2 C<< %hash = $record->as_hash >>
651              
652             Returns a hash containing all the data in the record. The keys in the
653             hash are the field names, and the corresponding values are the record
654             data.
655              
656             =cut
657              
658             sub as_hash {
659 64     64   66 my $self = shift;
660 64         55 return %{$self->{data}};
  64         300  
661             }
662              
663             =head2 C<< @data = $record->as_array >>
664              
665             Return the record data values only.
666              
667             =cut
668              
669             sub as_array {
670 11     11   21 my $self = shift;
671 11         25 my @f = $self->fields;
672 11         37 return @{$self->{data}}{@f};
  11         43  
673             }
674              
675             =head2 C<< $line = $record->as_string >>
676              
677             Return the record data in the same form that it appeared in the
678             original file. For example, if the record were selected from the Unix
679             password file, this might return the string
680             C<"root:x:0:0:Porpoise Super-User:/:/sbin/sh">.
681              
682             =cut
683              
684             sub as_string {
685 11     11   18 my $self = shift;
686 11         43 my $fsep = $self->db->field_separator_string;
687 11         26 my $rsep = $self->db->record_separator;
688 11         77 my @data = $self->as_array;
689 11         85 return join($fsep, @data) . $rsep;
690             }
691              
692             =head2 C<< $line = $record->delete >>
693              
694             Delete this record from its associated database. It will be removed
695             from the disk file the next time the database object is flushed.
696              
697             =cut
698              
699             # delete this record from its database
700             sub delete {
701 3     3   394 my $self = shift;
702 3         24 $self->db->delete_rec($self);
703             }
704              
705             package FlatFile::Position;
706              
707             sub new {
708 6     6   11 my ($class, $record_number_ref) = @_;
709 6         10 my $recno = $$record_number_ref;
710             my $self = sub {
711 6     6   34 $$record_number_ref = $recno;
712 6         24 };
713 6         22 bless $self => $class;
714             }
715              
716             sub DESTROY {
717 6     6   11 my $self = shift;
718 6         28 $self->();
719             }
720              
721             =head1 BUGS
722              
723             Various design defects; see TODO file
724              
725             This module is ALPHA-LEVEL software. Everything about it, including
726             the interface, might change in future versions.
727              
728             =head1 AUTHOR
729              
730             Mark Jason Dominus (mjd@plover.com)
731              
732             $Id: FlatFile.pm,v 1.4 2006/07/09 06:53:37 mjd Exp $
733             $Revision: 1.4 $
734              
735             =cut
736              
737             1;
738