File Coverage

blib/lib/SVN/Dumpfile/Node.pm
Criterion Covered Total %
statement 172 180 95.5
branch 41 72 56.9
condition 28 73 38.3
subroutine 38 38 100.0
pod 24 24 100.0
total 303 387 78.2


line stmt bran cond sub pod time code
1             ################################################################################
2             # Copyright (c) 2008 Martin Scharrer
3             # This is open source software under the GPL v3 or later.
4             #
5             # $Id: Node.pm 103 2008-10-14 21:11:21Z martin $
6             ################################################################################
7             package SVN::Dumpfile::Node;
8 11     11   196453 use strict;
  11         24  
  11         363  
9 11     11   58 use warnings;
  11         20  
  11         267  
10 11     11   275 use 5.008001;
  11         39  
  11         453  
11 11     11   6820 use SVN::Dumpfile::Node::Headers;
  11         32  
  11         406  
12 11     11   7391 use SVN::Dumpfile::Node::Properties;
  11         36  
  11         418  
13 11     11   7122 use SVN::Dumpfile::Node::Content;
  11         37  
  11         400  
14 11     11   62 use Digest::MD5 qw(md5_hex);
  11         18  
  11         715  
15 11     11   58 use Carp;
  11         16  
  11         605  
16 11     11   10418 use Date::Parse;
  11         110572  
  11         1585  
17 11     11   124 use Readonly;
  11         22  
  11         25049  
