File Coverage

blib/lib/PDL/IO/Dumper.pm
Criterion Covered Total %
statement 149 179 83.2
branch 38 56 67.8
condition 16 23 69.5
subroutine 16 20 80.0
pod 9 9 100.0
total 228 287 79.4


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PDL::IO::Dumper -- data dumping for structs with PDLs
4              
5             =head1 DESCRIPTION
6              
7             This package allows you cleanly to save and restore complex data structures
8             which include PDLs, as ASCII strings and/or transportable ASCII files. It
9             exports four functions into your namespace: sdump, fdump, frestore, and
10             deep_copy.
11              
12             PDL::IO::Dumper traverses the same types of structure that Data::Dumper
13             knows about, because it uses a call to Data::Dumper. Unlike Data::Dumper
14             it doesn't crash when accessing PDLs.
15              
16             The PDL::IO::Dumper routines have a slightly different syntax than
17             Data::Dumper does: you may only dump a single scalar perl expression
18             rather than an arbitrary one. Of course, the scalar may be a ref to
19             whatever humongous pile of spaghetti you want, so that's no big loss.
20              
21             The output string is intended to be about as readable as Dumper's
22             output is for non-PDL expressions. To that end, small PDLs (up to 8
23             elements) are stored as inline perl expressions, midsized PDLs (up to
24             200 elements) are stored as perl expressions above the main data
25             structure, and large PDLs are stored as FITS files that are uuencoded
26             and included in the dump string.
27              
28             No attempt is made to shrink the output string -- for example, inlined
29             PDL expressions all include explicit reshape() and typecast commands,
30             and uuencoding expands stuff by a factor of about 1.5. So your data
31             structures will grow when you dump them.
32              
33             =head1 Bugs
34              
35             It's still possible to break this code and cause it to dump core, for
36             the same reason that Data::Dumper crashes. In particular, other
37             external-hook variables aren't recognized (for that a more universal
38             Dumper would be needed) and will still exercise the Data::Dumper crash.
39             This is by choice: (A) it's difficult to recognize which objects
40             are actually external, and (B) most everyday objects are quite safe.
41              
42             Another shortfall of Data::Dumper is that it doesn't recognize tied objects.
43             This might be a Good Thing or a Bad Thing depending on your point of view,
44             but it means that PDL::IO::Dumper includes a kludge to handle the tied
45             Astro::FITS::Header objects associated with FITS headers (see the rfits
46             documentation in PDL::IO::Misc for details).
47              
48             There's currently no reference recursion detection, so a non-treelike
49             reference topology will cause Dumper to buzz forever. That will
50             likely be fixed in a future version. Meanwhile a warning message finds
51             likely cases.
52              
53             =head1 FUNCTIONS
54              
55             =cut
56              
57             package PDL::IO::Dumper;
58 1     1   6001 use strict;
  1         4  
  1         40  
59 1     1   11 use warnings;
  1         3  
  1         50  
60 1     1   982 use File::Temp;
  1         28843  
  1         124  
61 1     1   9 use Exporter ();
  1         2  
  1         23  
62 1     1   506 use PDL;
  1         8  
  1         7  
63 1     1   6 use PDL::Exporter;
  1         339  
  1         8  
64 1     1   642 use Data::Dumper 2.121;
  1         10685  
  1         99  
65 1     1   8 use Carp;
  1         2  
  1         3126  
