File Coverage

blib/lib/Acme/InputRecordSeparatorIsRegexp.pm
Criterion Covered Total %
statement 229 301 76.0
branch 73 114 64.0
condition 28 55 50.9
subroutine 32 36 88.8
pod 5 6 83.3
total 367 512 71.6


line stmt bran cond sub pod time code
1             package Acme::InputRecordSeparatorIsRegexp;
2              
3 18     18   1070399 use 5.006;
  18         182  
4 18     18   89 use strict;
  18         33  
  18         508  
5 18     18   96 use warnings FATAL => 'all';
  18         39  
  18         676  
6 18     18   7809 use Symbol;
  18         12234  
  18         985  
7 18     18   116 use Carp;
  18         31  
  18         889  
8 18     18   8515 use IO::Handle;
  18         76995  
  18         1516  
9             require Exporter;
10             our @ISA = 'Exporter';
11             our @EXPORT_OK = ('open','autochomp','input_record_separator','binmode');
12             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
13              
14             BEGIN {
15 18     18   120 no strict 'refs';
  18         38  
  18         641  
16 18     18   49 *{ 'Acme::IRSRegexp' . "::" } = \*{ __PACKAGE__ . "::" };
  18         6760  
  18         50  
17             }
18              
19             our $VERSION = '0.07';
20              
21             sub TIEHANDLE {
22 28     28   56835 my ($pkg, @opts) = @_;
23 28         110 my $handle;
24 28 100       100 if (@opts % 2) {
25 2         8 $handle = Symbol::gensym;
26             } else {
27 26         49 my $fh = *{shift @opts};
  26         91  
28             # will fail if open for $fh failed, but that's not important
29 26         58 eval { CORE::open $handle, '<&+', $fh };
  26         964  
30             }
31 28         125 my $rs = shift @opts;
32 28         67 my %opts = @opts;
33 28   50     319 $opts{maxrecsize} ||= ($opts{bufsize} || 16384) / 4;
      33        
34 28   33     164 $opts{bufsize} ||= $opts{maxrecsize} * 4;
35 28         234 my $self = bless {
36             %opts,
37             handle => $handle,
38             rs => $rs,
39             records => [],
40             buffer => ''
41             }, $pkg;
42 28         135 $self->_compile_rs;
43 28         113 return $self;
44             }
45              
46             # We abuse the PerlIO layers syntax to attach
47             # a regexp specification to a filehandle. This
48             # function extracts an ':irs(REGEXP)' layer from
49             # a string.
50             sub _extract_irs {
51 15     15   44 my ($mode) = @_;
52            
53 15         33 my $irs = "";
54 15         34 my $p0 = index($mode,":irs(");
55 15         32 my $p1 = $p0 + 5;
56 15         28 my $nest = 1;
57 15         49 while ($nest) {
58 145         198 my $c = eval { substr($mode,$p1++,1) };
  145         240  
59 145 50 33     434 if ($@ || !defined($c)) {
60 0         0 carp "Argument list not closed for PerlIO layer \"$irs\"";
61 0         0 return;
62             }
63 145 100       257 if ($c eq "\\") {
64 28         47 $c .= substr($mode,$p1++,1);
65             }
66 145 50       245 if ($c eq "(") { $nest++ }
  0         0  
67 145 100       246 if ($c eq ")") { $nest-- }
  15         26  
68 145 100       251 if ($nest) { $irs .= $c; }
  130         217  
69             }
70 15         46 substr($mode,$p0,length($irs)+6, "");
71 15         30 $_[0] = $mode;
72 15         36 return $irs;
73             }
74              
75             sub open (*;$@) {
76 18     18   158 no strict 'refs'; # or else bareword file handles will break
  18         43  
  18         37169  
77 23     23 1 88736 my (undef,$mode,$expr,@list) = @_;
78 23 50       171 if (!defined $_[0]) {
79 23         78 $_[0] = Symbol::gensym;
80             }
81 23         320 my $glob = $_[0];
82 23 0 33     71 if (!ref($glob) && $glob !~ /::/) {
83 0   0     0 $glob = join("::",caller(0) || "", $glob);
84             }
85              
86 23 100 66     143 if ($mode && index($mode,":irs(") >= 0) {
87 8         30 my $irs = _extract_irs($mode);
88 8 50   1   283 my $z = @list ? CORE::open *$glob, $mode, $expr, @list
  1         7  
  1         2  
  1         6  
89             : CORE::open *$glob, $mode, $expr;
90 8         753 tie *$glob, __PACKAGE__, *$glob, $irs;
91 8         34 return $z;
92             }
93 15 50       54 if (@list) {
    50          
    0          
94 0         0 return CORE::open(*$glob,$mode,$expr,@list);
95             } elsif ($expr) {
96 15         902 return CORE::open(*$glob,$mode,$expr);
97             } elsif ($mode) {
98 0         0 return CORE::open(*$glob,$mode);
99             } else {
100 0         0 return CORE::open(*$glob);
101             }
102             }
103              
104             sub binmode (*;$) {
105 7     7 1 53050 my ($glob,$mode) = @_;
106 7   50     109 $mode ||= ":raw";
107              
108 7 50       38 if (index($mode,":irs(") >= 0) {
109 7         23 my $irs = _extract_irs($mode);
110 7         29 input_record_separator($glob,$irs);
111 7 50       31 return 1 unless $mode;
112             }
113 0         0 return CORE::binmode($glob,$mode);
114             }
115              
116             sub _compile_rs {
117 80     80   130 my $self = shift;
118 80         236 my $rs = $self->{rs};
119              
120 80         144 my $q = eval { my @q = split /(?<=${rs})/,""; 1 };
  80         1424  
  33         82  
121 80 100       231 if ($q) {
122 33         277 $self->{rsc} = qr/(?<=${rs})/s;
123 33 50       103 if ($rs =~ /\?\^\w*m/) {
124 0         0 $self->{rsc} = qr/(?<=${rs})/ms;
125             }
126 33         87 $self->{can_use_lookbehind} = 1;
127             } else {
128 47         575 $self->{rsc} = qr/(.*?(?:${rs}))/s;
129 47 50       196 if ($rs =~ /\?\^\w*m/) {
130 0         0 $self->{rsc} = qr/(.*?(?:${rs}))/ms;
131             }
132 47         105 $self->{can_use_lookbehind} = 0;
133             }
134 80         137 return;
135             }
136              
137             sub READLINE {
138 11181     11181   1480688 my $self = shift;
139 11181 100       25364 if (wantarray) {
140 4         19 local $/ = undef;
141 4         2639 $self->{buffer} .= readline($self->{handle});
142 4         19 push @{$self->{records}}, $self->_split;
  4         23  
143 4         283 $self->{buffer} = "";
144 4         10 my @rec = splice @{$self->{records}};
  4         386  
145 4 50 33     86 if (@rec && $self->{autochomp}) {
146 0         0 $self->chomp( @rec );
147             }
148 4         2121 return @rec;
149             }
150             # want scalar
151 11177 100       15987 if (!@{$self->{records}}) {
  11177         26096  
152 5684         11219 $self->_populate_buffer;
153             }
154 11177         16524 my $rec = shift @{$self->{records}};
  11177         21249  
155 11177 100 100     40070 if (defined($rec) && $self->{autochomp}) {
156 82         189 $self->chomp( $rec );
157             }
158 11177         29874 return $rec;
159             }
160              
161             sub _populate_buffer {
162 5685     5685   8349 my $self = shift;
163 5685         8853 my $handle = $self->{handle};
164 5685 100 66     48867 return if !$handle || eof($handle);
165            
166 5668         11614 my @rec;
167             {
168 5668         8044 my $buffer = '';
  5672         9142  
169 5672         42639 my $n = read $handle, $buffer, $self->{bufsize};
170 5672         33568 $self->{buffer} .= $buffer;
171 5672         13282 @rec = $self->_split;
172 5672 100 100     54693 redo if !eof($handle) && @rec == 1;
173             }
174 5668         11494 push @{$self->{records}}, @rec;
  5668         109703  
175 5668         12366 $self->{buffer} = '';
176 5668 100       12516 if (eof($handle)) {
177 950         4636 return;
178             }
179              
180 4718 50       6915 if (@{$self->{records}} > 1) {
  4718         10909  
181 4718         6553 $self->{buffer} = pop @{$self->{records}};
  4718         9874  
182             }
183 4718         28364 return;
184             }
185              
186             sub EOF {
187 0     0   0 my $self = shift;
188 0         0 foreach my $rec (@{$self->{records}}, $self->{buffer}) {
  0         0  
189 0 0       0 return if length($rec) > 0;
190             }
191 0         0 return eof($self->{handle});
192             }
193              
194             sub _split {
195 5676     5676   8411 my $self = shift;
196 5676 50       12611 if (!defined $self->{can_use_lookbehind}) {
197 0         0 $self->_compile_rs;
198             }
199 5676         8458 my $rs = $self->{rsc};
200 5676         3530058 my @rec = split $rs, $self->{buffer};
201 5676 100       19557 if ($self->{can_use_lookbehind}) {
202 2         10 return @rec;
203             } else {
204 5674         238871 return grep length, @rec;
205             }
206             }
207              
208             sub CLOSE {
209 12     12   21169 my $self = shift;
210 12         41 $self->_clear_buffer;
211 12         130 my $z = close $self->{handle};
212             # delete $self->{handle};
213 12         58 return $z;
214             }
215              
216             sub _clear_buffer {
217 5534     5534   9104 my $self = shift;
218 5534         9886 $self->{buffer} = '';
219 5534         66046 $self->{records} = [];
220             }
221              
222             sub OPEN {
223 3     3   1028 my ($self, $mode, @args) = @_;
224 3 50       17 if ($self->{handle}) {
225             # close $self->{handle};
226             }
227 3         188 my $z = CORE::open $self->{handle}, $mode, @args;
228 3 50       17 if ($z) {
229 3         13 $self->_clear_buffer;
230             }
231 3         15 return $z;
232             }
233              
234             sub FILENO {
235 1     1   590 my $self = shift;
236 1         6 return fileno($self->{handle});
237             }
238              
239             sub WRITE {
240 0     0   0 my ($self, $buf, $len, $offset) = @_;
241 0   0     0 $offset ||= 0;
242 0 0       0 if (!defined $len) {
243 0         0 $len = length($buf)-$offset;
244             }
245 0         0 $self->PRINT( substr($buf,$offset,$len) );
246             }
247              
248             sub PRINT {
249 13     13   2842 my ($self, @msg) = @_;
250 13 100       27 if ($self->TELL() != tell($self->{handle})) {
251 1         3 $self->SEEK(0,1);
252             } else {
253 12         22 $self->_clear_buffer;
254             }
255 13         17 print {$self->{handle}} @msg;
  13         55  
256             }
257              
258             sub PRINTF {
259 0     0   0 my ($self, $template, @args) = @_;
260 0         0 $self->PRINT(sprintf($template,@args));
261             }
262              
263             sub READ {
264 0     0   0 my $self = shift;
265 0         0 my $bufref = \$_[0];
266 0         0 my (undef, $len, $offset) = @_;
267 0         0 my $nread = 0;
268              
269 0   0     0 while ($len > 0 && @{$self->{records}}) {
  0         0  
270 0 0       0 if (length($self->{records}[0])>=$len) {
271 0         0 my $rec = shift @{$self->{records}};
  0         0  
272 0         0 my $reclen = length($rec);
273 0         0 substr( $$bufref, $offset, $reclen, $rec);
274 0         0 $len -= $reclen;
275 0         0 $offset += $reclen;
276 0         0 $nread += $reclen;
277             } else {
278 0         0 my $rec = substr($self->{records}[0], 0, $len, "");
279 0         0 substr( $$bufref, $offset, $len, $rec);
280 0         0 $offset += $len;
281 0         0 $nread += $len;
282 0         0 $len = 0;
283             }
284             }
285 0 0 0     0 if ($len > 0 && length($self->{buffer}) > 0) {
286 0         0 my $reclen = length($self->{buffer});
287 0 0       0 if ($reclen >= $len) {
288 0         0 my $rec = substr( $self->{buffer}, 0, $len, "" );
289 0         0 substr( $$bufref, $offset, $len, $rec );
290 0         0 $offset += $len;
291 0         0 $nread += $len;
292 0         0 $len = 0;
293             } else {
294 0         0 substr( $$bufref, $offset, $reclen, $self->{buffer} );
295 0         0 $self->{buffer} = "";
296 0         0 $offset += $reclen;
297 0         0 $nread += $reclen;
298 0         0 $len -= $reclen;
299             }
300             }
301 0 0       0 if ($len > 0) {
302 0         0 return $nread + read $self->{handle}, $$bufref, $len, $offset;
303             } else {
304 0         0 return $nread;
305             }
306             }
307              
308             sub GETC {
309 1     1   3 my $self = shift;
310 1 50 33     2 if (@{$self->{records}}==0 && 0 == length($self->{buffer})) {
  1         8  
311 1         3 $self->_populate_buffer;
312             }
313              
314 1 50       2 if (@{$self->{records}}) {
  1 0       11  
315 1         4 my $c = substr( $self->{records}[0], 0, 1, "" );
316 1 50       4 if (0 == length($self->{records}[0])) {
317 0         0 shift @{$self->{records}};
  0         0  
318             }
319 1         4 return $c;
320             } elsif (0 != length($self->{buffer})) {
321 0         0 my $c = substr( $self->{buffer}, 0, 1, "" );
322 0         0 return $c;
323             } else {
324             # eof?
325 0         0 return undef;
326             }
327             }
328              
329             sub BINMODE {
330 1     1   241 my $self = shift;
331 1         2 my $handle = $self->{handle};
332 1 50       3 if (@_) {
333 0         0 CORE::binmode $handle, @_;
334             } else {
335 1         4 CORE::binmode $handle;
336             }
337             }
338              
339             sub SEEK {
340 5507     5507   2762372 my ($self, $pos, $whence) = @_;
341              
342 5507 100       14125 if ($whence == 1) {
343 1         2 $whence = 0;
344 1         2 $pos += $self->TELL;
345             }
346              
347             # easy implementation:
348             # on any seek, clear records, buffer
349              
350 5507         14417 $self->_clear_buffer;
351 5507         51077 seek $self->{handle}, $pos, $whence;
352              
353             # more sophisticated implementation
354             # on a seek forward, remove bytes from the front
355             # of buffered data
356             }
357              
358             sub TELL {
359 5541     5541   40659 my $self = shift;
360             # virtual cursor position is actual position on the file handle
361             # minus the length of any buffered data
362 5541         10766 my $tell = tell $self->{handle};
363 5541         9208 $tell -= length($self->{buffer});
364 5541         7614 $tell -= length($_) for @{$self->{records}};
  5541         77995  
365 5541         12087 return $tell;
366             }
367              
368              
369 18     18   168 no warnings 'redefine';
  18         34  
  18         8879  
