File Coverage

blib/lib/WWW/AUR/PKGBUILD.pm
Criterion Covered Total %
statement 127 137 92.7
branch 30 40 75.0
condition 6 7 85.7
subroutine 19 19 100.0
pod 2 3 66.6
total 184 206 89.3


line stmt bran cond sub pod time code
1             package WWW::AUR::PKGBUILD;
2              
3 8     8   35529 use warnings 'FATAL' => 'all';
  8         12  
  8         426  
4 8     8   37 use strict;
  8         12  
  8         296  
5              
6 8     8   35 use Fcntl qw(SEEK_SET);
  8         11  
  8         526  
7 8     8   49 use Carp qw();
  8         11  
  8         15508  
8              
9             my @ARRAY_FIELDS = qw{ license source noextract
10             md5sums sha1sums sha256sums sha384sums sha512sums
11             groups arch backup depends makedepends conflicts
12             provides replaces options };
13             # We cannot auto-split optdepends because spaces are allowed.
14              
15             sub new
16             {
17 6     6 0 3877 my $class = shift;
18 6         24 my $self = bless {}, $class;
19              
20 6 50       24 if ( @_ ) { $self->read( @_ ); }
  6         28  
21 6         28 return $self;
22             }
23              
24             #---HELPER FUNCTION---
25             sub _unquote_bash
26             {
27 180     180   1926 my ($bashtext, $start, $expander) = @_;
28 180         129 my $elem;
29              
30 180   100 2   359 $expander ||= sub { shift };
  2         7  
31 180   100     295 $start ||= 0;
32 180         243 ( pos $bashtext ) = $start;
33              
34             # Extract the values of a bash array...
35 180 100       412 if ( $bashtext =~ / \G [(] ([^)]*) [)] /gcx ) {
36 38         61 my $arrtext = $1;
37 38         28 my @result;
38              
39             ARRAY_LOOP:
40 38         29 while ( 1 ) {
41 46         79 my ($elem, $elem_end) = _unquote_bash( $arrtext,
42             pos $arrtext,
43             $expander );
44 46 100       110 push @result, $elem if $elem;
45              
46             # There should only be spaces leftover.
47 46         60 ( pos $arrtext ) = $elem_end;
48 46 100 66     148 last ARRAY_LOOP if ( $elem_end >= length $arrtext ||
49             $arrtext !~ /\G\s+/g );
50             }
51              
52             # Arrays are special, we do not recurse after we find one.
53 38         87 return \@result, pos $bashtext;
54             }
55              
56             # The rest is for string "parsing"...
57              
58             # Single quoted strings cannot escape the quote (')...
59 142 100       463 if ( $bashtext =~ /\G'([^']*)'/gc ) {
    100          
    100          
