File Coverage

blib/lib/FlatFile/DataStore/Preamble.pm
Criterion Covered Total %
statement 147 150 98.0
branch 72 74 97.3
condition 24 30 80.0
subroutine 27 27 100.0
pod 4 21 19.0
total 274 302 90.7


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package FlatFile::DataStore::Preamble;
3             #---------------------------------------------------------------------
4              
5             =head1 NAME
6              
7             FlatFile::DataStore::Preamble - Perl module that implements a flatfile
8             datastore preamble class.
9              
10             =head1 SYNOPSYS
11              
12             use FlatFile::DataStore::Preamble;
13              
14             my $preamble = FlatFile::DataStore::Preamble->new( {
15             datastore => $ds, # FlatFile::DataStore object
16             indicator => $indicator, # single-character crud flag
17             transind => $transind, # single-character crud flag
18             date => $date, # pre-formatted date
19             transnum => $transint, # transaction number (integer)
20             keynum => $keynum, # record sequence number (integer)
21             reclen => $reclen, # record length (integer)
22             thisfnum => $fnum, # file number (in base format)
23             thisseek => $datapos, # seek position (integer)
24             prevfnum => $prevfnum, # ditto these ...
25             prevseek => $prevseek,
26             nextfnum => $nextfnum,
27             nextseek => $nextseek,
28             user => $user_data, # pre-formatted user-defined data
29             } );
30              
31             my $string = $preamble->string();
32              
33             my $clone = FlatFile::DataStore::Preamble->new( {
34             datastore => $ds,
35             string => $string
36             } );
37              
38             =head1 DESCRIPTION
39              
40             FlatFile::DataStore::Preamble - Perl module that implements a flatfile
41             datastore preamble class. This class defines objects used by
42             FlatFile::DataStore::Record and FlatFile::DataStore. You will
43             probably not ever call new() yourself, but you might call some of the
44             accessors either directly or via a FF::DS::Record object;
45              
46             A "preamble" is a string of fixed-length fields that precedes every
47             record in a FlatFile::DataStore data file. In addition, this string
48             constitutes the entry in the datastore key file for each current
49             record.
50              
51             =head1 VERSION
52              
53             FlatFile::DataStore::Preamble version 1.03
54              
55             =cut
56              
57             our $VERSION = '1.03';
58              
59 23     23   49564 use 5.008003;
  23         77  
  23         6385  
60 23     23   124 use strict;
  23         38  
  23         953  
61 23     23   124 use warnings;
  23         37  
  23         680  
62              
63 23     23   120 use Carp;
  23         49  
  23         1538  
64              
65 23     23   24647 use Math::Int2Base qw( base_chars int2base base2int );
  23         22787  
  23         2114  
66              
67 23     23   28350 use Data::Omap qw( :ALL );
  23         101432  
  23         87873  
