File Coverage

blib/lib/Jar/Manifest.pm
Criterion Covered Total %
statement 108 110 98.1
branch 16 30 53.3
condition 4 10 40.0
subroutine 15 15 100.0
pod 2 2 100.0
total 145 167 86.8


line stmt bran cond sub pod time code
1             package Jar::Manifest;
2              
3             #######################
4             # LOAD MODULES
5             #######################
6 2     2   22260 use strict;
  2         4  
  2         82  
7 2     2   9 use warnings FATAL => 'all';
  2         4  
  2         85  
8 2     2   20 use Carp qw(croak carp);
  2         3  
  2         191  
9              
10             #######################
11             # VERSION
12             #######################
13             our $VERSION = '0.04';
14              
15             #######################
16             # EXPORT
17             #######################
18 2     2   10 use base qw(Exporter);
  2         6  
  2         300  
19             our (@EXPORT_OK);
20             @EXPORT_OK = qw(Dump Load);
21              
22             #######################
23             # LOAD CPAN MODULES
24             #######################
25 2     2   2028 use Encode qw();
  2         25206  
  2         51  
26 2     2   1639 use Text::Wrap qw();
  2         6377  
  2         3317  
27              
28             #######################
29             # READ MANIFEST
30             #######################
31             sub Load {
32 1     1 1 28 my $manifest = {
33             main => {}, # Main Attributes
34             entries => [], # Manifest entries
35             };
36              
37 1         5 foreach my $para ( _split_to_paras(@_) ) {
38 2         4 my $isa_entry = 0;
39 2         3 my %h;
40 2 100       19 $isa_entry = 1
41             if ( lc( ( split( /\n+/, $para ) )[0] ) =~ m{^\s*name}xi );
42              
43 2         11 foreach my $line ( split( /\n+/, $para ) ) {
44              
45 6 50       29 next unless ( $line =~ m{.+:.+} );
46 6         27 my ( $k, $v ) = map { _trim($_) } split( /\s*:\s+/, $line );
  12         22  
47 6 50 33     52 next unless ( defined $k and defined $v );
48 6 50 33     38 next if ( ( $k =~ m{^\s*$} ) or ( $v =~ m{^\s*$} ) );
49 6 50       14 if ( defined $h{$k} ) {
50              
51             # Attribute names cannot be repeated within a section
52 0         0 croak "Found duplicate attribute: $k\n";
53             }
54              
55 6         17 $h{$k} = $v;
56             } ## end foreach my $line ( split( /\n+/...))
57              
58 2 100       6 if ($isa_entry) {
59 1         3 push @{ $manifest->{entries} }, \%h;
  1         4  
60             }
61             else {
62 1         1 $manifest->{main} = { %{ $manifest->{main} }, %h };
  1         9  
63             }
64             } ## end foreach my $para ( _split_to_paras...)
65              
66 1         4 return $manifest;
67             } ## end sub Load
68              
69             #######################
70             # WRITE MANIFEST
71             #######################
72             sub Dump {
73 1     1 1 1938 my ($in) = @_;
74 1 50       9 croak "Hash ref expected" unless ( ref $in eq 'HASH' );
75              
76 1   50     11 my $manifest = {
      50        
77             main => $in->{main} || {},
78             entries => $in->{entries} || [],
79             };
80              
81 1         2 my $str = q();
82              
83             # Manifest-Version is required!
84 1 50       5 if ( not defined $manifest->{main}->{'Manifest-Version'} ) {
85 0         0 croak "Manifest-Version is not provided!\n";
86             }
87              
88             # Process Main
89 1         3 foreach my $main_attr ( _sort_attr( keys %{ $manifest->{main} } ) ) {
  1         7  
90 2         264 $main_attr = _trim($main_attr);
91 2         7 _validate_attr($main_attr);
92 2         10 $str
93             .= _wrap_line(
94             "${main_attr}: " . _clean_val( $manifest->{main}->{$main_attr} ) )
95             . "\n";
96             } ## end foreach my $main_attr ( _sort_attr...)
97              
98             # Process entries
99 1         90 foreach my $entry ( @{ $manifest->{entries} } ) {
  1         4  
100              
101             # Get Name
102 1         3 my ($name_attr) = grep { /^name$/xi } keys %{$entry};
  4         19  
  1         3  
103 1         3 $name_attr = _trim($name_attr);
104 1 50       5 $name_attr || croak "Missing 'Name' attribute in entry";
105 1         2 _validate_attr($name_attr);
106 1         5 $str
107             .= "\n"
108             . _wrap_line(
109             "${name_attr}: " . _clean_val( $entry->{$name_attr} ) )
110             . "\n";
111              
112             # Process others
113 1         90 foreach my $entry_attr (
  4         23  
114             _sort_attr(
115 1         4 grep { !/$name_attr/ }
116             keys %{$entry}
117             )
118             )
119             {
120 3         190 $entry_attr = _trim($entry_attr);
121 3         8 _validate_attr($entry_attr);
122 3         12 $str
123             .= _wrap_line(
124             "${entry_attr}: " . _clean_val( $entry->{$entry_attr} ) )
125             . "\n";
126             } ## end foreach my $entry_attr ( _sort_attr...)
127             } ## end foreach my $entry ( @{ $manifest...})
128              
129             # Append 2 new lines at EOF
130 1         154 $str .= "\n\n";
131              
132             # Done
133 1         7 return $str;
134             } ## end sub Dump
135              
136             #######################
137             # INTERNAL HELPERS
138             #######################
139              
140             # Split to paragraphs
141             sub _split_to_paras {
142 1     1   4 my $lines = join( '', @_ );
143 1         6 $lines = _fix_eol($lines);
144 1         2 my @paras;
145 1         15 foreach (
146             split(
147             /(?:\n\s*){2,}/, # Two or more new lines
148             $lines
149             )
150             )
151             {
152 2         62 $_ =~ s{\n+}{\n}gx; # Consolidate new lines
153 2         9 $_ =~ s{\n\s}{}gx; # Join multiline values
154 2         6 push @paras, $_; # Save
155             } ## end foreach ( split( /(?:\n\s*){2,}/...))
156 1         4 return @paras;
157             } ## end sub _split_to_paras
158              
159             # Trim
160             sub _trim {
161 24     24   34 my ($val) = @_;
162 24 50       54 return unless defined $val;
163 24         47 $val =~ s{^\s+}{}xi;
164 24         36 $val =~ s{\s+$}{}xi;
165 24         54 return $val;
166             } ## end sub _trim
167              
168             # Correct EOL
169             sub _fix_eol {
170 7     7   11 my ($val) = @_;
171 7 50       21 return unless defined $val;
172 7         13 $val =~ s{\r\n}{\n}mgxi;
173 7         13 return $val;
174             } ## end sub _fix_eol
175              
176             # Validate Attribute
177             sub _validate_attr {
178 6     6   8 my ($attr) = @_;
179              
180 6 50       27 croak
181             "Attributes can contain only alphanumeric, '-' or '_' characters : $attr"
182             unless ( $attr =~ m{^[-0-9a-zA-Z_]+$} );
183              
184 6 50       21 croak "Attribute must contain at least one alphanumeric character : $attr"
185             unless ( $attr =~ m{[a-zA-Z0-9]+} );
186              
187 6 50       90 croak "Attribute length exceeds allowed value of 70 : $attr"
188             if ( length($attr) > 70 );
189              
190 6         10 return 1;
191             } ## end sub _validate_attr
192              
193             # Clean Value
194             sub _clean_val {
195 6     6   9 my ($val) = @_;
196              
197             # Get rid of line breaks
198 6         18 $val = _fix_eol($val);
199 6         12 $val =~ s{\n}{}gix;
200              
201             # Trim
202 6         11 $val = _trim($val);
203              
204             # Return encoded
205 6         21 return Encode::encode_utf8($val);
206             } ## end sub _clean_val
207              
208             # Sort Attributes
209             sub _sort_attr {
210 2     2   7 my @attr = @_;
211 3         12 @attr = sort {
212 2 0       9 ( grep { /-/ } $a ) <=> ( grep { /-/ } $b )
  3         6  
  3         17  
213             || lc($a) cmp lc($b)
214             } @attr;
215              
216             # Manifest-Version must be first, and in exactly that case
217 2         3 my @order;
218 2         5 push @order, grep { /Manifest\-Version/ } @attr;
  5         13  
219 2         4 push @order, grep { !/Manifest\-Version/ } @attr;
  5         13  
220 2         8 return @order;
221             } ## end sub _sort_attr
222              
223             # Wrap Line
224             sub _wrap_line {
225              
226             # Wrap settings
227 6     6   45 $Text::Wrap::unexpand = 0;
228 6         8 $Text::Wrap::tabstop = 4;
229 6         6 $Text::Wrap::columns = 72;
230 6         9 $Text::Wrap::break = '';
231 6         9 $Text::Wrap::huge = 'wrap';
232              
233             # Wrap
234 6         20 return Text::Wrap::wrap( "", " ", @_ );
235             } ## end sub _wrap_line
236              
237             #######################
238             1;
239              
240             __END__