60 27         47 $elem = $1;
61             }
62             # Double quoted strings can...
63             elsif ( $bashtext =~ /\G"/gc ) {
64 5         10 my $beg = pos $bashtext;
65             # Skip past escaped double-quotes and non-double-quote chars.
66 5         223 while ( $bashtext =~ / \G (?: \\" | [^"] ) /gcx ) { ; }
67              
68 5         31 $elem = substr $bashtext, $beg, ( pos $bashtext ) - $beg;
69 5         15 $elem = $expander->( $elem );
70 5         14 ++( pos $bashtext ); # skip the closing "
71             }
72             # Otherwise regular words are treated as one element...
73             elsif ( $bashtext =~ /\G([^ \n\t'"]+)/gc ) {
74 36         59 $elem = $expander->( $1 );
75             }
76             # If none of the above matches, then we stop recursion.
77 74         151 else { return q{}, $start; }
78              
79             # We recurse in order to concatenate adjacent strings.
80 68         168 my ( $next_elem, $next_end ) = _unquote_bash( $bashtext,
81             pos $bashtext,
82             $expander );
83 68         211 return ( $elem . $next_elem, $next_end );
84             }
85              
86             # Perform the simplest parameter expansion possible.
87             sub _expand_bash
88             {
89 39     39   64 my ($bashstr, $fields_ref) = @_;
90              
91             my $expand_field = sub {
92 6     6   9 my $name = shift;
93 6 50       29 return $fields_ref->{ $name } if defined $fields_ref->{ $name };
94 0         0 return qq{\$$name};
95             # TODO: error reporting?
96 39         124 };
97              
98 39         63 $bashstr =~ s{ \$ ([\w_]+) }
99 6         13 { $expand_field->( $1 ) }gex;
100              
101             # TODO: check for special expansion modifiers
102 39         46 $bashstr =~ s( \$ \{ ([^}]+) \} )
103 0         0 ( $expand_field->( $1 ) )gex;
104              
105 39         134 return $bashstr;
106             }
107              
108             #---HELPER FUNCTION---
109             sub _depstr_to_hash
110             {
111 11     11   14 my ($depstr) = @_;
112 11         66 my ($pkg, $cmp, $ver) = $depstr =~ / \A ([^=<>]+)
113             (?: ([=<>]=?)
114             (.*) )? \z/xms;
115              
116 11 50       23 Carp::confess "Failed to parse depend string: $_" unless $pkg;
117              
118 11         51 return +{ 'pkg' => $pkg, 'cmp' => $cmp,
119             'ver' => $ver, 'str' => $depstr };
120             }
121              
122             sub _provides_to_hash
123             {
124 1     1   1 my ($provstr) = @_;
125 1         4 my ($pkg, $ver) = $provstr =~ / \A ([^=]+)
126             (?: = (.*))?
127             /xms;
128 1 50       3 Carp::confess "Failed to parse provides string: $_" unless $pkg;
129 1         4 return +{ 'pkg' => $pkg, 'ver' => $ver, 'str' => $provstr };
130             }
131              
132             #---HELPER FUNCTION---
133             sub _pkgbuild_fields
134             {
135 6     6   9 my ($pbtext) = @_;
136              
137 6         10 my %pbfields;
138             my $expander = sub {
139 39     39   69 _expand_bash( shift, \%pbfields )
140 6         30 };
141              
142 6         131 while ( $pbtext =~ / \G .*? \n? ^ ([\w_]+) = /gxms ) {
143 63         90 my $name = $1;
144 63         118 my ( $value, $endpos ) = _unquote_bash( $pbtext,
145             pos $pbtext,
146             $expander );
147 63         129 $pbfields{ $name } = $value;
148 63         337 ( pos $pbtext ) = $endpos;
149             }
150              
151             # Split arrays at whitespace for poorly made PKGBUILDs...
152             # also ensures each field has an arrayref.
153             ARRAY_LOOP:
154 6         16 for my $arrkey ( @ARRAY_FIELDS ) {
155 102 100       165 unless ( $pbfields{ $arrkey } ) {
156 65         90 $pbfields{ $arrkey } = [];
157 65         74 next ARRAY_LOOP;
158             }
159              
160 37         37 my $val_ref = $pbfields{ $arrkey };
161              
162             # Force the value into being an array...
163 37 50       56 $val_ref = [ $val_ref ] unless ref $val_ref;
164              
165             # Try to filter out common problems people have with defining arrays.
166             # 1) trailing \'s
167             # 2) commented array items (generally a complete line is commented)
168             # 3) depends=('foo=1 bar<2 baz>=3') (a string separated by spaces)
169             # 4) depends=('turbojpegipp >=1.11') (only in the turbovnc-bin pkg)
170             # (These should be done by the parser, eventually)
171 39         77 $val_ref = [ grep { $_ ne q{\\} } # *1
  38         64  
172 38         91 map { split } # *3
173 38         51 map { s{ \A (\w+) \s+
174 38         51 ([<>=]{1,2}\d+) }{$1$2}x; $_ } # *4
175 38         42 map { s/\A\s+//; s/\s+\z//; $_ } # trim ws
  38         65  
  38         43  
176 37         48 grep { length } map { s/\#.*//; $_ } # *2
  38         43  
  38         50  
177             @$val_ref ];
178              
179 37         72 $pbfields{ $arrkey } = $val_ref;
180             }
181              
182             # optdepends are special, we should only split on newlines
183 6 100       19 if ( $pbfields{'optdepends'} ) {
184 1         3 my $optdeps = $pbfields{'optdepends'};
185 1 50       3 $optdeps = [ $optdeps ] unless ref $optdeps;
186              
187             # Remember stupid \'s at the end of lines
188 0         0 $optdeps = [ grep { length } map { s/\#.*//; $_ }
  0         0  
  0         0  
  0         0  
189 0         0 grep { $_ ne q{\\} }
190 1         4 map { s/\A\s+//; s/\s+\z//; $_ }
  0         0  
  0         0  
191             @$optdeps ];
192 1         2 $pbfields{'optdepends'} = $optdeps;
193             }
194             else {
195 5         14 $pbfields{'optdepends'} = [];
196             }
197              
198             # Convert all depends into hash references...
199             VERSPEC_LOOP:
200 6         13 for my $depkey ( qw/ makedepends depends conflicts / ) {
201 18         18 my @deps = @{ $pbfields{ $depkey } };
  18         40  
202 18 100       46 next VERSPEC_LOOP unless @deps;
203              
204 8         11 eval {
205 8         12 $pbfields{ $depkey } = [ map { _depstr_to_hash($_) } @deps ];
  11         25  
206             };
207 8 50       26 if ( $@ ) {
208 0         0 die qq{Error with "$depkey" field:\n$@};
209             }
210             }
211              
212             # Provides has no comparison operator and may have no version...
213 6 50       20 if ( $pbfields{'provides'} ) {
214 1         3 $pbfields{'provides'} =
215 6         8 [ map { _provides_to_hash($_) } @{$pbfields{'provides'}} ];
  6         21  
216             }
217              
218 6         120 return %pbfields;
219             }
220              
221             #---HELPER FUNCTION---
222             sub _slurp
223             {
224 1     1   2 my ($fh) = @_;
225              
226             # Make sure we start reading from the beginning of the file...
227 1 50       6 seek $fh, SEEK_SET, 0 or die "seek: $!";
228              
229 1         3 local $/;
230 1         19 return <$fh>;
231             }
232              
233             sub read
234             {
235 6     6 1 9 my $self = shift;
236 6 100       38 $self->{'text'} = ( ref $_[0] eq 'GLOB' ? _slurp( shift ) : shift );
237              
238 6         21 my %pbfields = _pkgbuild_fields( $self->{'text'} );
239 6         28 $self->{'fields'} = \%pbfields;
240 6         11 return %pbfields;
241             }
242              
243             sub fields
244             {
245 2     2 1 6 my ($self) = @_;
246 2         2 return %{ $self->{'fields'} }
  2         19  
247             }
248              
249             sub _def_field_acc
250             {
251 200     200   167 my ($name) = @_;
252              
253 8     8   71 no strict 'refs';
  8         14  
  8         1099  
254 200         670 *{ $name } = sub {
255 4     4   1435 my ($self) = @_;
256 4         14 my $val = $self->{'fields'}{$name};
257              
258 4 50       12 return q{} unless defined $val;
259 4         23 return $val;
260             }
261 200         381 }
262              
263             _def_field_acc( $_ ) for qw{ pkgname pkgver pkgdesc pkgrel url
264             license install changelog source
265             noextract md5sums sha1sums sha256sums
266             sha384sums sha512sums groups arch
267             backup depends makedepends optdepends
268             conflicts provides replaces options };
269              
270             1;
271              
272             __END__