68              
69             my %Generated = qw(
70             string 1
71             );
72              
73             my %Attrs = ( %Generated, qw(
74             indicator 1
75             transind 1
76             date 1
77             transnum 1
78             keynum 1
79             reclen 1
80             thisfnum 1
81             thisseek 1
82             prevfnum 1
83             prevseek 1
84             nextfnum 1
85             nextseek 1
86             user 1
87             ) );
88              
89             my $Ascii_chars = qr/^[ -~]+$/;
90              
91             #---------------------------------------------------------------------
92              
93             =head1 CLASS METHODS
94              
95             =head2 FlatFile::DataStore::Preamble->new( $parms )
96              
97             Constructs a new FlatFile::DataStore::Preamble object.
98              
99             The parm C<$parms> is a hash reference containing key/value pairs to
100             populate the preamble string. If there is a C<< $parms->{'string'} >>
101             value, it will be parsed into fields and the resulting key/value pairs
102             will replace the C<$parms> hash reference.
103              
104             =cut
105              
106             sub new {
107 187     187 1 33063 my( $class, $parms ) = @_;
108              
109 187         527 my $self = bless {}, $class;
110              
111 187 50       876 $self->init( $parms ) if $parms;
112 152         554 return $self;
113             }
114              
115             #---------------------------------------------------------------------
116             # init(), called by new() to parse the parms
117              
118             sub init {
119 187     187 0 292 my( $self, $parms ) = @_;
120              
121 187   66     883 my $datastore = $parms->{'datastore'} || croak qq/Missing: datastore/;
122              
123 186 100       635 if( my $string = $parms->{'string'} ) {
124 66         249 $parms = $datastore->burst_preamble( $string ); # replace parms
125             }
126              
127 186         646 my $crud = $datastore->crud();
128 186         701 $self->crud( $crud );
129              
130             # single chars for character classes:
131 186         545 my $create = quotemeta $crud->{'create'};
132 186         567 my $update = quotemeta $crud->{'update'};
133 186         337 my $delete = quotemeta $crud->{'delete'};
134 186         326 my $oldupd = quotemeta $crud->{'oldupd'};
135 186         319 my $olddel = quotemeta $crud->{'olddel'};
136              
137             # need these in validations below
138 186   66     638 my $indicator = $parms->{'indicator'} || croak qq/Missing: indicator/;
139 185   66     630 my $transind = $parms->{'transind'} || croak qq/Missing: transind/;
140 184         636 $self->indicator( $indicator );
141 184         447 $self->transind( $transind );
142              
143 184         303 my $string = '';
144 184         649 for my $href ( $datastore->specs() ) { # each field is href of aref
145 2126         5071 my( $field, $aref ) = %$href;
146 2126         3891 my( $pos, $len, $parm ) = @$aref;
147 2126         3439 my $value = $parms->{ $field };
148              
149 2126         3246 for( $field ) {
150              
151 2126 100       10397 if( /indicator|transind/ ) {
    100          
    100          
    100          
152              
153 366         2952 my $regx = qr/^[\Q$parm\E]{1,$len}$/;
154 366 100       2471 croak qq/Invalid value, $value, for: $_/ unless $value =~ $regx;
155              
156             # did these above
157             # croak qq/Missing: $_/ unless defined $value;
158             # $self->$_( $value );
159              
160 362         1567 $string .= $value;
161             }
162             elsif( /date/ ) {
163              
164 180 100       655 croak qq/Missing: $_/ unless defined $value;
165 179 100       634 croak qq/Invalid value, $value, for: $_/ unless length $value == $len;
166              
167 178         532 $self->$_( then( $value, $parm ) );
168 178         810 $string .= $value;
169             }
170             elsif( /user/ ) {
171              
172 156 100       560 unless( defined $value ) {
173 55         240 $value = $datastore->userdata;
174 55 100       451 croak qq/Missing: $_/ unless defined $value;
175             }
176              
177 155         546 my $try = sprintf "%-${len}s", $value; # pads with blanks
178 155 100       700 croak qq/Value, $try, too long for: $_/ if length $try > $len;
179              
180 154         1093 my $regx = qr/^[$parm]+ *$/; # $parm chars already escaped as needed
181 154 100       1214 croak qq/Invalid value, $value, for: $_/ unless $try =~ $regx;
182              
183 153         510 $self->$_( $value );
184 153         773 $string .= $try;
185             }
186             elsif( not defined $value ) {
187              
188 463 100 100     6474 if( ( /transnum|keynum|reclen|thisfnum|thisseek/ ) ||
      66        
      100        
      66        
189             ( /prevfnum|prevseek/ and $transind =~ /[$update$delete]/ ) ||
190             ( /nextfnum|nextseek/ and $indicator =~ /[$oldupd$olddel]/ ) ){
191 9         1300 croak qq/Missing: $_/;
192             }
193              
194 454         1716 $string .= '-' x $len; # string of '-' for null
195             }
196             else {
197              
198 961 100 100     5700 if( ( /prevfnum|prevseek/ and $indicator =~ /[$create]/ ) ||
      100        
      66        
199             ( /nextfnum|nextseek/ and $indicator =~ /[$update$delete]/ ) ){
200 4         528 croak qq/For indicator, $indicator, you may not set: $_/;
201             }
202              
203 957 100       4251 my $try = sprintf "%0${len}s", /fnum/? $value: int2base( $value, $parm );
204 957 100       14597 croak qq/Value, $try, too long for: $_/ if length $try > $len;
205              
206 948 100       4214 $self->$_( /fnum/? $try: 0+$value );
207 948         3286 $string .= $try;
208             }
209             }
210             }
211              
212 153 100       740 croak qq/Something is wrong with preamble: $string/
213             unless $string =~ $datastore->regx();
214            
215 152         520 $self->string( $string );
216              
217 152         466 $self; # returned
218             }
219              
220             #---------------------------------------------------------------------
221              
222             =head1 OBJECT METHODS: ACCESSORS
223              
224             The following methods set and return their respective attribute values
225             if C<$value> is given. Otherwise, they just return the value.
226              
227             $preamble->string( $value ); # full preamble string
228             $preamble->indicator( $value ); # single-character crud indicator
229             $preamble->transind( $value ); # single-character crud indicator
230             $preamble->date( $value ); # date as YYYY-MM-DD (hh:mm:ss)
231             $preamble->transnum( $value ); # transaction number (integer)
232             $preamble->keynum( $value ); # record sequence number (integer)
233             $preamble->reclen( $value ); # record length (integer)
234             $preamble->thisfnum( $value ); # file number (in base format)
235             $preamble->thisseek( $value ); # seek position (integer)
236             $preamble->prevfnum( $value ); # ditto these ...
237             $preamble->prevseek( $value ); #
238             $preamble->nextfnum( $value ); #
239             $preamble->nextseek( $value ); #
240             $preamble->user( $value ); # pre-formatted user-defined data
241             $preamble->crud( $value ); # hash ref of all crud indicators
242              
243             Note: the class code uses these accessors to set values in the object
244             as it is assembling the preamble string in new(). Unless you have a
245             really good reason, you should not set these values yourself (outside
246             of a call to new()). For example: setting the date with date() will
247             I change the date in the C attribute.
248              
249             In other words, even though these are read/write accessors, you should
250             only use them for reading.
251              
252             =cut
253              
254 330 100   330 0 1964 sub string {for($_[0]->{string} ){$_=$_[1]if@_>1;return$_}}
  330         840  
  330         747  
