File Coverage

blib/lib/App/mkpkgconfig/PkgConfig.pm
Criterion Covered Total %
statement 147 150 98.0
branch 29 44 65.9
condition 6 12 50.0
subroutine 26 26 100.0
pod 13 13 100.0
total 221 245 90.2


line stmt bran cond sub pod time code
1             package App::mkpkgconfig::PkgConfig;
2              
3             # ABSTRACT: output pkg-config .pc files
4              
5 2     2   491998 use v5.10.0;
  2         22  
6              
7 2     2   1115 use Regexp::Common 'balanced';
  2         5539  
  2         8  
8              
9 2     2   4632 use Moo;
  2         22520  
  2         13  
10              
11 2     2   4001 use App::mkpkgconfig::PkgConfig::Entry;
  2         4  
  2         66  
12 2     2   17 use constant Keyword => 'App::mkpkgconfig::PkgConfig::Entry::Keyword';
  2         4  
  2         158  
13 2     2   14 use constant Variable => 'App::mkpkgconfig::PkgConfig::Entry::Variable';
  2         5  
  2         117  
14              
15             our $VERSION = 'v2.0.1';
16              
17 2     2   1051 use IO::File ();
  2         9956  
  2         51  
18 2     2   15 use IO::Handle ();
  2         3  
  2         82  
19              
20             sub croak {
21             require Carp;
22             goto &Carp::croak;
23             }
24              
25 2     2   1034 use namespace::clean;
  2         23621  
  2         16  