370             sub IO::Handle::input_record_separator {
371 15     15 0 1235 my $self = shift;
372 15 50 66     54 if (ref($self) eq 'GLOB' || ref(\$self) eq 'GLOB') {
373 15 100       35 if (tied(*$self)) {
374 6 50       18 if (ref(tied(*$self)) eq __PACKAGE__) {
375 6         15 return input_record_separator($self,@_);
376             }
377 0         0 my $z = eval { (tied *$self)->input_record_separator(@_) };
  0         0  
378 0 0       0 if ($@) {
379 0         0 carp "input_record_separator is not supported on tied handle";
380             }
381 0         0 return $z;
382             }
383 9 100       22 if (!@_) { return $/ }
  6         24  
384 3         17 $self = tie *$self, __PACKAGE__, $self, quotemeta($/);
385 3         11 return input_record_separator($self,@_);
386             } else {
387 0         0 carp "input to input_record_separator was not a handle";
388 0         0 return;
389             }
390             }
391              
392              
393             sub input_record_separator {
394 56     56 1 6724 my $self = shift;
395 56 100 100     250 if (ref($self) eq 'GLOB' || ref(\$self) eq 'GLOB') {
396 39 100       107 if (!tied *$self) {
397 15 100       48 if (!@_) {
398 4         11 return IO::Handle::input_record_separator(*$self);
399             }
400 11         83 $self = tie *$self, __PACKAGE__, $self, quotemeta($/);
401             } else {
402 24         42 $self = tied *$self;
403             }
404             }
405 52 100       118 if (@_) {
406 25         52 $self->{rs} = shift;
407 25         44 delete $self->{can_use_lookbehind};
408             }
409 52         124 $self->_compile_rs;
410 52         120 return $self->{rs};
411             }
412              
413             sub autochomp {
414 28     28 1 3510 my $self = shift;
415 28 100 100     107 if (ref($self) eq 'GLOB' || ref(\$self) eq 'GLOB') {
416 14 100       42 if (!tied *$self) {
417 4 100       13 if (!@_) {
418 2         5 return 0;
419             }
420 2         12 $self = tie *$self, __PACKAGE__, $self, quotemeta($/);
421             } else {
422 10         18 $self = tied *$self;
423             }
424             }
425 26   100     75 my $val = $self->{autochomp} || 0;
426 26 100       64 if (@_) {
427 10         23 $self->{autochomp} = 0+!!$_[0];
428             }
429 26         71 return 0+$val;
430             }
431              
432             sub chomp {
433 158     158 1 26882 my $self = shift;
434 158         239 my $removed = 0;
435 158         239 my $rs = $self->{rs};
436 158         274 foreach my $line (@_) {
437 158         1378 $line =~ s/($rs)$//;
438 158 100       487 if (defined($1)) {
439 120         265 $removed += length($1);
440             }
441             }
442 158         272 return $removed;
443             }
444              
445             1; #
446              
447             __END__