255 230 100   230 0 650 sub indicator {for($_[0]->{indicator} ){$_=$_[1]if@_>1;return$_}}
  230         681  
  230         761  
256 187 100   187 0 463 sub transind {for($_[0]->{transind} ){$_=$_[1]if@_>1;return$_}}
  187         613  
  187         278  
257 206 100   206 0 708 sub crud {for($_[0]->{crud} ){$_=$_[1]if@_>1;return$_}}
  206         648  
  206         437  
258 181 100   181 0 834 sub date {for($_[0]->{date} ){$_=$_[1]if@_>1;return$_}}
  181         619  
  181         300  
259 194 100   194 0 524 sub user {for($_[0]->{user} ){$_=$_[1]if@_>1;return$_}}
  194         615  
  194         447  
260              
261 229 100   229 0 721 sub keynum {for($_[0]->{keynum} ){$_=0+$_[1]if@_>1;return$_}}
  229         636  
  229         831  
262 237 100   237 0 652 sub reclen {for($_[0]->{reclen} ){$_=0+$_[1]if@_>1;return$_}}
  237         761  
  237         432  
263 179 100   179 0 553 sub transnum {for($_[0]->{transnum} ){$_=0+$_[1]if@_>1;return$_}}
  179         591  
  179         400  
264 197 100   197 0 814 sub thisfnum {for($_[0]->{thisfnum} ){$_= $_[1]if@_>1;return$_}}
  197         616  
  197         388  
265 195 100   195 0 549 sub thisseek {for($_[0]->{thisseek} ){$_=0+$_[1]if@_>1;return$_}}
  195         574  
  195         342  
