File Coverage

blib/lib/Inline/Files/Virtual.pm
Criterion Covered Total %
statement 205 273 75.0
branch 69 134 51.4
condition 35 86 40.7
subroutine 31 45 68.8
pod 3 18 16.6
total 343 556 61.6


line stmt bran cond sub pod time code
1             package Inline::Files::Virtual;
2             $VERSION = '0.53';
3 8     8   33 use strict;
  8         13  
  8         243  
4 8     8   32 use Carp;
  8         11  
  8         551  
5 8     8   35 use Cwd qw(abs_path);
  8         11  
  8         2212  
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             BEGIN {
24             sub DEBUG () { 0 }
25 8     8   1151 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   12 DEBUG && TRACE(@_);
57 8         19 my $caller = caller;
58 8     8   51 no strict 'refs';
  8         15  
  8         4043  
59 96         612 *{"${caller}::$_"} = \&$_
60 8         44 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         210 1;
65             }
66              
67             sub vf_load {
68 8     8 1 16 DEBUG && TRACE(@_);
69 8         15 my ($file, $header) = @_;
70 8         18 my $path = './';
71 8         20 $file =~ s|\\|/|g;
72 8 50       84 ($path, $file) = ($1, $2) if $file =~ m|^(.*)/(.*)$|;
73 8         307 $file = abs_path($path) . "/$file";
74 8 50       35 return @{$afs{$file}{vfiles}} if $afs{$file};
  0         0  
75 8         49 local ($/, *FILE);
76 8 50       281 open FILE, $file or croak "Could not vf_load '$file'";
77 8         439 my @vdata = split /(?m)($header)/, ;
78 8         123 my ($offset,$linecount) = (0,1);
79 8         26 unshift @vdata, "";
80 8         14 my ($marker, $data, $vfiles);
81 8         48 while (($marker, $data) = splice @vdata,0,2) {
82 20         91 my $vfile = sprintf "$file(%-0.20d)",$offset;
83 20         97 $vfs{$vfile} =
84             { data => $data,
85             marker => $marker,
86             offset => $offset,
87             line => $linecount,
88             };
89 20         40 $offset += length($marker) + length($data);
90 20         52 $linecount += linecount($marker, $data);
91 20         38 push @$vfiles, $vfile;
92 20         25 push @{$mfs{$marker}}, $vfile;
  20         96  
93             }
94 8         30 $afs{$file}{vfiles} = $vfiles;
95 8         29 return @{$vfiles}[1..$#$vfiles];
  8         131  
96             }
97              
98             my $new_counter = 0;
99             sub vf_open (*;$$$) {
100 42     42 0 3522 DEBUG && TRACE(@_);
101 42         75 my $glob = shift;
102 42         58 my $file = shift;
103 42         54 my $symbol = shift;
104              
105 42         49 my $mode;
106 42 100 100     380 if ($file && $file =~ /^(?:\|-|-\||>|<|>>|>:.*)$/) {
107 18         507 $mode = $file;
108 18         28 $file = $symbol;
109 18         39 $symbol = shift;
110             }
111              
112 8     8   44 no strict;
  8         11  
  8         14128  
113 42 100       100 if (defined $glob) {
114 34 100 66     290 $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 8         19 $glob = $_[0] = \do{local *ANON};
  8         59  
122             }
123              
124 42 100       96 if (!$mode) {
125             # Resolve file
126 24   100     59 $file ||= "";
127 24         129 $file =~ s/^([^\w\s\/]*)\s*//i;
128 24   100     129 $mode = $1 || "";
129              
130 24 100 100     165 if (!$mode && $file =~ s/\s*\|\s*$//) {
131 1   50     5 $mode = $mode || "-|";
132             }
133             }
134 42 100       93 unless ($file) {
135 7         12 my $scalar = *{$glob}{SCALAR};
  7         35  
136 7 50       26 $file = $scalar ? $$scalar : "";
137 7         46 $file =~ s/^([^a-z\s\/]*)\s*//i;
138 7   50     46 $mode = $mode || $1 || "<";
139             }
140 42   100     106 $mode ||= "<";
141 42 50 66     186 $file = $mfs{$file}[0] if $file and exists $mfs{$file};
142              
143             # Create a new Inline file (for Inline::Files only)
144 42 100 66     126 if (not $file and defined $Inline::Files::{get_filename}) {
145 2         3 (my $marker = *{$glob}{NAME}) =~ s|.*::(.*)|$1|;
  2         5  
146 2 50       10 if ($marker =~ /^[A-Z](?:_*[A-Z0-9]+)*$/) {
147 2 50       11 if ($file = Inline::Files::get_filename((caller)[0])) {
148 2         4 $marker = "__${marker}__\n";
149 2         8 my $vfile = sprintf "$file(NEW%-0.8d)", ++$new_counter;
150 2         8 $vfs{$vfile} =
151             { data => '',
152             marker => $marker,
153             offset => -1,
154             line => -1,
155             };
156 2         2 push @{$mfs{$marker}}, $vfile;
  2         4  
157 2         3 push @{$afs{$file}{vfiles}}, $vfile;
  2         4  
158 2         26 $file = $vfile;
159             }
160             }
161             }
162              
163 42 50       83 $! = 2, return 0 unless $file; # Can't work at this point; confuses core
164             # Default to CORE::open
165 42 100       103 unless (exists $vfs{$file}) {
166 30         11107 return CORE::open($glob, $mode, $file);
167             }
168              
169 12 50       108 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         114 my $impl = tie (*$glob, 'Inline::Files::Virtual',
174             $file, $afile, $mode, $symbol);
175              
176 12         54 $afs{$afile}{changed} = 0;
177 12 100 100     81 $impl->TRUNCATE() if (exists $write{$mode}
178             and not exists $preserve{$mode});
179 12         35 return 1;
180             }
181              
182             sub linecount {
183 20     20 0 17 DEBUG && TRACE();
184 20         24 my $sum = 0;
185 20         40 foreach (@_) { $sum += tr/\n// }
  40         88  
186 20         37 return $sum;
187             }
188              
189             sub vf_save {
190 14     14 1 22 DEBUG && TRACE(@_);
191 14         35 my @files = @_;
192 14 50       95 @files = keys %afs unless @files;
193 14         40 for my $file (@files) {
194 14 100       74 next unless $afs{$file}{changed};
195 4         8 $afs{$file}{changed}=0;
196 4         10 local *FILE;
197 9         22 open FILE, ">$file"
198 4         33 and print FILE map { my $entry = $vfs{$_};
199 9 50       30 if (length $entry->{data}) {
200 9         39 chomp $entry->{data};
201 9         27 $entry->{data} .= "\n";
202             }
203 9         455 "$entry->{marker}$entry->{data}";
204 4 50 0     129592 } @{$afs{$file}{vfiles}}
      0        
      33        
      33        
205             and close FILE
206             or ($^W and warn "Could not vf_save '$file'\n$!")
207             and return 0;
208             }
209 14         69 return 1;
210             }
211              
212             END {
213 8     8   3081 DEBUG && TRACE(@_);
214 8         39 vf_save;
215             }
216              
217             sub vf_marker ($) {
218 12     12 1 15 DEBUG && TRACE(@_);
219 12         18 my ($virtual_filename) = @_;
220 12         109 return $vfs{$virtual_filename}{marker};
221             }
222              
223             sub vf_prefix ($) {
224 8     8 0 9 DEBUG && TRACE(@_);
225 8         15 my ($actual_filename) = @_;
226 8         219 return $vfs{$afs{$actual_filename}{vfiles}[0]}{data};
227             }
228              
229             sub vf_close (*) {
230 28     28 0 8745 DEBUG && TRACE(@_);
231 28         58 my ($glob) = @_;
232 8     8   102 no strict;
  8         11  
  8         1180  
233 28 100 66     215 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
234 28         83 my $impl = tied(*$glob);
235 28 100       539 return CORE::close $glob unless $impl;
236 6         84 return vf_save();
237             }
238              
239             sub vf_seek (*$$) {
240 11     11 0 19 DEBUG && TRACE(@_);
241 11         20 my ($glob, $pos, $whence) = @_;
242 8     8   45 no strict;
  8         14  
  8         1181  
243 11 50 33     84 $glob = caller() . "::$glob" unless ref($glob) || $glob =~ /::/;
244 11         32 my $impl = tied(*$glob);
245 11 50       28 return seek $glob, $pos, $whence unless $impl;
246 11         27 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   44 no strict;
  8         83  
  8         1145  
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   43 no strict;
  8         12  
  8         1130  
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   47 no strict;
  8         13  
  8         3886  
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   16 DEBUG && TRACE(@_);
282 12         29 my ($class, $vfile, $afile, $mode, $symbol) = @_;
283 12 50       46 my $vfs_entry = $vfs{$vfile} or return;
284 12 50       129 bless { vfile => $vfs_entry,
285             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   32 DEBUG && TRACE(@_);
298 6         17 my($impl,@args) = @_;
299 6 50 0     17 $^W && warn(not_output), return 1 unless exists $write{$impl->{mode}};
300 6         16 my $text = join '', @args;
301 6         18 substr($impl->{vfile}{data},$impl->{pos},-1) = $text;
302 6         12 $impl->{pos} += length $text;
303 6         12 $afs{$impl->{afile}}{changed} = 1;
304 6         15 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   48 use vars '$AUTOLOAD';
  8         11  
  8         1491  
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   31 DEBUG && TRACE(@_);
327             }
328              
329             # Inline::Files support
330             sub _magic_handle {
331 5     5   5 DEBUG && TRACE(@_);
332 5         5 my ($impl) = @_;
333 5 100 66     38 return unless $INC{'Inline/Files.pm'} && $impl->{symbol};
334 8     8   45 no strict 'refs';
  8         12  
  8         6661  
335 4         5 return tie *{$impl->{symbol}}, 'Inline::Files', $impl->{symbol};
  4         30  
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   864 DEBUG && TRACE(@_);
360 17         29 my ($impl) = @_;
361 17 50 0     67 $^W && warn(not_input), return unless exists $read{$impl->{mode}};
362 17 50       102 my $match = !defined($/) ? '.*'
    100          
363             : length $/ ? ".*?\Q$/\E|.*"
364             : '.*?\n{2,}';
365 17         20 my (@lines);
366 17   66     72 my $list_context ||= wantarray;
367 17         18 while (1) {
368 22 100 66     589 if ($impl->{pos} < length $impl->{vfile}{data} and
369             $impl->{vfile}{data} =~ m{\A(.{$impl->{pos}})($match)}s) {
370 17         49 $impl->{pos} += length($2);
371 17         39 push @lines, $2;
372             }
373             else {
374 5 100       34 last unless $impl = _magic_handle($impl);
375 4 100       12 last unless $impl = $impl->MAGIC;
376 3         18 next;
377             }
378 17 100       58 last unless $list_context;
379             }
380 17 100       124 return $list_context ? (@lines) :
    100          
381             @lines ? $lines[0] : undef;
382             }
383              
384             sub MAGIC {
385 3     3 0 3 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   9 DEBUG && TRACE(@_);
408 11         13 my ($impl, $position, $whence) = @_;
409 11         23 my $length = length $impl->{vfile}{data};
410 11         18 my $pos = $impl->{pos};
411 11 50       37 $pos = ( $whence==0 ? $position :
    100          
    100          
412             $whence==1 ? $position + $impl->{pos} :
413             $whence==2 ? $position + $length :
414             return
415             );
416 11 100       35 return if $pos < 0;
417 8 100       17 $pos = $length if $pos >= $length;
418 8         9 $impl->{pos} = $pos;
419 8         39 return 1;
420             }
421              
422             sub TRUNCATE {
423 5     5 0 6 DEBUG && TRACE(@_);
424 5         9 my ($impl, $length) = @_;
425 5   50     23 $length ||= 0;
426 5         40 substr($impl->{vfile}{data},$length,-1) = "";
427 5 50       19 $impl->{pos} = $length if $length < $impl->{pos};
428 5         13 $afs{$impl->{afile}}{changed} = 1;
429 5         8 return 1;
430             }
431              
432             1;
433              
434             __END__