66              
67             our $VERSION = '1.3.2';
68             our @ISA = qw( Exporter ) ;
69             our @EXPORT_OK = qw( fdump sdump frestore deep_copy);
70             our @EXPORT = @EXPORT_OK;
71             our %EXPORT_TAGS = ( Func=>\@EXPORT_OK);
72              
73             ######################################################################
74              
75             =head2 sdump
76              
77             =for ref
78              
79             Dump a data structure to a string.
80              
81             =for usage
82              
83             use PDL::IO::Dumper;
84             $s = sdump();
85             ...
86             = eval $s;
87              
88             =for description
89              
90             sdump dumps a single complex data structure into a string. You restore
91             the data structure by eval-ing the string. Since eval is a builtin, no
92             convenience routine exists to use it.
93              
94             =cut
95              
96             sub PDL::IO::Dumper::sdump {
97             # Make an initial dump...
98 7     7 1 30 local $Data::Dumper::Purity = 1;
99 7         55 my($s) = Data::Dumper->Dump([@_]);
100 7         606 my(%pdls);
101             # Find the bless(...,'PDL') lines
102 7         61 while($s =~ s/bless\( do\{\\\(my \$o \= '?(-?\d+)'?\)\}\, \'PDL\' \)/sprintf('$PDL_%u',$1)/e) {
  11         60  
103 11         51 $pdls{$1}++;
104             }
105              
106             ## Check for duplicates -- a weak proxy for recursion...
107 7         17 my($v);
108             my($dups);
109 7         20 foreach $v(keys %pdls) {
110 11 50       28 $dups++ if($pdls{$v} >1);
111             }
112 7 50       18 print STDERR "Warning: duplicated PDL ref. If sdump hangs, you have a circular reference.\n" if($dups);
113              
114             # This next is broken into two parts to ensure $s is evaluated *after* the
115             # find_PDLs call (which modifies $s using the s/// operator).
116              
117 7         33 my($s2) = "{my(\$VAR1);\n".&PDL::IO::Dumper::find_PDLs(\$s,@_)."\n\n";
118 7         147 return $s2.$s."\n\$VAR1}";
119              
120             #
121             }
122              
123             ######################################################################
124              
125             =head2 fdump
126              
127             =for ref
128              
129             Dump a data structure to a file
130              
131             =for usage
132              
133             use PDL::IO::Dumper;
134             fdump(,$filename);
135             ...
136             = frestore($filename);
137              
138             =for description
139              
140             fdump dumps a single complex data structure to a file. You restore the
141             data structure by eval-ing the perl code put in the file. A convenience
142             routine (frestore) exists to do it for you.
143              
144             I suggest using the extension '.pld' or (for non-broken OS's) '.pdld'
145             to distinguish Dumper files. That way they are reminiscent of .pl
146             files for perl, while still looking a little different so you can pick
147             them out. You can certainly feed a dump file straight into perl (for
148             syntax checking) but it will not do much for you, just build your data
149             structure and exit.
150              
151             =cut
152              
153             sub PDL::IO::Dumper::fdump {
154 0     0 1 0 my($struct,$file) = @_;
155 0         0 open my $fh, ">", $file;
156 0 0       0 unless ( defined $fh ) {
157 0         0 Carp::cluck ("fdump: couldn't open '$file'\n");
158 0         0 return undef;
159             }
160 0         0 print $fh "####################\n## PDL::IO::Dumper dump file -- eval this in perl/PDL.\n\n";
161 0         0 print $fh sdump($struct);
162 0         0 return $struct;
163             }
164              
165             ######################################################################
166              
167             =head2 frestore
168              
169             =for ref
170              
171             Restore a dumped file
172              
173             =for usage
174              
175             use PDL::IO::Dumper;
176             fdump(,$filename);
177             ...
178             = frestore($filename);
179              
180             =for description
181              
182             frestore() is a convenience function that just reads in the named
183             file and executes it in an eval. It's paired with fdump().
184              
185             =cut
186              
187             sub PDL::IO::Dumper::frestore {
188 0     0 1 0 local($_);
189 0         0 my($fname) = shift;
190 0         0 open my $fh, "<", $fname;
191 0 0       0 unless ( defined $fh ) {
192 0         0 Carp::cluck("frestore: couldn't open '$fname'\n");
193 0         0 return undef;
194             }
195 0         0 my($file) = join("",<$fh>);
196 0         0 return eval $file;
197             }
198              
199             ######################################################################
200              
201             =head2 deep_copy
202              
203             =for ref
204              
205             Convenience function copies a complete perl data structure by the
206             brute force method of "eval sdump".
207              
208             =cut
209              
210             sub PDL::IO::Dumper::deep_copy {
211 0     0 1 0 return eval sdump @_;
212             }
213              
214             ######################################################################
215              
216             =head2 PDL::IO::Dumper::big_PDL
217              
218             =for ref
219              
220             Identify whether a PDL is ``big'' [Internal routine]
221              
222             Internal routine takes a PDL and returns a boolean indicating whether
223             it's small enough for direct insertion into the dump string. If 0,
224             it can be inserted. Larger numbers yield larger scopes of PDL.
225             1 implies that it should be broken out but can be handled with a couple
226             of perl commands; 2 implies full uudecode treatment.
227              
228             PDLs with Astro::FITS::Header objects as headers are taken to be FITS
229             files and are always treated as huge, regardless of size.
230              
231             =cut
232              
233             $PDL::IO::Dumper::small_thresh = 8; # Smaller than this gets inlined
234             $PDL::IO::Dumper::med_thresh = 400; # Smaller than this gets eval'ed
235             # Any bigger gets uuencoded
236              
237             sub PDL::IO::Dumper::big_PDL {
238 11     11 1 20 my($x) = shift;
239            
240             return 0
241             if($x->nelem <= $PDL::IO::Dumper::small_thresh
242 11 100 100     116 && !(keys %{$x->hdr()})
  2         12  
243             );
244            
245             return 1
246             if($x->nelem <= $PDL::IO::Dumper::med_thresh
247 10 100 50     34 && ( !( ( (tied %{$x->hdr()}) || '' ) =~ m/^Astro::FITS::Header\=/) )
      66        
248             );
249              
250 4         11 return 2;
251             }
252              
253             ######################################################################
254              
255             =head2 PDL::IO::Dumper::stringify_PDL
256              
257             =for ref
258              
259             Turn a PDL into a 1-part perl expr [Internal routine]
260              
261             Internal routine that takes a PDL and returns a perl string that evals to the
262             PDL. It should be used with care because it doesn't dump headers and
263             it doesn't check number of elements. The point here is that numbers are
264             dumped with the correct precision for their storage class. Things we
265             don't know about get stringified element-by-element by their builtin class,
266             which is probably not a bad guess.
267              
268             =cut
269              
270             %PDL::IO::Dumper::stringify_formats = (
271             "byte"=>"%d",
272             "short"=>"%d",
273             "long"=>"%d",
274             "float"=>"%.6g",
275             "double"=>"%.16g"
276             );
277              
278              
279             sub PDL::IO::Dumper::stringify_PDL{
280 7     7 1 11 my($pdl) = shift;
281            
282 7 50       16 if(!ref $pdl) {
283 0         0 confess "PDL::IO::Dumper::stringify -- got a non-pdl value!\n";
284 0         0 die;
285             }
286              
287             ## Special case: empty PDL
288 7 50       18 if($pdl->nelem == 0) {
289 0         0 return "which(pdl(0))";
290             }
291              
292             ## Normal case: Figure out how to dump each number and dump them
293             ## in sequence as ASCII strings...
294              
295 7         23 my($pdlflat) = $pdl->flat;
296 7         23 my($t) = $pdl->type;
297              
298 7         8 my($dmp_elt);
299 7 50       18 if(defined $PDL::IO::Dumper::stringify_formats{$t}) {
300 7         11 $dmp_elt = eval "sub { sprintf '$PDL::IO::Dumper::stringify_formats{$t}',shift }";
301             } else {
302 0 0       0 if(!$PDL::IO::Dumper::stringify_warned) {
303 0         0 print STDERR "PDL::IO::Dumper: Warning, stringifying a '$t' PDL using default method\n\t(Will be silent after this)\n";
304 0         0 $PDL::IO::Dumper::stringify_warned = 1;
305             }
306 0     0   0 $dmp_elt = sub { my($x) = shift; "$x"; };
  0         0  
  0         0  
307             }
308              
309 7         12 my(@s);
310 7         32 for (my $i = 0; $i < $pdl->nelem; $i++) {
311 60         190 push(@s, &{$dmp_elt}( $pdlflat->slice("($i)") ) );
  60         1087  
312             }
313            
314             ## Assemble all the strings and bracket with a pdl() call.
315            
316 7 50 66     27 my $s = ($PDL::IO::Dumper::stringify_formats{$t}?$t:'pdl').
317             "(" . join( "," , @s ) . ")".
318             (($_->getndims > 1) && ("->reshape(" . join(",",$pdl->dims) . ")"));
319              
320 7         90 return $s;
321             }
322              
323              
324             ######################################################################
325              
326             =head2 PDL::IO::Dumper::uudecode_PDL
327              
328             =for ref
329              
330             Recover a PDL from a uuencoded string [Internal routine]
331              
332             This routine encapsulates uudecoding of the dumped string for large ndarrays.
333              
334             =cut
335              
336             sub _make_tmpname () {
337 8     8   30 return File::Temp::tmpnam() . ".fits";
338             }
339              
340             sub PDL::IO::Dumper::uudecode_PDL {
341 4     4 1 1808 my $lines = shift;
342 4         8 my $out;
343 4         14 my $fname = _make_tmpname();
344 4         1219 my @result;
345 4         12 my $mode = my $file = "";
346 4         45 while ($lines =~ m/\G(.*?(\n|\r|\r\n|\n\r))/gc) {
347 780         1301 my $line = $1;
348 780 100 66     1497 if ($file eq "" and !$mode){
349 4         28 ($mode,$file) = $line =~ /^begin\s+(\d+)\s+(.+)$/ ;
350 4         30 next;
351             }
352 776 50 33     1450 next if $file eq "" and !$mode;
353 776 100       1390 last if $line =~ /^end/;
354 772         1485 my $string = substr($line,0,int((((ord($line) - 32) & 077) + 2) / 3)*4+1);
355 772   100     6104 push @result, unpack("u", $string) // "";
356             }
357 4         88 my $fits = join "",@result;
358 4         769 open my $fh, ">", $fname;
359 4         177 print $fh $fits;
360 4         110 close $fh;
361 4         40 $out = rfits($fname);
362 4         458 unlink($fname);
363 4         199 $out;
364             }
365            
366             =head2 PDL::IO::Dumper::dump_PDL
367              
368             =for ref
369              
370             Generate 1- or 2-part expr for a PDL [Internal routine]
371              
372             Internal routine that produces commands defining a PDL. You supply
373             (, ) and get back two strings: a prepended command string and an
374             expr that evaluates to the final PDL. PDL is the PDL you want to dump.
375             is a flag whether dump_PDL is being called inline or before
376             the inline dump string (0 for before; 1 for in). is the
377             name of the variable to be assigned (for medium and large PDLs,
378             which are defined before the dump string and assigned unique IDs).
379              
380             =cut
381              
382             sub PDL::IO::Dumper::dump_PDL {
383 11     11 1 17 local($_) = shift;
384 11         33 my($pdlid) = @_;
385 11         15 my(@out);
386              
387 11         58 my($style) = &PDL::IO::Dumper::big_PDL($_);
388              
389 11 100       26 if($style==0) {
390 1         3 @out = ("", "( ". &PDL::IO::Dumper::stringify_PDL($_). " )");
391             }
392              
393             else {
394 10         15 my(@s);
395              
396             ## midsized case
397 10 100       18 if($style==1){
398 6         16 @s = ("my(\$$pdlid) = (",
399             &PDL::IO::Dumper::stringify_PDL($_),
400             ");\n");
401             }
402              
403             ## huge case
404             else {
405            
406             ##
407             ## Write FITS file, uuencode it, snarf it up, and clean up the
408             ## temporary directory
409             ##
410 4         13 my $fname = _make_tmpname();
411 4         1583 wfits($_,$fname);
412 4         190 open my $fh,"<", $fname;
413 4         18 my $mode = "644";
414 4         8 my $file = "uuencode.uu";
415 4         17 my @uulines = "begin $mode $file\n";
416 4         10 binmode($fh);
417 4         7 my $in = do { local $/; <$fh> };
  4         19  
  4         182  
418 4         20 pos($in)=0;
419 4         1688 push @uulines, pack("u", $1) while $in =~ m/\G(.{1,45})/sgc;
420 4         19 push @uulines, "`\n", "end\n";
421 4         61 close $fh;
422 4         532 unlink $fname;
423              
424             ##
425             ## Generate commands to uudecode the FITS file and resnarf it
426             ##
427 4         173 @s = ("my(\$$pdlid) = PDL::IO::Dumper::uudecode_PDL(<<'DuMPERFILE'\n",
428             @uulines,
429             "\nDuMPERFILE\n);\n",
430             "\$$pdlid->hdrcpy(".$_->hdrcpy().");\n"
431             );
432              
433             ##
434             ## Unfortunately, FITS format mangles headers (and gives us one
435             ## even if we don't want it). Delete the FITS header if we don't
436             ## want one.
437             ##
438 4 100       9 if( !scalar(keys %{$_->hdr()}) ) {
  4         50  
439 3         49 push(@s,"\$$pdlid->sethdr(undef);\n");
440             }
441             }
442              
443             ##
444             ## Generate commands to reconstitute the header
445             ## information in the PDL -- common to midsized and huge case.
446             ##
447             ## We normally want to reconstitute, because FITS headers mangle
448             ## arbitrary hashes and we can reconsitute efficiently with a private
449             ## sdump(). The one known exception to this is when there's a FITS
450             ## header object (Astro::FITS::Header) tied to the original
451             ## PDL's header. Other types of tied object will get handled just
452             ## like normal hashes.
453             ##
454             ## Ultimately, Data::Dumper will get fixed to handle tied objects,
455             ## and this kludge will go away.
456             ##
457              
458 10 100       21 if( scalar(keys %{$_->hdr()}) ) {
  10         41  
459 2 50 50     3 if( ((tied %{$_->hdr()}) || '') =~ m/Astro::FITS::Header\=/) {
460 0         0 push(@s,"# (Header restored from FITS file)\n");
461             } else {
462 2         16 push(@s,"\$$pdlid->sethdr( eval <<'EndOfHeader_${pdlid}'\n",
463             &PDL::IO::Dumper::sdump($_->hdr()),
464             "\nEndOfHeader_${pdlid}\n);\n",
465             "\$$pdlid->hdrcpy(".$_->hdrcpy().");\n"
466             );
467             }
468             }
469            
470 10         233 @out = (join("",@s), undef);
471             }
472              
473 11         58 return @out;
474             }
475            
476             ######################################################################
477              
478             =head2 PDL::IO::Dumper::find_PDLs
479              
480             =for ref
481              
482             Walk a data structure and dump PDLs [Internal routine]
483              
484             Walks the original data structure and generates appropriate exprs
485             for each PDL. The exprs are inserted into the Data::Dumper output
486             string. You shouldn't call this unless you know what you're doing.
487             (see sdump, above).
488              
489             =cut
490              
491             sub PDL::IO::Dumper::find_PDLs {
492 7     7 1 17 my($sp, @items) = @_;
493              
494              
495 7         23 my $out_aref = _find_PDLs_inner(dumped_string => $sp, items => \@items);
496              
497             # deduplicate - should not be needed now but retained just in case.
498 7         16 my @uniq;
499             my %seen;
500             LINE:
501 7         17 foreach my $line (@$out_aref) {
502 11 100       60 if ($line =~ /^my\(\$(PDL_\d+)\)/) {
503 10         25 my $id = $1;
504 10 50       23 next LINE if $seen{$id};
505 10         25 $seen{$id}++;
506             }
507 11         24 push @uniq, $line;
508             }
509              
510 7         102 my $out = join "\n", @uniq;
511 7         17 $out .= "\n";
512              
513 7         118 return $out;
514             }
515              
516             sub _find_PDLs_inner {
517 25     25   76 my %args = @_;
518 25         41 my $sp = $args{dumped_string};
519             # internal sub so legitimate uses will pass an array
520 25         27 my @items = @{$args{items}};
  25         41  
521 25   100     77 my $seen = $args{seen} //= {};
522              
523 25         31 my @out;
524              
525             findpdl:
526 25         43 foreach my $item (@items) {
527 25 100       55 next findpdl unless ref($item);
528              
529 22 100       145 if(UNIVERSAL::isa($item,'ARRAY')) {
    100          
    50          
    0          
530 2         4 my($x);
531 2         4 foreach $x(@{$item}) {
  2         6  
532 5         21 my $res = _find_PDLs_inner(%args, items => [$x]);
533 5         18 push @out, @$res;
534             }
535             }
536             elsif(UNIVERSAL::isa($item,'HASH')) {
537 5         7 my($x);
538 5         7 foreach $x (values %{$item}) {
  5         14  
539 13         37 my $res = _find_PDLs_inner(%args, items => [$x]);
540 13         35 push @out, @$res;
541             }
542             }
543             elsif(UNIVERSAL::isa($item,'PDL')) {
544              
545             # In addition to straight PDLs,
546             # this gets subclasses of PDL, but NOT magic-hash subclasses of
547             # PDL (because they'd be got by the previous clause).
548             # So if you subclass PDL but your actual data structure is still
549             # just a straight PDL (and not a hash with PDL field), you end up here.
550             #
551              
552 15         111 my($pdlid) = sprintf('PDL_%u',$$item);
553 15 100       36 if (!$seen->{$pdlid}) {
554 11         23 my (@strings) = &PDL::IO::Dumper::dump_PDL($item, $pdlid);
555              
556 11         27 push @out, $strings[0];
557 11 100       38 $$sp =~ s/\$$pdlid/$strings[1]/g if (defined($strings[1]));
558 11         45 $seen->{$pdlid}++;
559             }
560             }
561             elsif(UNIVERSAL::isa($item,'SCALAR')) {
562             # This gets other kinds of refs -- PDLs have already been got.
563             # Naked PDLs are themselves SCALARs, so the SCALAR case has to come
564             # last to let the PDL case run.
565 0         0 my $res = _find_PDLs_inner( %args, items => [${$item}] );
  0         0  
566 0         0 push @out, @$res;
567             }
568              
569             }
570              
571 25         79 return \@out;
572             }
573              
574             =head1 AUTHOR
575              
576             Copyright 2002, Craig DeForest.
577              
578             This code may be distributed under the same terms as Perl itself
579             (license available at L). Copying, reverse
580             engineering, distribution, and modification are explicitly allowed so
581             long as this notice is preserved intact and modified versions are
582             clearly marked as such.
583              
584             This package comes with NO WARRANTY.
585              
586             =cut
587              
588             1;