266 42 100   42 0 130 sub prevfnum {for($_[0]->{prevfnum} ){$_= $_[1]if@_>1;return$_}}
  42         164  
  42         77  
267 40 100   40 0 137 sub prevseek {for($_[0]->{prevseek} ){$_=0+$_[1]if@_>1;return$_}}
  40         135  
  40         91  
268 9 100   9 0 30 sub nextfnum {for($_[0]->{nextfnum} ){$_= $_[1]if@_>1;return$_}}
  9         52  
  9         20  
269 7 100   7 0 29 sub nextseek {for($_[0]->{nextseek} ){$_=0+$_[1]if@_>1;return$_}}
  7         110  
  7         19  
270              
271             #---------------------------------------------------------------------
272              
273             =head2 Convenience methods
274              
275             =head3 is_created(), is_updated(), is_deleted();
276              
277             These methods return true if the indicator matches the value implied by
278             the method name, e.g.,
279              
280             print "Deleted!" if $preamble->is_deleted();
281              
282             =cut
283              
284             sub is_created {
285 6     6 1 835 my $self = shift;
286 6         19 $self->indicator eq $self->crud->{'create'};
287             }
288             sub is_updated {
289 4     4 1 7 my $self = shift;
290 4         16 $self->indicator eq $self->crud->{'update'};
291             }
292             sub is_deleted {
293 9     9 1 24 my $self = shift;
294 9         32 $self->indicator eq $self->crud->{'delete'};
295             }
296              
297             #---------------------------------------------------------------------
298             # then(), translates stored date to YYYY-MM-DD hh:mm:ss
299             # Takes a date and a format and returns the date as
300             # yyyy-mm-dd hh:mm:ss
301             # If the format contains 'yyyy' it is assumed to have decimal
302             # values for month, day, year, hours, minutes, seconds.
303             # Otherwise, it is assumed to have base62 values for them.
304             #
305             # Private method.
306              
307             sub then {
308 178     178 0 318 my( $date, $format ) = @_;
309 178         253 my( $yr, $mo, $da, $hr, $mn, $sc );
310 178         269 my $tm = '';
311 178         238 my $ret;
312 178         320 for( $format ) {
313 178 100       565 if( /yyyy/ ) { # decimal
314 59         162 $yr = substr $date, index( $format, 'yyyy' ), 4;
315 59         118 $mo = substr $date, index( $format, 'mm' ), 2;
316 59         106 $da = substr $date, index( $format, 'dd' ), 2;
317 59 50       282 if( (my $pos = index( $format, 'tttttt' )) > -1 ) {
318 0         0 $tm = substr $date, $pos, 2;
319 0         0 ( $hr, $mn, $sc ) = $tm =~ /(..)(..)(..)/;
320 0         0 $tm = " $hr:$mn:$sc";
321             }
322             }
323             else { # base62
324 119         357 $yr = substr $date, index( $format, 'yy' ), 2;
325 119         240 $mo = substr $date, index( $format, 'm' ), 1;
326 119         245 $da = substr $date, index( $format, 'd' ), 1;
327              
328 119         478 $yr = sprintf "%04d", base2int( $yr, 62 );
329 119         3562 $mo = sprintf "%02d", base2int( $mo, 62 );
330 119         2265 $da = sprintf "%02d", base2int( $da, 62 );
331              
332 119 100       2747 if( (my $pos = index( $format, 'ttt' )) > -1 ) {
333 118         227 $tm = substr $date, $pos, 3;
334 118         643 ( $hr, $mn, $sc ) = $tm =~ /(.)(.)(.)/;
335 118         407 $hr = sprintf "%02d", base2int( $hr, 62 );
336 118         2236 $mn = sprintf "%02d", base2int( $mn, 62 );
337 118         2113 $sc = sprintf "%02d", base2int( $sc, 62 );
338 118         2314 $tm = " $hr:$mn:$sc";
339             }
340             }
341             }
342 178         1008 return "$yr-$mo-$da$tm";
343             }
344              
345             __END__