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   25201 use warnings 'FATAL' => 'all';
  8         9  
  8         283  
4 8     8   40 use strict;
  8         10  
  8         139  
5              
6 8     8   23 use Fcntl qw(SEEK_SET);
  8         7  
  8         274  
7 8     8   26 use Carp qw();
  8         7  
  8         12858  
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 2807 my $class = shift;
18 6         15 my $self = bless {}, $class;
19              
20 6 50       18 if ( @_ ) { $self->read( @_ ); }
  6         21  
21 6         15 return $self;
22             }
23              
24             #---HELPER FUNCTION---
25             sub _unquote_bash
26             {
27 174     174   759 my ($bashtext, $start, $expander) = @_;
28 174         112 my $elem;
29              
30 174   100 2   218 $expander ||= sub { shift };
  2         4  
31 174   100     229 $start ||= 0;
32 174         177 ( pos $bashtext ) = $start;
33              
34             # Extract the values of a bash array...
35 174 100       312 if ( $bashtext =~ / \G [(] ([^)]*) [)] /gcx ) {
36 36         40 my $arrtext = $1;
37 36         26 my @result;
38              
39             ARRAY_LOOP:
40 36         22 while ( 1 ) {
41 44         53 my ($elem, $elem_end) = _unquote_bash( $arrtext,
42             pos $arrtext,
43             $expander );
44 44 100       68 push @result, $elem if $elem;
45              
46             # There should only be spaces leftover.
47 44         44 ( pos $arrtext ) = $elem_end;
48 44 100 66     102 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 36         53 return \@result, pos $bashtext;
54             }
55              
56             # The rest is for string "parsing"...
57              
58             # Single quoted strings cannot escape the quote (')...
59 138 100       302 if ( $bashtext =~ /\G'([^']*)'/gc ) {
    100          
    100          
