File Coverage

blib/lib/SVN/Dumpfile/Node/Properties.pm
Criterion Covered Total %
statement 173 187 92.5
branch 40 58 68.9
condition 17 30 56.6
subroutine 26 26 100.0
pod 16 16 100.0
total 272 317 85.8


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: Properties.pm 103 2008-10-14 21:11:21Z martin $
6             ################################################################################
7             package SVN::Dumpfile::Node::Properties;
8 12     12   57553 use IO::File;
  12         94223  
  12         1877  
9 12     12   100 use Carp;
  12         22  
  12         689  
10 12     12   72 use strict;
  12         28  
  12         351  
11 12     12   73 use warnings;
  12         36  
  12         413  
12 12     12   995 use Readonly;
  12         3292  
  12         16846  
13             Readonly my $NL => chr(10);
14              
15             our $VERSION = do { '$Rev: 103 $' =~ /\$Rev: (\d+) \$/; '0.13' . ".$1" };
16              
17             sub new {
18 52     52 1 4449 my $class = shift;
19 52         338 my $self = bless {
20             order => [],
21             property => {},
22             deleted => [],
23             unknown => [],
24             }, $class;
25              
26 52 100 100     726 if ( @_ == 1 && ref $_[0] eq 'HASH' ) {
  2 100 100     10  
    100 66        
    100 66        
27 3         7 $self->{'property'} = { %{ $_[0] } };
  3         24  
28 3         8 @{ $self->{'order'} } = keys %{ $_[0] };
  3         10  
  3         19  
29             }
30             elsif ( @_ == 1 && ref $_[0] eq 'ARRAY' && @{ $_[0] } % 2 == 0 ) {
31 2         4 my $i = 0;
32 2         3 $self->{'property'} = { @{ $_[0] } };
  2         11  
33 2 100       7 @{ $self->{'order'} }
  12         29  
34 2         3 = map { $i++ % 2 ? () : $_ } @{ $_[0] }; # even entries only
  2         6  
35             }
36             elsif ( @_ % 2 == 0 ) {
37 5         9 my $i = 0;
38 5         17 $self->{'property'} = {@_};
39 5 100       12 @{ $self->{'order'} }
  6         15  
40 5         11 = map { $i++ % 2 ? () : $_ } @_; # even entries only
41             }
42             elsif ( @_ == 1 && !defined $_[0] ) {
43              
44             # Ignore single undef value
45             }
46             else {
47 1         185 croak ${class}
48             . '::new() awaits hashref or key/value pairs as arguments.';
49             }
50              
51 51         347 return $self;
52             }
53              
54             sub number {
55 12     12 1 1527 my $self = shift;
56 12         16 return scalar keys %{ $self->{property} };
  12         102  
57             }
58              
59             sub add {
60 41     41 1 68 my ( $self, $prop, $value, $position ) = @_;
61 41         63 my $order = $self->{order};
62              
63 41 100 66     117 if ( !defined $position || $position > @$order ) {
64 40         52 $position = @$order;
65             }
66              
67 41         75 my $existed = exists $self->{property}{$prop};
68 41         92 $self->{property}{$prop} = $value;
69 41 50       118 splice @$order, $position, 0, $prop
70             if not $existed;
71 41         139 return $self;
72             }
73              
74             sub del {
75 3     3 1 5 my $self = shift;
76 3         4 my $prop = shift;
77              
78 3 50       12 return unless exists $self->{property}{$prop};
79 3         7 delete $self->{property}{$prop};
80              
81 3         5 my $order = $self->{order};
82 3         9 for my $i ( 0 .. $#$order ) {
83 11 100       28 if ( $order->[$i] eq $prop ) {
84 2         5 splice @$order, $i, 1;
85 2         5 last;
86             }
87             }
88 3         7 return $self;
89             }
90              
91             sub mark_deleted {
92 2     2 1 3 my $self = shift;
93 2         6 my $prop = shift;
94              
95 2         5 $self->del($prop);
96 2         4 push @{ $self->{deleted} }, $prop;
  2         5  
97 2         8 return $self;
98             }
99              
100             sub unmark_deleted {
101 1     1 1 3 my $self = shift;
102 1         2 my $prop = shift;
103              
104 1         3 my $deleted = $self->{deleted};
105 1         4 for my $i ( 0 .. $#$deleted ) {
106 2 100       9 if ( $deleted->[$i] eq $prop ) {
107 1         4 splice @$deleted, $i, 1;
108 1         3 last;
109             }
110             }
111 1         5 return $self;
112             }
113              
114             sub is_deleted {
115 3     3 1 6 my $self = shift;
116 3         6 my $prop = shift;
117 3 50       8 return unless defined $prop;
118              
119 3         5 foreach my $deleted ( @{ $self->{deleted} } ) {
  3         12  
120 4 100       22 return 1 if $deleted eq $prop;
121             }
122 1         5 return;
123             }
124              
125             sub list_deleted {
126 4     4 1 9 my $self = shift;
127              
128 4         6 return @{ $self->{deleted} };
  4         24  
129             }
130              
131             sub parse {
132 107     107 1 133 my $self = shift;
133 107         101 my $propstrref = shift; # String in SVN property format to parse
134              
135 107 50 33     428 return unless defined $propstrref and defined $$propstrref;
136 107 100       446 return unless ( $$propstrref =~ s/^([A-Z]) (\d+)$NL//o );
137 78         308 my ( $ident, $length ) = ( $1, $2 );
138              
139 78         146 my $entry
140             = substr( $$propstrref, 0, $length, '' ); # get key with length given by
141             # above line and replace it with an null-string
142 78         158 $$propstrref =~ s/^$NL//o; # delete trailing new-line
143              
144 78         345 return ( $ident, $entry );
145             }
146              
147             sub from_string {
148 29     29 1 41 my $self = shift;
149 29         40 my $propstr = shift; # String in SVN property format to parse
150 29         55 my $prophash = $self->{property}; # Hash reference to store properties
151 29         82 my $proporder = $self->{order}; # Array ref. to store order of properties
152              
153 29 50       59 return if not defined $propstr;
154 29         32 my @props;
155              
156             # Parse string and save all property entries in array
157 29         82 while ( my ( $ident, $entry ) = $self->parse( \$propstr ) ) {
158 78         295 push @props, [ $ident, $entry ];
159             }
160              
161 29         88 for ( my $i = 0; $i < $#props; $i++ ) {
162 39         41 my ( $ident, $entry ) = @{ $props[$i] };
  39         69  
163 39 50       74 if ( $ident eq 'K' ) {
    0          
164 39         38 my ( $ident2, $value ) = @{ $props[ ++$i ] };
  39         62  
165 39 50       87 if ( $ident2 eq 'V' ) {
166 39         97 $self->add( $entry, $value );
167             }
168             }
169             elsif ( $ident eq 'D' ) {
170 0         0 push @{ $self->{deleted} }, $entry;
  0         0  
171             }
172             else {
173 0         0 push @{ $self->{unknown} }, [ $ident, $entry ];
  0         0  
174 0         0 print STDERR "Error: Found unknown entry in property field:\n",
175             "------\n", "${ident}: $entry", "\n";
176             }
177             }
178              
179 29 50       136 if ( not $propstr =~ s/(?:PROPS-)?END$NL\Z//o ) {
180 0         0 print STDERR "Error at parsing properties at input line $.:",
181             "Couldn't understand '$propstr'.\n";
182 0         0 return 0;
183             }
184              
185 29         271 return scalar @props;
186             }
187              
188             sub read {
189 12     12   14217 use bytes;
  12         133  
  12         63  
190 26     26 1 35 my $self = shift;
191 26         37 my $fh = shift;
192 26         32 my $length = shift;
193 26         25 my $str;
194              
195 26         37 my $ret = eval { $fh->read( $str, $length ) };
  26         85  
196 26 50 33     275 return $ret unless defined $ret and $ret;
197              
198 26 100       72 return ( $self->from_string($str) ) ? $ret : undef;
199             }
200              
201             sub write {
202 18     18 1 23 my $self = shift;
203 18         18 my $fh = shift;
204              
205 18 0 33     19 unless ( eval { $fh->isa('IO::Handle') }
  18   33     84  
206             || ref $fh eq 'GLOB'
207             || ref \$fh eq 'GLOB' )
208             {
209 0         0 croak "Given argument is no valid file handle.";
210             }
211              
212 18         37 return $fh->print( $self->as_string );
213             }
214              
215             # Load properties from a file
216             sub load {
217 12     12   2094 use bytes;
  12         39  
  12         49  
218 2     2 1 21 my $self = shift;
219 2         3 my $fr = shift; # File handle or name
220 2         4 my $fh;
221             my $str;
222              
223 2 50       3 if ( eval { $fr->isa('IO::Handle') } ) {
  2         14  
224 0         0 $fh = $fr;
225             }
226             else {
227 2         14 $fh = IO::File->new( $fr, '<' );
228 2 50       214 return unless defined $fh;
229             }
230              
231 2         61 $str = join '', $fh->getlines;
232 2 50 33     128 return unless defined $str and $str ne '';
233              
234 2         6 return $self->from_string($str);
235             }
236              
237             # Save properties to a file
238             sub save {
239 12     12   1796 use bytes;
  12         24  
  12         58  
240 1     1 1 407 my $self = shift;
241 1         2 my $fr = shift; # File handle or name
242 1         2 my $fh;
243             my $str;
244              
245 1 50       2 if ( eval { $fr->isa('IO::Handle') } ) {
  1         11  
246 0         0 $fh = $fr;
247             }
248             else {
249 1         5 $fh = IO::File->new( $fr, '>' );
250 1 50       145 return unless defined $fh;
251             }
252              
253 1         3 return $fh->print( $self->as_string(1) );
254             }
255              
256             sub length {
257 12     12   1489 use bytes;
  12         26  
  12         50  
258 17     17 1 51 return bytes::length shift->as_string;
259             }
260              
261             # Returns formatted string in SVN property format
262             sub as_string {
263 12     12   665 use bytes;
  12         20  
  12         65  
264 65     65 1 170 my $self = shift;
265 65         71 my $forfile = shift; # bool
266 65         94 my $prophash = $self->{property}; # Hash reference to store properties
267 65         77 my $proporder = $self->{order}; # Array ref. to store order of properties
268 65         91 my $propstr = ''; # Return string
269              
270             # Create check-hash
271 65         143 my %prop_notprinted = map { $_ => 0 } ( keys %$prophash );
  84         200  
272              
273             # Print properties by given order
274 65         129 foreach my $key (@$proporder) {
275 84         231 $propstr
276             .= 'K '
277             . bytes::length($key)
278             . $NL
279             . $key
280             . $NL . 'V '
281             . bytes::length( $prophash->{$key} )
282             . $NL
283             . $prophash->{$key}
284             . $NL;
285 84         7476 delete $prop_notprinted{$key}; # printed so delete from check-hash
286             }
287              
288             # Print now all remaining properties (if any)
289 65         192 foreach my $key ( sort keys %prop_notprinted ) {
290 0         0 $propstr
291             .= 'K '
292             . bytes::length($key)
293             . $NL
294             . $key
295             . $NL . 'V '
296             . bytes::length( $prophash->{$key} )
297             . $NL
298             . $prophash->{$key}
299             . $NL;
300             }
301              
302             # Print list of deleted properties
303 65         89 foreach my $entry ( @{ $self->{deleted} } ) {
  65         136  
304 0         0 $propstr .= 'D ' . bytes::length($entry) . $NL . $entry . $NL;
305             }
306 65         74 foreach my $ref ( @{ $self->{unknown} } ) {
  65         119  
307 0         0 my ( $ident, $entry ) = @$ref;
308 0         0 $propstr .= "$ident " . bytes::length($entry) . $NL . $entry . $NL;
309             }
310              
311 65 100       239 $propstr .= ($forfile) ? "END$NL" : "PROPS-END$NL";
312 65         526 return $propstr;
313             }
314              
315             # Alias:
316             *to_string = \&as_string;
317              
318             1;
319             __END__