File Coverage

blib/lib/Python/Serialise/Marshal.pm
Criterion Covered Total %
statement 129 140 92.1
branch 15 20 75.0
condition 16 33 48.4
subroutine 24 29 82.7
pod 4 4 100.0
total 188 226 83.1


line stmt bran cond sub pod time code
1             package Python::Serialise::Marshal;
2              
3 9     9   12695 use strict;
  9         21  
  9         375  
4 9     9   8900 use IO::File;
  9         135091  
  9         1698  
5 9     9   9683 use File::Binary;
  9         21650  
  9         269  
6 9     9   64 use Math::Complex;
  9         17  
  9         2532  
7 9     9   54 use vars qw($VERSION);
  9         19  
  9         16886  
8              
9             $VERSION = '0.02';
10              
11              
12             my %_set_handlers = (
13            
14             'NONE' => \&_set_none,
15             'INTEGER' => \&_set_int,
16             'FLOAT' => \&_set_float,
17             'LONG' => \&_set_long,
18             'STRING' => \&_set_string,
19             'HASH' => \&_set_dict,
20             'ARRAY' => \&_set_mylist,
21             'COMPLEX' => \&_set_complex,
22             );
23              
24              
25             my %_get_handlers = (
26             'N' => \&_get_none,
27             'i' => \&_get_int,
28             # 'l' => \&_get_long, # long support is broken
29             'f' => \&_get_float,
30             's' => \&_get_string,
31             '(' => \&_get_list,
32             '[' => \&_get_list,
33             '{' => \&_get_dict,
34             'x' => \&_get_complex,
35             );
36              
37              
38             =head1 NAME
39              
40             Python::Serialise::Marshal - a module for reading and writing marshalled Python files
41              
42             =head1 SYNOPSIS
43              
44              
45             use Python::Serialise::Marshal;
46              
47             my $pr = Python::Serialise::Marshal->new("file/for/reading");
48             while (my $data = $pr->load()) {
49             print Dumper $data;
50             }
51              
52             my $pw = Python::Serialise::Marshal->new(">file/for/writing");
53            
54             $pw->dump(['a' 'list']);
55             $pw->dump("a string");
56             $pw->dump(42);
57             $pw->dump({'a'=>'hash'});
58              
59             $pw->close();
60              
61             =head1 DESCRIPTION
62              
63             Marshalling is a method of serialising files in Python (another method,
64             Pickling, is also available). It is the method that Mailman uses to store
65             its config files.
66              
67             This module is an attempt to write a pure Perl implementation of the algorithm.
68              
69             =head1 METHODS
70              
71             =head2 new
72              
73             Open a file for reading or writing. Can take any arguments that C can.
74              
75             =cut
76              
77              
78             sub new {
79 27     27 1 13726 my $class = shift;
80 27   50     111 my $file = shift || die "You must pass a file\n";
81              
82             ## FIXME error here
83 27   50     165 my $fh = File::Binary->new($file) || die "Couldn't open file\n";
84 27         6286 my $self = { _fh => $fh };
85              
86 27         98 $self->{_fh}->set_endian($File::Binary::BIG_ENDIAN);
87              
88 27         359 return bless $self, $class;
89            
90             }
91              
92              
93              
94              
95             =head2 load
96              
97             Returns the next data structure from the marshalled file or undef.
98              
99             =cut
100              
101             sub load {
102 150     150 1 17498 my $self = shift;
103 150         410 $self->{_cur_id} = 0;
104              
105              
106 150         350 my $id = $self->_get_char();
107 150 50 33     10600 return undef if (!defined $id or $id eq "");
108              
109 150   50     794 my $sub = $_get_handlers{$id} || die "We have no handler to deal with '$id'\n";
110 150         314 return $self->$sub();
111              
112             }
113              
114             =head2 dump
115              
116             Takes a ref to an array or a hash or a number or string and pickles it.
117              
118             Structures may be nested.
119              
120             =cut
121              
122              
123              
124             sub dump {
125 30     30 1 6769 my $self = shift;
126 30         119 my $val = shift;
127              
128 30         80 my $type = $self->_type($val);
129 30   50     99 my $sub = $_set_handlers{$type} || die "We have no handler for value '$val' of type $type";
130            
131 30         70 return $self->$sub($val);
132            
133             #$self->_write($line);
134             #return $line;
135              
136             }
137              
138             sub _write {
139 0     0   0 my $self = shift;
140 0         0 my $line = shift;
141              
142 0         0 $self->{_fh}->put_bytes($line);
143             }
144              
145              
146             sub _get_char
147             {
148 200     200   244 my $self = shift;
149 200         583 return $self->{_fh}->get_bytes(1);
150              
151             }
152              
153             sub _put_char
154             {
155 100     100   115 my $self = shift;
156 100         153 my $char = shift;
157 100         299 $self->{_fh}->put_bytes($char);
158             }
159              
160              
161             sub _get_int {
162 72     72   109 my $self = shift;
163              
164 72         207 return $self->{_fh}->get_si32();
165              
166              
167             }
168              
169             sub _set_int {
170 36     36   45 my $self = shift;
171 36         44 my $val = shift;
172              
173 36         61 $self->_put_char('i');
174 36         580 $self->{_fh}->put_si32($val);
175             }
176              
177             sub _get_long {
178 0     0   0 my $self = shift;
179              
180 0         0 return 0;
181              
182              
183             }
184              
185             sub _get_float {
186 8     8   15 my $self = shift;
187 8         36 my $size = $self->{_fh}->get_ui8();
188 8         324 return $self->{_fh}->get_bytes($size);
189              
190             }
191              
192             sub _set_float {
193 4     4   12 my $self = shift;
194 4         6 my $val = shift;
195              
196 4         13 $self->_put_char('f');
197 4         144 $self->{_fh}->put_ui8(length($val));
198 4         162 $self->{_fh}->put_bytes($val);
199              
200             }
201              
202             sub _get_string {
203 74     74   99 my $self = shift;
204 74         717 my $size = $self->{_fh}->get_ui32();
205            
206 74 50       3249 if ($size>0) {
207 74         211 return $self->{_fh}->get_bytes($size);
208             } else {
209 0         0 return "";
210             }
211             }
212              
213             sub _set_string {
214 37     37   44 my $self = shift;
215 37         45 my $val = shift;
216 37         87 $self->_put_char('s');
217 37         773 $self->{_fh}->put_ui32(length $val);
218 37         931 $self->{_fh}->put_bytes($val);
219             }
220              
221             sub _get_list {
222 24     24   29 my $self = shift;
223 24         76 my $n = $self->{_fh}->get_ui32();
224 24         728 my @return;
225 24         59 foreach (1..$n) {
226 90         1860 push @return, $self->load();
227             }
228              
229 24         1269 return \@return;
230             }
231              
232             sub _set_mylist {
233 12     12   16 my $self = shift;
234 12         13 my $arr = shift;
235            
236 12         23 $self->_put_char('[');
237 12         255 $self->{_fh}->put_ui32(scalar @$arr);
238 12         278 foreach my $val (@$arr) {
239 45         661 my $type = $self->_type($val);
240 45   50     120 my $sub = $_set_handlers{$type} || die "We have no handler for value '$val' of type '$type'\n";
241 45         84 $self->$sub($val);
242             }
243 12         196 return 1;
244             }
245              
246              
247             sub _get_dict {
248 10     10   12 my $self = shift;
249              
250 10         12 my %hash;
251 10         11 while (1) {
252 30         56 my $id = $self->_get_char();
253 30 50 33     599 return undef if (!defined $id or $id eq "");
254 30 100       62 last if $id eq '0';
255              
256 20   50     50 my $sub = $_get_handlers{$id} || die "We have no handler to deal wwith '$id'\n";
257 20         33 my $key = $self->$sub();
258            
259 20         415 $id = $self->_get_char();
260 20   50     362 $sub = $_get_handlers{$id} || die "We have no handler to deal wwith '$id'\n";
261 20         34 my $value = $self->$sub();
262            
263 20         422 $hash{$key} = $value;
264              
265             }
266              
267 10         62 return \%hash;
268             }
269              
270              
271             sub _set_dict {
272 5     5   8 my $self = shift;
273 5         5 my $hash = shift;
274              
275 5         10 $self->_put_char('{');
276 5         120 foreach my $key (keys %$hash) {
277 10         136 my $ktype = $self->_type($key);
278 10   50     27 my $ksub = $_set_handlers{$ktype} || die "We have no handler for key '$key' of type '$ktype'\n";
279 10         19 $self->$ksub($key);
280              
281 10         172 my $val = $hash->{$key};
282 10         21 my $vtype = $self->_type($val);
283 10   50     26 my $vsub = $_set_handlers{$vtype} || die "We have no handler for value '$val' of type '$vtype'\n";
284 10         20 $self->$vsub($val);
285              
286             }
287 5         82 $self->_put_char('0');
288              
289             }
290              
291             sub _get_none {
292 0     0   0 return Python::Serialise::None->new();
293             }
294              
295             sub _set_none {
296 0     0   0 my $self = shift;
297 0         0 $self->_put_char('N');
298             }
299              
300             sub _get_complex {
301 2     2   3 my $self = shift;
302              
303 2         10 my $rsize = $self->{_fh}->get_ui8();
304 2         67 my $real = $self->{_fh}->get_bytes($rsize);
305              
306 2         48 my $isize = $self->{_fh}->get_ui8();
307 2         56 my $imag = $self->{_fh}->get_bytes($isize);
308              
309 2         50 my $comp = Math::Complex->new($real, $imag);
310 2         283 $comp->display_format('cartesian');
311              
312 2         54 return $comp;
313             }
314              
315             sub _set_complex {
316 1     1   2 my $self = shift;
317 1         2 my $comp = shift;
318            
319 1         23 my $real = Re($comp);
320 1         15 my $imag = Im($comp);
321              
322 1         13 $self->_put_char('x');
323 1         46 $self->{_fh}->put_ui8(length $real);
324 1         52 $self->{_fh}->put_bytes($real);
325              
326 1         17 $self->{_fh}->put_ui8(length $imag);
327 1         24 $self->{_fh}->put_bytes($imag);
328              
329             }
330              
331             =head2 close
332              
333             Closes the current file.
334              
335             =cut
336              
337             sub close {
338 27     27 1 7469 my $self = shift;
339 27         124 $self->{_fh}->close();
340             }
341              
342              
343             sub _type {
344 95     95   287 my $self = shift;
345 95         147 my $val = shift;
346              
347 95 50       256 return "NONE" unless defined $val;
348              
349 95         138 my $ref = ref $val;
350 95 50 33     623 return "NONE" if defined $ref and UNIVERSAL::isa($val,'Python::Serialise::None');
351 95 100 66     478 return "COMPLEX" if defined $ref and UNIVERSAL::isa($val,'Math::Complex');
352              
353 94 100 66     412 return $ref if defined $ref && $ref ne "";
354              
355 77 100       396 return "INTEGER" if ($val =~ /^[+-]?\d+$/);
356 41 100       161 return "FLOAT" if ($val =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/);
357 37         78 return "STRING";
358              
359             }
360              
361             =head1 NOTES
362              
363             =head2 Complex numbers
364              
365             Python has inbuilt support form complex numbers whilst Perl
366             provides it through the core module C. Unserialising
367             a Python complex number will return a C object
368             and, as you'd expect, serialising something that ISA C
369             will result in a serialised Python complex number.
370              
371             =head2 None
372              
373             Python has C objects, similar to Perl's C.
374              
375             Because I indictaes "no more objects" by returning C
376             we have to return C objects. However dump can
377             take C and serialise it as a C object.
378              
379             =cut
380              
381             =head1 BUGS
382              
383             Much less than my C module because this is a
384             I saner file format.
385              
386             =over 4
387              
388             =item Tests for None
389              
390             I can't think of a nice elegant way of doing tests at the moment.
391              
392             I'm sure I will soon.
393              
394             =item Longs
395              
396             There's no support for longs. I've figured out how to write them in
397             Python but I just can't seem to extract them properly.
398              
399             =item Unicode
400              
401             Not an itch that needs scratching at the moment so there's no support.
402              
403             =item Code
404              
405             Ditto
406              
407             =back
408              
409             =head1 ALTERNATIVES
410              
411             You could always dump the data structure out as YAML in Python
412             and then read it back in with YAML in Perl.
413              
414             I also may look into wrapping the Python source code file in XS.
415              
416             =head1 AUTHOR
417              
418             Simon Wistow
419              
420             =head1 COPYRIGHT
421              
422             (c) 2003 Simon Wistow
423              
424             Distributed under the same terms as Perl itself.
425              
426             This software is under no warranty and will probably ruin your life,
427             kill your friends, burn your house and bring about the apocalypse.
428              
429             =head1 SEE ALSO
430              
431             http://www.python.org, L, L, L
432             and the RESOURCES file in this distribution.
433              
434             =cut
435              
436              
437              
438             package Python::Serialise::None;
439              
440             sub new {
441 0     0     my $class = shift;
442 0           return bless {}, $class;
443             }
444              
445             1;