60 41         53 $elem = $1;
61             }
62             # Double quoted strings can...
63             elsif ( $bashtext =~ /\G"/gc ) {
64 7         9 my $beg = pos $bashtext;
65             # Skip past escaped double-quotes and non-double-quote chars.
66 7         227 while ( $bashtext =~ / \G (?: \\" | [^"] ) /gcx ) { ; }
67              
68 7         28 $elem = substr $bashtext, $beg, ( pos $bashtext ) - $beg;
69 7         11 $elem = $expander->( $elem );
70 7         11 ++( pos $bashtext ); # skip the closing "
71             }
72             # Otherwise regular words are treated as one element...
73             elsif ( $bashtext =~ /\G([^ \n\t'"]+)/gc ) {
74 18         25 $elem = $expander->( $1 );
75             }
76             # If none of the above matches, then we stop recursion.
77 72         106 else { return q{}, $start; }
78              
79             # We recurse in order to concatenate adjacent strings.
80 66         99 my ( $next_elem, $next_end ) = _unquote_bash( $bashtext,
81             pos $bashtext,
82             $expander );
83 66         120 return ( $elem . $next_elem, $next_end );
84             }
85              
86             # Perform the simplest parameter expansion possible.
87             sub _expand_bash
88             {
89 23     23   31 my ($bashstr, $fields_ref) = @_;
90              
91             my $expand_field = sub {
92 10     10   11 my $name = shift;
93 10 50       35 return $fields_ref->{ $name } if defined $fields_ref->{ $name };
94 0         0 return qq{\$$name};
95             # TODO: error reporting?
96 23         55 };
97              
98 23         37 $bashstr =~ s{ \$ ([\w_]+) }
99 10         13 { $expand_field->( $1 ) }gex;
100              
101             # TODO: check for special expansion modifiers
102 23         19 $bashstr =~ s( \$ \{ ([^}]+) \} )
103 0         0 ( $expand_field->( $1 ) )gex;
104              
105 23         65 return $bashstr;
106             }
107              
108             #---HELPER FUNCTION---
109             sub _depstr_to_hash
110             {
111 11     11   8 my ($depstr) = @_;
112 11         38 my ($pkg, $cmp, $ver) = $depstr =~ / \A ([^=<>]+)
113             (?: ([=<>]=?)
114             (.*) )? \z/xms;
115              
116 11 50       18 Carp::confess "Failed to parse depend string: $_" unless $pkg;
117              
118 11         40 return +{ 'pkg' => $pkg, 'cmp' => $cmp,
119             'ver' => $ver, 'str' => $depstr };
120             }
121              
122             sub _provides_to_hash
123             {
124 1     1   2 my ($provstr) = @_;
125 1         3 my ($pkg, $ver) = $provstr =~ / \A ([^=]+)
126             (?: = (.*))?
127             /xms;
128 1 50       2 Carp::confess "Failed to parse provides string: $_" unless $pkg;
129 1         3 return +{ 'pkg' => $pkg, 'ver' => $ver, 'str' => $provstr };
130             }
131              
132             #---HELPER FUNCTION---
133             sub _pkgbuild_fields
134             {
135 6     6   10 my ($pbtext) = @_;
136              
137 6         6 my %pbfields;
138             my $expander = sub {
139 23     23   34 _expand_bash( shift, \%pbfields )
140 6         23 };
141              
142 6         52 while ( $pbtext =~ / \G .*? \n? ^ ([\w_]+) = /gxms ) {
143 61         67 my $name = $1;
144 61         81 my ( $value, $endpos ) = _unquote_bash( $pbtext,
145             pos $pbtext,
146             $expander );
147 61         98 $pbfields{ $name } = $value;
148 61         206 ( 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         14 for my $arrkey ( @ARRAY_FIELDS ) {
155 102 100       137 unless ( $pbfields{ $arrkey } ) {
156 67         68 $pbfields{ $arrkey } = [];
157 67         56 next ARRAY_LOOP;
158             }
159              
160 35         33 my $val_ref = $pbfields{ $arrkey };
161              
162             # Force the value into being an array...
163 35 50       45 $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 37         56 $val_ref = [ grep { $_ ne q{\\} } # *1
172 36         50 map { split } # *3
173 36         58 map { s{ \A (\w+) \s+
174 36         36 ([<>=]{1,2}\d+) }{$1$2}x; $_ } # *4
175 36         39 map { s/\A\s+//; s/\s+\z//; $_ } # trim ws
  36         36  
  36         37  
176 35         36 grep { length } map { s/\#.*//; $_ } # *2
  36         35  
  36         36  
  36         41  
177             @$val_ref ];
178              
179 35         55 $pbfields{ $arrkey } = $val_ref;
180             }
181              
182             # optdepends are special, we should only split on newlines
183 6 100       12 if ( $pbfields{'optdepends'} ) {
184 1         1 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  
189 0         0 grep { $_ ne q{\\} }
190 1         2 map { s/\A\s+//; s/\s+\z//; $_ }
  0         0  
  0         0  
  0         0  
191             @$optdeps ];
192 1         2 $pbfields{'optdepends'} = $optdeps;
193             }
194             else {
195 5         9 $pbfields{'optdepends'} = [];
196             }
197              
198             # Convert all depends into hash references...
199             VERSPEC_LOOP:
200 6         10 for my $depkey ( qw/ makedepends depends conflicts / ) {
201 18         11 my @deps = @{ $pbfields{ $depkey } };
  18         29  
202 18 100       36 next VERSPEC_LOOP unless @deps;
203              
204 8         7 eval {
205 8         10 $pbfields{ $depkey } = [ map { _depstr_to_hash($_) } @deps ];
  11         15  
206             };
207 8 50       16 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       10 if ( $pbfields{'provides'} ) {
214             $pbfields{'provides'} =
215 6         7 [ map { _provides_to_hash($_) } @{$pbfields{'provides'}} ];
  1         1  
  6         10  
216             }
217              
218 6         76 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         16 return <$fh>;
231             }
232              
233             sub read
234             {
235 6     6 1 7 my $self = shift;
236 6 100       25 $self->{'text'} = ( ref $_[0] eq 'GLOB' ? _slurp( shift ) : shift );
237              
238 6         28 my %pbfields = _pkgbuild_fields( $self->{'text'} );
239 6         19 $self->{'fields'} = \%pbfields;
240 6         7 return %pbfields;
241             }
242              
243             sub fields
244             {
245 2     2 1 4 my ($self) = @_;
246 2         2 return %{ $self->{'fields'} }
  2         15  
247             }
248              
249             sub _def_field_acc
250             {
251 200     200   151 my ($name) = @_;
252              
253 8     8   38 no strict 'refs';
  8         9  
  8         812  
254 200         540 *{ $name } = sub {
255 4     4   1077 my ($self) = @_;
256 4         10 my $val = $self->{'fields'}{$name};
257              
258 4 50       10 return q{} unless defined $val;
259 4         16 return $val;
260             }
261 200         285 }
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__