26              
27              
28              
29              
30              
31              
32             has _keywords => (
33             is => 'ro',
34             default => sub { {} },
35             init_args => 'keywords',
36             );
37              
38             has _variables => (
39             is => 'ro',
40             default => sub { {} },
41             init_args => 'variables',
42             );
43              
44              
45              
46              
47              
48              
49              
50              
51              
52              
53              
54              
55              
56              
57              
58              
59              
60              
61             sub new_from {
62 7     7 1 11688 my $class = shift;
63 7         15 my $file = shift;
64              
65 2 50   2   33 open( my $fh, '<', $file )
  2         7  
  2         23  
  7         142  
66             or croak ("unable to open $file\n" );
67              
68 7         2106 my $pkg = $class->new;
69              
70 7         151 while ( defined( $_ = $fh->getline) ) {
71              
72 89 100       2460 next if /^\s*#/; # ignore comments
73 76 100       433 next if /^\s*$/; # ignore empty lines
74              
75 63         134 chomp;
76 63 50       493 croak( "unable to parse line: $_\n" )
77             unless /^[\s]*(?[^\s:=]+)\s*(?[:=])\s*(?.*?)\s*(#.*)?$/;
78              
79 2 100   2   2256 if ( $+{op} eq ':' ) {
  2         705  
  2         2011  
  63         379  
80 28         133 $pkg->add_keyword( $+{name} => $+{value} );
81             }
82             else {
83 35         130 $pkg->add_variable( $+{name} => $+{value} );
84             }
85             }
86              
87 7 50       199 close $fh or croak;
88              
89 7         37 return $pkg;
90             }
91              
92              
93              
94              
95              
96              
97              
98              
99              
100              
101             sub variable {
102 13     13 1 15706 return $_[0]->_variables->{ $_[1] };
103             }
104              
105              
106              
107              
108              
109              
110              
111              
112              
113             sub variables {
114 11     11 1 19470 return values %{ $_[0]->_variables };
  11         74  
115             }
116              
117              
118              
119              
120              
121              
122              
123              
124              
125             sub keyword {
126 2     2 1 1816 return $_[0]->_keywords->{ $_[1] };
127             }
128              
129              
130              
131              
132              
133              
134              
135              
136              
137              
138             sub keywords {
139 8     8 1 1475 return values %{ $_[0]->_keywords };
  8         50  
140             }
141              
142              
143              
144              
145              
146              
147              
148              
149              
150              
151              
152             sub add_variable {
153 129     129 1 459 my ( $self, $name, $value ) = @_;
154              
155 129 50       318 croak ( "attempt to set $name to an undefined value\n" )
156             unless defined $name;
157 129         421 $self->_variables->{$name} = Variable->new( $name, $value );
158             }
159              
160              
161              
162              
163              
164              
165              
166              
167              
168             sub add_variables {
169 14     14 1 452 my ( $self, $variables ) = @_;
170              
171             $self->add_variable( $_, $variables->{$_} )
172 14         30 for keys %{ $variables };
  14         80  
173             }
174              
175              
176              
177              
178              
179              
180              
181              
182              
183              
184             sub add_keyword {
185 50     50 1 239 my ( $self, $name, $value ) = @_;
186              
187 50 50       135 croak ( "attempt to set $name to an undefined value\n" )
188             unless defined $name;
189              
190 50         191 $self->_keywords->{$name} = Keyword->new( $name, $value );
191             }
192              
193              
194              
195              
196              
197              
198              
199              
200              
201             sub add_keywords {
202 8     8 1 4445 my ( $self, $keywords ) = @_;
203              
204             $self->add_keyword( $_, $keywords->{$_} )
205 8         17 for keys %{ $keywords };
  8         34  
206             }
207              
208              
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228              
229              
230              
231              
232              
233              
234              
235              
236              
237              
238              
239              
240             sub write {
241 9     9 1 23354 my ( $self, $file ) = ( shift, shift );
242              
243 9         45 my %options = (
244             vars => [],
245             write => 'all',
246             @_
247             );
248              
249 9 50       94 my $fh
    50          
250             = defined $file
251             ? IO::File->new( $file, 'w' )
252             : IO::Handle->new_from_fd( fileno( STDOUT ), 'w' )
253             or croak( "unable to create $file: $!\n" );
254              
255 9 100 66     823 if ( $options{comments} && @{ $options{comments} } ) {
  6         35  
256 6         14 $fh->say( "# $_" ) for @{ $options{comments}};
  6         39  
257 6         289 $fh->say();
258             }
259              
260 9         54 my @entries = values %{ $self->_keywords };
  9         52  
261              
262 9 100       37 if ( $options{write} eq 'req' ) {
263              
264 7 50       37 if ( defined $options{vars} ) {
265             push @entries, $self->_variables->{$_}
266             // croak( "request for an undefined variable: $_\n" )
267 7   33     14 for @{ $options{vars} };
  7         36  
268             }
269             }
270             else {
271 2         5 push @entries, values %{ $self->_variables };
  2         18  
272             }
273              
274 9         36 my @vars_needed = $self->resolve_dependencies( @entries );
275              
276 43         435 $fh->say( "${_} = @{[ $self->_variables->{$_}->value ]}" )
277 9         38 foreach $self->order_variables( @vars_needed );
278              
279 9         101 $fh->say();
280              
281 25         187 $fh->say( "${_}: @{[ $self->_keywords->{$_}->value ]}" )
282 9         70 foreach order_keywords( keys %{ $self->_keywords } );
  9         43  
283              
284 9         619 return;
285             }
286              
287             sub _entry_type {
288 4 50   4   27 $_[0]->isa( Keyword ) ? "Keyword" : "Variable",
289             }
290              
291              
292              
293              
294              
295              
296              
297              
298              
299              
300              
301             sub resolve_dependencies {
302 18     18 1 2780 my ( $self, @entries ) = @_;
303              
304 18         35 my %validated;
305 2     2   1146 use Hash::Ordered;
  2         7266  
  2         1537  
306              
307             # descend dependency tree. use an ordered hash to keep track of
308             # which variables are in the current tree, and an array of dependency
309             # arrays to keep track of each variable's dependencies. The ordered
310             # hash makes it easy to generate human readable error output.
311              
312             # could have used an actual tree, but only need a fraction of the
313             # functionality, and it's faster to check for duplicates in a hash
314             # than to compare tree nodes.
315              
316 18         39 for my $entry ( @entries ) {
317 64         200 my $track = Hash::Ordered->new;
318 64         712 $track->push( $entry->name, undef );
319 64         1081 my @depends = ( [ $entry->depends ] );
320              
321 64         145 while ( @depends ) {
322              
323             # check dependencies for last variable
324 138         202 while ( my $name = pop @{ $depends[-1] } ) {
  219         522  
325 85 50       196 next if $validated{$name};
326              
327 85 100       212 if ( $track->exists( $name ) ) {
328 3         17 croak(
329             sprintf(
330             "%s '%s' has a circular dependency: %s\n",
331             _entry_type( $entry ),
332             $entry->name,
333             join( '->', $track->keys, $name ) ) );
334             }
335              
336 82   66     454 my $var = $self->_variables->{$name} // croak(
337             sprintf(
338             "%s '%s' depends upon an undefined variable: %s\n",
339             _entry_type( $entry ),
340             $entry->name,
341             join( '->', $track->keys, $name, 'undef' ),
342             ) );
343              
344 81         207 $track->push( $name, undef );
345 81         1221 push @depends, [ $var->depends ];
346             }
347              
348 134         287 my ( $name ) = $track->pop;
349 134         1293 $validated{$name} = undef;
350 134         298 pop @depends;
351             }
352              
353 60 100       278 delete $validated{$entry->name} if $entry->isa( Keyword );
354             }
355              
356 14         66 return keys %validated;
357             }
358              
359              
360              
361              
362              
363              
364              
365              
366              
367              
368             sub order_variables {
369              
370 10     10 1 62 my ( $self, @needed ) = @_;
371              
372 10 50       38 return () unless @needed;
373              
374 10         20 @needed = do { my %uniqstr; @uniqstr{@needed} = (); keys %uniqstr; };
  10         16  
  10         43  
  10         41  
375              
376             my %dephash = map {
377 10   33     37 $_ => [ ( $self->_variables->{$_} // croak( "unknown variable: $_\n" ) )->depends ] }
  48         153  
378             @needed;
379              
380 10         1361 require Algorithm::Dependency::Ordered;
381 10         12541 require Algorithm::Dependency::Source::HoA;
382              
383 10         853 my $ordered;
384              
385 10         24 eval {
386 10 50       78 my $deps
387             = Algorithm::Dependency::Ordered->new(
388             source => Algorithm::Dependency::Source::HoA->new( \%dephash ) )
389             or die( "error creating dependency object\n" );
390              
391 10         697 $ordered = $deps->schedule( @needed );
392              
393 10 50       6649 if ( !defined $ordered ) {
394 0         0 die( "error in variable dependencies: perhaps there's cycle?\n" ),;
395             }
396             };
397              
398 10 50       47 if ( length( my $err = $@ ) ) {
399 0         0 require Data::Dumper;
400 0         0 die( $err,
401             Data::Dumper->Dump( [ \%dephash, \@needed ], [qw( deps needed )] ) );
402             }
403              
404             # move variables with no dependencies to the beginning of the list
405             # to make it more human friendly
406 10         37 my @nodeps = sort grep { !@{ $dephash{$_} } } @$ordered;
  48         61  
  48         110  
407              
408              
409 10 50       36 if ( @nodeps ) {
410 10         20 my %nodeps;
411 10         26 @nodeps{@nodeps} = ();
412 10         23 $ordered = [ @nodeps, grep { !exists $nodeps{$_} } @$ordered ];
  48         103  
413             }
414              
415 10         20 return @{$ordered};
  10         70  
416             }
417              
418              
419              
420              
421              
422              
423              
424              
425              
426              
427             sub order_keywords {
428 10     10 1 4632 my ( @keywords ) = @_;
429              
430 10         21 my %keywords;
431 10         31 @keywords{ @keywords } = ();
432              
433             my @first_keys
434 10         21 = grep { exists $keywords{$_} } qw( Name Description Version );
  30         73  
435 10         18 my %last_keys;
436 10         23 @last_keys{ @keywords } = ();
437 10         32 delete @last_keys{@first_keys};
438              
439 10         51 return @first_keys, keys %last_keys;
440             }
441              
442             1;
443              
444             #
445             # This file is part of App-mkpkgconfig
446             #
447             # This software is Copyright (c) 2020 by Smithsonian Astrophysical Observatory.
448             #
449             # This is free software, licensed under:
450             #
451             # The GNU General Public License, Version 3, June 2007
452             #
453              
454             __END__