File Coverage

lib/Tie/File/FixedRecLen.pm
Criterion Covered Total %
statement 103 109 94.5
branch 41 58 70.6
condition 2 6 33.3
subroutine 15 16 93.7
pod n/a
total 161 189 85.1


line stmt bran cond sub pod time code
1             package Tie::File::FixedRecLen;
2             {
3             $Tie::File::FixedRecLen::VERSION = '2.112531';
4             }
5              
6 37     37   148365 use strict;
  37         75  
  37         1813  
7 37     37   190 use warnings FATAL => 'all';
  37         65  
  37         1881  
8              
9 37     37   1191 use 5.008;
  37         122  
  37         1499  
10              
11 37     37   193 use base 'Tie::File';
  37         57  
  37         54741  
12             # v.0.97 says: "This version promises absolutely nothing about the internals,
13             # which may change without notice. A future version of the module will have a
14             # well-defined and stable subclassing API."
15              
16             my $DEBUG = $ENV{FIXEDRECLEN_DEBUG} || 0;
17             my @good_opts = qw(record_length pad_dir pad_char);
18              
19             # ===========================================================================
20              
21             sub TIEARRAY {
22 2976     2976   4594843 my ($class, $file, %opts) = @_;
23 2976         6016 my %tmp_opts = (); # ugh, Tie::File is broken for subclassing
24              
25 2976         6541 for (@good_opts) {
26 8928 100       35908 $tmp_opts{$_} = delete $opts{$_} if exists $opts{$_};
27             }
28 2976         24801 my $self = $class->SUPER::TIEARRAY(
29             $file, %opts, autodefer => 0, memory => 0,
30             );
31 2975         419305 for (keys %tmp_opts) {
32 5948         15170 $self->{$_} = $tmp_opts{$_};
33             }
34              
35 2975 50 33     42102 die "Useless use of Tie::File::FixedRecLen without a record_length\n"
      33        
36             if !exists $self->{record_length}
37             or !defined $self->{record_length}
38             or $self->{record_length} !~ m/^\d+$/
39             or $self->{record_length} == 0;
40              
41 2975 50       9999 $self->{pad_dir} = 'left' if !defined $self->{pad_dir};
42 2975 100       6710 $self->{pad_char} = ' ' if !defined $self->{pad_char};
43 2975         14640 return $self;
44             }
45              
46             # ===========================================================================
47             # utility methods
48              
49             sub _add_padding {
50 548     548   849 my ($self, $rec) = @_;
51 548 100       1096 $rec = '' if !defined $rec;
52 548 50       961 print STDERR "_add_padding(1) :$rec:\n" if $DEBUG;
53              
54             # deferred records may have already been terminated with recsep
55 548         643 my $has_recsep = 0;
56 548         1835 while (index($rec, $self->{recsep}, (length($rec) - $self->{recseplen})) != -1) {
57 8         17 substr($rec, - $self->{recseplen}) = ''; # temporary chomp
58 8         26 $has_recsep = 1;
59             }
60              
61 548         668 my $rl = length($rec);
62 548 50       1176 die "Record '$rec' ($rl) exceeds record length ($self->{record_length})\n"
63             if $rl > $self->{record_length};
64              
65 548 50       1146 if (length($rec) != $self->{record_length}) {
66 548         1120 my $pad
67             = $self->{pad_char} x ($self->{record_length} - length($rec));
68              
69 548 50       1054 if ($self->{pad_dir} eq 'right') {
70 0         0 $rec .= $pad;
71             }
72             else {
73 548         1124 $rec = $pad . $rec;
74             }
75             }
76              
77 548 100       1120 $rec .= $self->{recsep} if $has_recsep;
78 548 50       932 print STDERR "_add_padding(2) :$rec:\n" if $DEBUG;
79              
80 548         1296 return $rec;
81             }
82              
83             sub _del_padding {
84 656     656   930 my ($self, $rec) = @_;
85 656 50       1504 return undef if !defined $rec;
86 656 50       1182 print STDERR "_del_padding(1) :$rec:\n" if $DEBUG;
87              
88 656         737 my $has_recsep = 0;
89 656         2121 while (index($rec, $self->{recsep}, (length($rec) - $self->{recseplen})) != -1) {
90 428         620 substr($rec, - $self->{recseplen}) = ''; # temporary chomp
91 428         1469 $has_recsep = 1;
92             }
93              
94 656         764 my $rl = length($rec);
95 656 50       1291 die "Record '$rec' ($rl) is not set length ($self->{record_length})\n"
96             if $rl != $self->{record_length};
97              
98 656 50       1509 if ($self->{pad_dir} eq 'right') {
99 0         0 while (index($rec,$self->{pad_char},(length($rec) - 1))
100             == (length($rec) - 1)) {
101 0         0 substr($rec, -1, 1) = '';
102             }
103             }
104             else {
105 656         2944 while (index($rec,$self->{pad_char},0) == 0) {
106 6344         13371 substr($rec, 0, 1) = '';
107             }
108             }
109              
110 656 100       1316 $rec .= $self->{recsep} if $has_recsep;
111 656 50       1114 print STDERR "_del_padding(2) :$rec:\n" if $DEBUG;
112              
113 656         3263 return $rec;
114             }
115              
116             # ===========================================================================
117              
118             sub FETCH {
119 383     383   29417 my ($self, $n) = @_;
120              
121 383         1080 my $rec = $self->SUPER::FETCH($n);
122 383 100       22521 return undef if !defined $rec;
123              
124 378         751 return $self->_del_padding($rec);
125             }
126              
127             # SUPER->STORE will append record separator for us
128             sub STORE {
129 222     222   31670 my ($self, $n, $rec) = @_;
130 222         506 $rec = $self->_add_padding($rec);
131              
132 222         769 return $self->SUPER::STORE($n, $rec);
133             }
134              
135             # need to override this as it is called from STORESIZE with $self->{recsep}
136             # sadly it could be called from STORE as well but that can't be helped.
137             sub _store_deferred {
138 0     0   0 my ($self, $n, $rec) = @_;
139 0         0 $rec = $self->_add_padding($rec);
140              
141 0         0 return $self->SUPER::_store_deferred($n, $rec);
142             }
143              
144             sub SPLICE {
145 286     286   44102 my ($self, $pos, $nrecs, @data) = @_;
146              
147 286         394 map {$_ = $self->_add_padding($_)} @data;
  326         669  
148 286         976 my @result = $self->SUPER::SPLICE($pos, $nrecs, @data);
149              
150             # Yes, the return value of 'splice' *is* actually this complicated
151             wantarray
152 282 100       50256 ? map {$self->_del_padding($_)} @result
  198 100       394  
153             : @result ? $self->_del_padding($result[-1]) : undef;
154             }
155              
156             # to work around _extend_file_to being called in many circumstances
157             sub STORESIZE {
158 12     12   497 my ($self, $len) = @_;
159              
160 12 100       35 $len += ($len > $self->FETCHSIZE ? 1 : 0);
161 12 50       151 print STDERR "STORESIZE $len\n" if $DEBUG;
162 12         54 return $self->SUPER::STORESIZE($len);
163             }
164              
165             sub EXTEND {
166 26     26   10820 my ($self, $len) = @_;
167              
168 26 100       134 $len += ($len > $self->FETCHSIZE ? 1 : 0);
169 26 50       413 print STDERR "EXTEND $len\n" if $DEBUG;
170 26         117 return $self->SUPER::EXTEND($len);
171             }
172              
173             # ===========================================================================
174              
175             # okay, according to the Tie::File code comments, the offset table has one
176             # more entry than the total number of records. I assume this means the last
177             # offset table entry is the seek position of the record which is next to be
178             # written in the file (or put another way, the size of the file).
179              
180             sub _fill_offsets {
181 826     826   1396 my ($self) = @_;
182              
183 826         1113 my $fh = $self->{fh};
184 826         6662 my $size = -s $fh;
185 826         1130 my $off = $self->{offsets};
186 826         1211 my $totreclen = $self->{record_length} + $self->{recseplen};
187              
188             # for development
189             # seek $fh,0,0;
190             # my $lines = join '',<$fh>;
191             # print STDERR "OFFSETS(1) content is :$lines:\n" if $DEBUG;
192              
193 826 100       1857 die "File ($size) does not appear to be using ".
194             "fixed length records ($totreclen)\n" if ($size % $totreclen) != 0;
195              
196 824         2048 @$off = map {$_ * $totreclen} (0 .. ($size / $totreclen));
  4864         7510  
197 824 50       1949 print STDERR "OFFSETS(2) offsets :@$off:\n" if $DEBUG;
198              
199 824         2203 $self->_seek(-1); # position after the end of the last record
200 824         9353 $self->{eof} = 1;
201              
202 824         904 return $#{$off};
  824         1646  
203             }
204              
205             # populate the offsets table up to the beginning of record $n
206             # return the offset of record $n
207             sub _fill_offsets_to {
208 667     667   31370 my ($self, $n) = @_;
209              
210 667         1205 $self->_fill_offsets;
211              
212 667         1033 my $off = $self->{offsets};
213 667 100       713 return undef if $n > $#{$off};
  667         1433  
214 646         1523 return $off->[$n];
215             }
216              
217             # We have read to the end of the file and have the offsets table
218             # entirely populated. Now we need to write a new record beyond
219             # the end of the file. We prepare for this by writing
220             # empty records into the file up to the position we want
221             #
222             # assumes that the offsets table already contains the offset of record $n,
223             # if it exists, and extends to the end of the file if not.
224             sub _extend_file_to {
225 70     70   2831 my ($self, $n) = @_;
226              
227 70         204 my $record = $self->{pad_char} x $self->{record_length};
228 70         181 my $recs = $self->_fill_offsets;
229             # a bit safer to just refresh this now
230             # and also positions us at the end of the file
231             # and gives us a starting counter for writing records
232              
233 70 50       176 print STDERR "_extend_file_to $n (-2)...\n" if $DEBUG;
234 70         169 for ($recs .. ($n - 2)) {
235 140 50       8414 print STDERR
236             "_extend_file_to $_ writing record '$record$self->{recsep}'\n"
237             if $DEBUG;
238 140         543 $self->_write_record($record . $self->{recsep});
239             }
240 70         886 $self->_fill_offsets; # refresh offsets table
241              
242 70         1147 return undef; # not sure what Tie::File's version wants to return
243             }
244              
245             1;
246              
247             # ABSTRACT: Fixed Length Record support for Tie:File
248              
249              
250             __END__