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