File Coverage

blib/lib/Inline/Files/Virtual.pm
Criterion Covered Total %
statement 203 274 74.0
branch 68 134 50.7
condition 35 86 40.7
subroutine 31 45 68.8
pod 3 18 16.6
total 340 557 61.0


line stmt bran cond sub pod time code
1             package Inline::Files::Virtual;
2             $VERSION = '0.70';
3 8     8   52 use strict;
  8         14  
  8         229  
4 8     8   45 use Carp;
  8         15  
  8         572  
5 8     8   48 use Cwd qw(abs_path);
  8         11  
  8         2513  
6              
7             # To Do:
8             # - Add an EOF flag and make sure (virtual) system calls treat it right
9             # - Call close on an implicit open.
10             # - Add unlink(). Should behave properly if file is open.
11             # - Support this idiom for multiple FOO:
12             # open FOO;
13             # close FOO;
14             # while (open FOO) {
15             # while () {
16             # }
17             # }
18              
19             # Damian. Let's leave this trace feature in for a while.
20             # The calls to it should be constant folded out of the bytecode anyway,
21             # (when DEBUG is 0) so there's no real performance penalty.
22             # It has helped me find many a bug :)
23 0         0 BEGIN {
24             sub DEBUG () { 0 }
25 8     8   1176 my ($TRACING, $ARGS) = (1, 1);
26 0 0   0 0 0 sub TRACING {$ENV{INLINE_FILES_TRACE} || $TRACING}
27 0 0   0 0 0 sub ARGS {$ENV{INLINE_FILES_ARGS} || $ARGS}
28             sub TRACE {
29 0     0 0 0 $| = 1;
30 0         0 local $^W;
31 0 0 0     0 return unless TRACING || ARGS;
32 0 0       0 print "=" x 79, "\n" if ARGS;
33 0 0 0     0 print ((caller(1))[3], "\n") if TRACING || ARGS;
34 0 0       0 return unless @_;
35 0         0 require Data::Dumper;
36 0         0 $Data::Dumper::Purity = 1;
37 0         0 $Data::Dumper::Indent = 1;
38 0 0       0 print Data::Dumper::Dumper(\@_) if ARGS;
39             }
40             }
41              
42             my %vfs; # virtual file system
43             my %mfs; # marker-to-virtual-file mapping
44             my %afs; # actual file system
45              
46             my (%read, %write, %append, %preserve);
47             @read{qw( < +> +< )} = ();
48             @write{qw( > +> +< >> )} = ();
49             @append{qw( >> )} = ();
50             @preserve{qw( >> +< < )} = ();
51              
52 0     0 0 0 sub not_input { "Virtual file not open for input\n" }
53 0     0 0 0 sub not_output { "Virtual file not open for output\n" }
54              
55             sub import {
56 8     8   14 DEBUG && TRACE(@_);
57 8         20 my $caller = caller;
58 8     8   65 no strict 'refs';
  8         14  
  8         4930  
59 96         375 *{"${caller}::$_"} = \&$_
60 8         35 for (qw( vf_load vf_save vf_marker vf_prefix
61             vf_open vf_close vf_seek vf_tell vf_truncate vf_write
62             DEBUG TRACE
63             ));
64 8         234 1;
65             }
66              
67             sub vf_load {
68 8     8 1 17 DEBUG && TRACE(@_);
69 8         26 my ($file, $header) = @_;
70 8         17 my $path = './';
71 8         21 $file =~ s|\\|/|g;
72 8 50       73 ($path, $file) = ($1, $2) if $file =~ m|^(.*)/(.*)$|;
73 8         345 $file = abs_path($path) . "/$file";
74 8 50       43 return @{$afs{$file}{vfiles}} if $afs{$file};
  0         0  
75 8         110 local ($/, *FILE);
76 8 50       306 open FILE, $file or croak "Could not vf_load '$file'";
77 8         573 my @vdata = split /(?m)($header)/, ;
78 8         42 my ($offset,$linecount) = (0,1);
79 8         24 unshift @vdata, "";
80 8         18 my ($marker, $data, $vfiles);
81 8         49 while (($marker, $data) = splice @vdata,0,2) {
82 20         98 my $vfile = sprintf "$file(%-0.20d)",$offset;
83 20         103 $vfs{$vfile} =
84             { data => $data,
85             marker => $marker,
86             offset => $offset,
87             line => $linecount,
88             };
89 20         42 $offset += length($marker) + length($data);
90 20         47 $linecount += linecount($marker, $data);
91 20         40 push @$vfiles, $vfile;
92 20         26 push @{$mfs{$marker}}, $vfile;
  20         86  
93             }
94 8         37 $afs{$file}{vfiles} = $vfiles;
95 8         28 return @{$vfiles}[1..$#$vfiles];
  8         150  
96             }
97              
98             my $new_counter = 0;
99             sub vf_open (*;$$$) {
100 34     34 0 1860 DEBUG && TRACE(@_);
101 34         98 my $glob = shift;
102 34         60 my $file = shift;
103 34         54 my $symbol = shift;
104              
105 34         42 my $mode;
106 34 100 100     276 if ($file && $file =~ /^(?:\|-|-\||>|<|>>|>:.*)$/) {
107 10         25 $mode = $file;
108 10         19 $file = $symbol;
109 10         15 $symbol = shift;
110             }
111              
112 8     8   62 no strict;
  8         17  
  8         9014  
113 34 50       82 if (defined $glob) {
114 34 100 66     190 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
115             # The following line somehow manages to cause failure on threaded perls.
116             # The good news is that everything works just fine without it.
117             # $glob = \*{$glob};
118             }
119             else {
120             # autovivify for: open $fh, $filename
121 0         0 $glob = $_[0] = \do{local *ANON};
  0         0  
122             }
123              
124 34 100       78 if (!$mode) {
125             # Resolve file
126 24   100     56 $file ||= "";
127 24         116 $file =~ s/^([^\w\s\/]*)\s*//i;
128 24   100     108 $mode = $1 || "";
129              
130 24 100 100     110 if (!$mode && $file =~ s/\s*\|\s*$//) {
131 1   50     7 $mode = $mode || "-|";
132             }
133             }
134 34 100       93 unless ($file) {
135 7         12 my $scalar = *{$glob}{SCALAR};
  7         42  
136 7 50       35 $file = $scalar ? $$scalar : "";
137 7         38 $file =~ s/^([^a-z\s\/]*)\s*//i;
138 7   50     44 $mode = $mode || $1 || "<";
139             }
140 34   100     95 $mode ||= "<";
141 34 50 66     135 $file = $mfs{$file}[0] if $file and exists $mfs{$file};
142              
143             # Create a new Inline file (for Inline::Files only)
144 34 100 66     105 if (not $file and defined $Inline::Files::{get_filename}) {
145 2         2 (my $marker = *{$glob}{NAME}) =~ s|.*::(.*)|$1|;
  2         8  
146 2 50       13 if ($marker =~ /^[A-Z](?:_*[A-Z0-9]+)*$/) {
147 2 50       12 if ($file = Inline::Files::get_filename((caller)[0])) {
148 2         6 $marker = "__${marker}__\n";
149 2         12 my $vfile = sprintf "$file(NEW%-0.8d)", ++$new_counter;
150 2         12 $vfs{$vfile} =
151             { data => '',
152             marker => $marker,
153             offset => -1,
154             line => -1,
155             };
156 2         3 push @{$mfs{$marker}}, $vfile;
  2         6  
157 2         4 push @{$afs{$file}{vfiles}}, $vfile;
  2         6  
158 2         6 $file = $vfile;
159             }
160             }
161             }
162              
163 34 50       75 $! = 2, return 0 unless $file; # Can't work at this point; confuses core
164             # Default to CORE::open
165 34 100       129 unless (exists $vfs{$file}) {
166 22         5014 return CORE::open($glob, $mode, $file);
167             }
168              
169 12 50       86 my $afile = $file =~ /^(.*)[(](NEW)?\d+[)]$/ ? $1 :
170             croak "Internal error\n";
171              
172             # If file is virtual, tie it up, and set it up
173 12         97 my $impl = tie (*$glob, 'Inline::Files::Virtual',
174             $file, $afile, $mode, $symbol);
175              
176 12         39 $afs{$afile}{changed} = 0;
177             $impl->TRUNCATE() if (exists $write{$mode}
178 12 100 100     79 and not exists $preserve{$mode});
179 12         34 return 1;
180             }
181              
182             sub linecount {
183 20     20 0 27 DEBUG && TRACE();
184 20         26 my $sum = 0;
185 20         37 foreach (@_) { $sum += tr/\n// }
  40         100  
186 20         30 return $sum;
187             }
188              
189             sub vf_save {
190 14     14 1 26 DEBUG && TRACE(@_);
191 14         34 my @files = @_;
192 14 50       81 @files = keys %afs unless @files;
193 14         43 for my $file (@files) {
194 14 100       64 next unless $afs{$file}{changed};
195 4         8 $afs{$file}{changed}=0;
196 4         12 local *FILE;
197             open FILE, ">$file"
198 9         22 and print FILE map { my $entry = $vfs{$_};
199 9 50       24 if (length $entry->{data}) {
200 9         31 chomp $entry->{data};
201 9         21 $entry->{data} .= "\n";
202             }
203 9         664 "$entry->{marker}$entry->{data}";
204 4 50 0     425 } @{$afs{$file}{vfiles}}
  4   0     27  
      33        
      33        
205             and close FILE
206             or ($^W and warn "Could not vf_save '$file'\n$!")
207             and return 0;
208             }
209 14         75 return 1;
210             }
211              
212             END {
213 8     8   1890 DEBUG && TRACE(@_);
214 8         40 vf_save;
215             }
216              
217             sub vf_marker ($) {
218 12     12 1 18 DEBUG && TRACE(@_);
219 12         23 my ($virtual_filename) = @_;
220 12         35 return $vfs{$virtual_filename}{marker};
221             }
222              
223             sub vf_prefix ($) {
224 8     8 0 20 DEBUG && TRACE(@_);
225 8         23 my ($actual_filename) = @_;
226 8         199 return $vfs{$afs{$actual_filename}{vfiles}[0]}{data};
227             }
228              
229             sub vf_close (*) {
230 28     28 0 8656 DEBUG && TRACE(@_);
231 28         86 my ($glob) = @_;
232 8     8   67 no strict;
  8         16  
  8         1169  
233 28 100 66     212 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
234 28         111 my $impl = tied(*$glob);
235 28 100       554 return CORE::close $glob unless $impl;
236 6         20 return vf_save();
237             }
238              
239             sub vf_seek (*$$) {
240 11     11 0 30 DEBUG && TRACE(@_);
241 11         41 my ($glob, $pos, $whence) = @_;
242 8     8   59 no strict;
  8         12  
  8         1058  
243 11 50 33     76 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
244 11         30 my $impl = tied(*$glob);
245 11 50       20 return seek $glob, $pos, $whence unless $impl;
246 11         24 return $impl->SEEK($pos, $whence);
247             }
248              
249             sub vf_tell (*) {
250 0     0 0 0 DEBUG && TRACE(@_);
251 0         0 my ($glob) = @_;
252 8     8   61 no strict;
  8         32  
  8         1136  
253 0 0 0     0 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
254 0         0 my $impl = tied(*$glob);
255 0 0       0 return tell $glob unless $impl;
256 0         0 return $impl->TELL();
257             }
258              
259             sub vf_truncate (*$) {
260 0     0 0 0 DEBUG && TRACE(@_);
261 0         0 my ($glob, $length) = @_;
262 8     8   52 no strict;
  8         19  
  8         1066  
263 0 0 0     0 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
264 0         0 my $impl = tied(*$glob);
265 0 0       0 return truncate $glob, $length unless $impl;
266 0         0 return $impl->TRUNCATE($length);
267             }
268              
269              
270             sub vf_write (*) {
271 0     0 0 0 DEBUG && TRACE(@_);
272 0         0 my ($glob) = @_;
273 8     8   54 no strict;
  8         13  
  8         3969  
274 0 0 0     0 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
275 0         0 my $impl = tied(*$glob);
276 0 0       0 return write $glob unless $impl;
277 0         0 return $impl->WRITE();
278             }
279              
280             sub TIEHANDLE {
281 12     12   20 DEBUG && TRACE(@_);
282 12         38 my ($class, $vfile, $afile, $mode, $symbol) = @_;
283 12 50       65 my $vfs_entry = $vfs{$vfile} or return;
284             bless { vfile => $vfs_entry,
285 12 50       122 pos => exists $append{$mode} ? length $vfs_entry->{data} : 0,
286             mode => $mode,
287             symbol => $symbol,
288             afile => $afile,
289             }, $class;
290             }
291              
292             sub STORE {
293 0     0   0 DEBUG && TRACE(@_);
294             }
295              
296             sub PRINT {
297 6     6   40 DEBUG && TRACE(@_);
298 6         16 my($impl,@args) = @_;
299 6 50 0     22 $^W && warn(not_output), return 1 unless exists $write{$impl->{mode}};
300 6         26 my $text = join '', @args;
301 6         18 substr($impl->{vfile}{data},$impl->{pos},-1) = $text;
302 6         14 $impl->{pos} += length $text;
303 6         12 $afs{$impl->{afile}}{changed} = 1;
304 6         14 return 1;
305             }
306              
307             sub PRINTF {
308 0     0   0 DEBUG && TRACE(@_);
309 0         0 my($impl,$format,@args) = @_;
310 0 0 0     0 $^W && warn(not_output), return 1 unless exists $write{$impl->{mode}};
311 0         0 my $text = sprintf($format,@args);
312 0         0 substr($impl->{vfile}{data},$impl->{pos},-1) = $text;
313 0         0 $impl->{pos} += length $text;
314 0         0 $afs{$impl->{afile}}{changed} = 1;
315 0         0 return 1;
316             }
317              
318 8     8   63 use vars '$AUTOLOAD';
  8         14  
  8         1439  
319             sub AUTOLOAD {
320 0     0   0 DEBUG && TRACE(@_);
321 0         0 my $impl = shift;
322 0         0 croak "$AUTOLOAD not yet implemented";
323             }
324              
325             sub DESTROY {
326 4     4   14 DEBUG && TRACE(@_);
327             }
328              
329             # Inline::Files support
330             sub _magic_handle {
331 5     5   6 DEBUG && TRACE(@_);
332 5         9 my ($impl) = @_;
333 5 100 66     22 return unless $INC{'Inline/Files.pm'} && $impl->{symbol};
334 8     8   56 no strict 'refs';
  8         23  
  8         6663  
335 4         5 return tie *{$impl->{symbol}}, 'Inline::Files', $impl->{symbol};
  4         22  
336             }
337              
338             sub READ {
339 0     0   0 DEBUG && TRACE(@_);
340 0         0 my($impl,$buffer,$length,$offset) = @_;
341 0 0 0     0 $^W && warn(not_input), return unless exists $read{$impl->{mode}};
342 0 0       0 $offset = $impl->{pos} unless defined $offset;
343 0         0 my $remainder = length($impl->{vfile}{data})-$impl->{pos};
344 0 0       0 $length = $remainder if $remainder < $length;
345 0         0 $_[1] = substr($impl->{vfile}{data},$offset,$length);
346 0         0 $impl->{pos} += $length;
347 0 0       0 if ($length>=0) {
    0          
348 0         0 return $length
349             }
350             elsif ($impl = _magic_handle($impl)) {
351 0         0 return $impl->READ($buffer,$length,$offset);
352             }
353             else {
354 0         0 return;
355             }
356             }
357              
358             sub READLINE {
359 17     17   158 DEBUG && TRACE(@_);
360 17         37 my ($impl) = @_;
361 17 50 0     50 $^W && warn(not_input), return unless exists $read{$impl->{mode}};
362 17 50       70 my $match = !defined($/) ? '.*'
    100          
363             : length $/ ? ".*?\Q$/\E|.*"
364             : '.*?\n{2,}';
365 17         29 my (@lines);
366 17   66     68 my $list_context ||= wantarray;
367 17         26 while (1) {
368 22 100 66     399 if ($impl->{pos} < length $impl->{vfile}{data} and
369             $impl->{vfile}{data} =~ m{\A(.{$impl->{pos}})($match)}s) {
370 17         59 $impl->{pos} += length($2);
371 17         48 push @lines, $2;
372             }
373             else {
374 5 100       12 last unless $impl = _magic_handle($impl);
375 4 100       23 last unless $impl = $impl->MAGIC;
376 3         5 next;
377             }
378 17 100       48 last unless $list_context;
379             }
380 17 100       109 return $list_context ? (@lines) :
    100          
381             @lines ? $lines[0] : undef;
382             }
383              
384             sub MAGIC {
385 3     3 0 4 DEBUG && TRACE(@_);
386 3         14 $_[0];
387             }
388              
389             sub GETC {
390 0     0   0 DEBUG && TRACE(@_);
391 0         0 my ($impl) = @_;
392 0 0 0     0 $^W && warn(not_input), return unless exists $read{$impl->{mode}};
393 0         0 my $char = substr($impl->{vfile}{data},$impl->{pos},1);
394 0         0 $impl->{pos}++;
395 0 0       0 return $char if length $char;
396 0 0       0 return unless $impl = _magic_handle($impl);
397 0         0 return $impl->GETC();
398             }
399              
400             sub TELL {
401 0     0   0 DEBUG && TRACE(@_);
402 0         0 my ($impl) = @_;
403 0         0 return $impl->{pos};
404             }
405              
406             sub SEEK {
407 11     11   13 DEBUG && TRACE(@_);
408 11         17 my ($impl, $position, $whence) = @_;
409 11         20 my $length = length $impl->{vfile}{data};
410 11         14 my $pos = $impl->{pos};
411             $pos = ( $whence==0 ? $position :
412             $whence==1 ? $position + $impl->{pos} :
413 11 50       42 $whence==2 ? $position + $length :
    100          
    100          
414             return
415             );
416 11 100       29 return if $pos < 0;
417 8 100       15 $pos = $length if $pos >= $length;
418 8         10 $impl->{pos} = $pos;
419 8         69 return 1;
420             }
421              
422             sub TRUNCATE {
423 5     5 0 7 DEBUG && TRACE(@_);
424 5         14 my ($impl, $length) = @_;
425 5   50     23 $length ||= 0;
426 5         38 substr($impl->{vfile}{data},$length,-1) = "";
427 5 50       19 $impl->{pos} = $length if $length < $impl->{pos};
428 5         16 $afs{$impl->{afile}}{changed} = 1;
429 5         7 return 1;
430             }
431              
432             1;
433              
434             __END__