18             Readonly my $NL => chr(10);
19              
20             our $VERSION = do { '$Rev: 103 $' =~ /\$Rev: (\d+) \$/; '0.13' . ".$1" };
21              
22             sub new {
23 43     43 1 4390 my $arg = shift;
24 43   66     230 my $class = ref $arg || $arg;
25              
26 43         64 my %hasharg;
27 43         80 my $hargref = \%hasharg;
28              
29 43 50 33     236 if ( @_ == 1 && ref( $_[0] ) eq 'HASH' ) {
    50 0        
    0          
30 0         0 $hargref = shift;
31             }
32             elsif ( @_ % 2 == 0 ) {
33 43         94 %hasharg = @_;
34 43         69 $hargref = \%hasharg;
35             }
36             elsif ( @_ == 1 && !defined $_[0] ) {
37              
38             # Ignore single undef value
39             }
40             else {
41 0         0 carp
42             "${class}::new() awaits a hashref or even array with key/value pairs."
43             . "Ignoring all arguments.";
44             }
45              
46 43         296 my $self = bless {
47             headers => SVN::Dumpfile::Node::Headers->new( $hargref->{'headers'} ),
48             properties =>
49             SVN::Dumpfile::Node::Properties->new( $hargref->{'properties'} ),
50             contents => SVN::Dumpfile::Node::Content->new( $hargref->{'content'} ),
51             changed => scalar keys %$hargref,
52             }, $class;
53 43         205 return $self;
54             }
55              
56             sub newrev {
57 1     1 1 371 my $arg = shift;
58 1   33     7 my $class = ref $arg || $arg;
59 1         2 my $r;
60              
61 1 50 33     18 if ( @_ == 1 && ref( $_[0] ) eq 'HASH' ) {
    50 0        
    0          
62 0         0 $r = shift;
63             }
64             elsif ( @_ % 2 == 0 ) {
65 1         10 $r = {@_};
66             }
67             elsif ( @_ == 1 && !defined $_[0] ) {
68              
69             # Ignore single undef value
70             }
71             else {
72 0         0 carp
73             "${class}::newrev() awaits a hashref or even array with key/value pairs."
74             . "Ignoring all arguments.";
75             }
76              
77 1         2 my $strdate;
78 1 50 33     23 if ( !exists $r->{date}
    0 33        
79             || $r->{date} =~ /^\d+$/
80             || ( $strdate = str2time( $r->{date} ) ) )
81             {
82 1   33     308 my $time = $strdate || $r->{date} || time;
83 1         4 my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime($time);
84              
85             # '2006-05-10T13:31:40.486172Z'
86 1         13 $r->{date} = sprintf(
87             "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ",
88             $year + 1900,
89             $mon + 1, $mday, $hour, $min, $sec, 0
90             );
91             }
92             elsif ( $r->{date} !~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/ ) {
93 0         0 carp "Wrong format for new revision node given. Import of resulting "
94             . "dumpfile might break.";
95             }
96              
97 1 50 50     21 return $class->new(
      50        
98             headers => {
99             'Revision-number' => $r->{number} || 0,
100             'Prop-content-length' => 0,
101             'Content-length' => 0,
102             },
103             properties => {
104             'svn:author' => $r->{author} || $ENV{USER} || '(unknown)',
105             'svn:date' => $r->{date},
106             (exists $r->{'log'}) ? ('svn:log' => $r->{'log'}) : ()
107             }
108             );
109              
110             }
111              
112             sub content : lvalue {
113 1     1 1 5 my $self = shift;
114 1         60 $self->{contents}->value(@_);
115             }
116              
117             sub contents {
118 35     35 1 1861 my $self = shift;
119 35         181 return $self->{contents};
120             }
121              
122             sub has_contents {
123 4     4 1 8 my $self = shift;
124 4 50       16 return unless exists $self->{contents};
125 4         15 return $self->{contents}->exists;
126             }
127              
128             sub header : lvalue {
129 18     18 1 1842 my ( $self, $h, $value ) = @_;
130              
131 18 100       43 $self->{headers}->{$h} = $value
132             if defined $value;
133 18         106 $self->{headers}->{$h};
134             }
135              
136             sub headers {
137 24     24 1 81 my $self = shift;
138 24         100 return $self->{headers};
139             }
140              
141             sub has_header {
142 3     3 1 2096 my $self = shift;
143 3         7 my $header = shift;
144 3 50       10 return unless exists $self->{headers};
145 3         12 return exists $self->{headers}->{$header};
146             }
147              
148             sub has_headers {
149 20     20 1 24 my $self = shift;
150 20 50       56 return if not exists $self->{headers};
151 20         25 return scalar keys %{ $self->{headers} };
  20         78  
152             }
153              
154             sub property : lvalue {
155 7     7 1 15 my ( $self, $prop, $value ) = @_;
156 7 50       19 if ( @_ == 1 ) {
157 0         0 return $self->{properties}->{property};
158             }
159 7 50       14 $self->{properties}->{property}->{$prop} = $value
160             if defined $value;
161 7         39 $self->{properties}->{property}->{$prop};
162             }
163              
164             sub has_property {
165 4     4 1 8 my $self = shift;
166 4         7 my $prop = shift;
167             return
168 4 50 33     58 unless exists $self->{properties}
169             and exists $self->{properties}->{property};
170 4         20 return exists $self->{properties}->{property}->{$prop};
171             }
172              
173             sub properties {
174 12     12 1 45 my $self = shift;
175 12         53 return $self->{properties};
176             }
177              
178             sub has_properties {
179 2     2 1 2146 my $self = shift;
180             return
181 2 50 33     22 unless exists $self->{properties}
182             and exists $self->{properties}->{property};
183 2         3 return scalar keys %{ $self->{properties}->{property} };
  2         11  
184             }
185              
186             sub changed {
187 9     9 1 27 my $self = shift;
188 9         15 $self->{changed} = 1;
189 9         17 return;
190             }
191              
192             sub has_changed {
193 9     9 1 21 my $self = shift;
194 9         27 return $self->{changed};
195             }
196              
197             sub is_rev {
198 1     1 1 815 my $self = shift;
199 1         8 return exists $self->{headers}{'Revision-number'};
200             }
201              
202             sub revnum : lvalue {
203 1     1 1 2 my ( $self, $value ) = shift;
204 1 50       5 $self->{headers}{'Revision-number'} = $value
205             if defined $value;
206              
207 1         13 $self->{headers}{'Revision-number'};
208             }
209              
210             sub read {
211 29     29 1 69 my $self = shift;
212 29         35 my $fh = shift; # Filehandle to read
213 29         31 my $line;
214              
215 29 50 33     72 return unless defined $fh and eval { $fh->isa('IO::Handle') };
  29         183  
216              
217 29         62 my $header = $self->{'headers'};
218              
219 29         111 my $irs = IO::Handle->input_record_separator($NL);
220              
221 29         436 $self->{headers}->read($fh);
222 29 100       91 return if $fh->eof;
223              
224             # Get properties when they exist (but then they can be empty also!)
225 26 50       310 $self->{properties}->read( $fh, $header->{'Prop-content-length'} )
226             if exists $header->{'Prop-content-length'};
227              
228             # Get content
229 26 100       99 $self->{contents}->read( $fh, $header->{'Text-content-length'} )
230             if exists $header->{'Text-content-length'};
231              
232             # Save delimiter blank lines to be able to restore the input file exact
233 26         107 $self->{delim} = "";
234 26         28 my $c;
235 26   100     76 while ( $c = $fh->getc and $c eq $NL ) {
236 43         612 $self->{delim} .= $c;
237             }
238 26 100       407 $fh->ungetc( ord $c ) if defined $c;
239              
240 26         124 IO::Handle->input_record_separator($irs);
241 26         264 return 1;
242             }
243              
244             #################
245             ## Write node entry to filehandle
246              
247             sub write {
248 18     18 1 1837 my $self = shift; # Hash (as reference) with node to be written
249 18         22 my $fh = shift; # Filehandle to write to
250 18         18 my $ret = 1;
251              
252 18 50       42 croak "Given argument is not a valid file handle."
253             unless defined $fh;
254              
255 18         27 my $header = $self->{headers};
256              
257 18 50       55 return unless ( $header->number ); # skip if there is no header
258              
259 18 100       49 $self->recalc_headers if $self->{changed};
260              
261 18   33     74 $ret &&= $self->{headers}->write($fh);
262              
263             # Properties
264 18 50 33     427 $ret &&= $self->{properties}->write($fh)
      33        
265             if ( exists $header->{'Prop-content-length'}
266             and $header->{'Prop-content-length'} > 0 );
267              
268             # Content
269 18 100 33     210 $ret &&= $self->{contents}->write($fh)
      66        
270             if ( exists $header->{'Text-content-length'}
271             and $header->{'Text-content-length'} > 0 );
272              
273 18 100 33     127 $ret &&= $fh->print( exists $self->{delim} ? $self->{delim} : $NL );
274 18         175 return $ret;
275             }
276              
277             sub as_string {
278 18     18 1 44 my $self = shift;
279 18 50       46 return '' unless ( $self->has_headers ); # skip if there are no header
280 18 50       54 $self->recalc_headers if $self->{changed};
281              
282 18   33     59 return ''
283             . $self->{headers}->as_string
284             . $NL
285             . $self->{properties}->as_string
286             . $self->{contents}->as_string
287             . ( $self->{delim} || $NL );
288             }
289              
290             sub recalc_headers {
291 17     17 1 25 my $self = shift;
292              
293 17         44 $self->recalc_textcontent_header;
294 17         40 $self->recalc_prop_header;
295              
296 17         25 $self->{changed} = 0;
297 17         24 return;
298             }
299              
300             #################
301             ## recalc_content_header - Recalculate 'Content-length' header
302             #####
303             # Depends on correct values in other headers.
304             # Will be called by other recalc-functions.
305              
306             sub recalc_content_header {
307 17     17 1 21 my $self = shift;
308 17         27 my $header = $self->{headers};
309 11     11   104 no warnings 'uninitialized';
  11         34  
  11         1763  
310              
311 17         32 my $header_existed = exists $header->{'Content-length'};
312              
313 17         39 $header->{'Content-length'}
314             = $header->{'Text-content-length'} + $header->{'Prop-content-length'};
315              
316 17 50 33     47 if ( $header->{'Content-length'} == 0 && !$header_existed ) {
317 0         0 delete $header->{'Content-length'};
318             }
319 17         25 return;
320             }
321              
322             #################
323             ## recalc_textcontent_header - Recalculate 'Text-content'* and dependend headers
324             #####
325              
326             sub recalc_textcontent_header {
327 11     11   74 use bytes;
  11         28  
  11         94  
328 11     11   324 no warnings 'uninitialized';
  11         28  
  11         2074  
329 17     17 1 30 my $self = shift;
330 17         27 my $header = $self->{headers};
331              
332 17         31 my $oldlength = $header->{'Text-content-length'};
333 17         77 my $header_existed = exists $header->{'Text-content-length'};
334              
335 17 50       93 my $length
336             = defined $self->{'contents'}
337             ? $self->{'contents'}->length
338             : 0;
339              
340 17 100 66     83 if ( !$header_existed && $length == 0 ) {
341 13         31 delete $header->{'Text-content-length'};
342 13         21 delete $header->{'Text-content-md5'};
343             }
344             else {
345 4         10 $header->{'Text-content-length'} = $length;
346 4         6 $header->{'Text-content-md5'} = md5_hex( ${ $self->{'contents'} } );
  4         43  
347             }
348              
349 17 50       53 $self->recalc_content_header
350             if ( $oldlength != $header->{'Text-content-length'} );
351 17         95 return;
352             }
353              
354             #################
355             ## recalc_prop_header - Recalculate 'Prop-content-length' and dependend headers
356             #####
357              
358             sub recalc_prop_header {
359 11     11   56 use bytes;
  11         23  
  11         61  
360 17     17 1 27 my $self = shift;
361 17         27 my $header = $self->{'headers'};
362 17         25 my $prop = $self->{'properties'};
363              
364             # Don't remove or create header unless necessary
365 17         25 my $header_existed = exists $header->{'Prop-content-length'};
366              
367             # Correct properties length:
368 17         51 $header->{'Prop-content-length'} = $prop->length;
369              
370 17 50 33     93 if ( !$header_existed && $header->{'Prop-content-length'} eq 10 ) {
371 0         0 delete $header->{'Prop-content-length'};
372             }
373              
374 17         39 $self->recalc_content_header;
375 17         24 return;
376             }
377              
378             1;
379             __END__