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   492698 use v5.10.0;
  2         19  
6              
7 2     2   1068 use Regexp::Common 'balanced';
  2         5582  
  2         7  
8              
9 2     2   4763 use Moo;
  2         22198  
  2         10  
10              
11 2     2   3967 use App::mkpkgconfig::PkgConfig::Entry;
  2         5  
  2         68  
12 2     2   14 use constant Keyword => 'App::mkpkgconfig::PkgConfig::Entry::Keyword';
  2         3  
  2         157  
13 2     2   11 use constant Variable => 'App::mkpkgconfig::PkgConfig::Entry::Variable';
  2         5  
  2         108  
14              
15             our $VERSION = 'v2.0.0';
16              
17 2     2   1037 use IO::File ();
  2         10291  
  2         64  
18 2     2   15 use IO::Handle ();
  2         4  
  2         86  
19              
20             sub croak {
21             require Carp;
22             goto &Carp::croak;
23             }
24              
25 2     2   1073 use namespace::clean;
  2         23652  
  2         13  
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 11113 my $class = shift;
63 7         16 my $file = shift;
64              
65 2 50   2   15 open( my $fh, '<', $file )
  2         4  
  2         15  
  7         155  
66             or croak ("unable to open $file\n" );
67              
68 7         1812 my $pkg = $class->new;
69              
70 7         160 while ( defined( $_ = $fh->getline) ) {
71              
72 89 100       2377 next if /^\s*#/; # ignore comments
73 76 100       417 next if /^\s*$/; # ignore empty lines
74              
75 63         117 chomp;
76 63 50       452 croak( "unable to parse line: $_\n" )
77             unless /^[\s]*(?[^\s:=]+)\s*(?[:=])\s*(?.*?)\s*(#.*)?$/;
78              
79 2 100   2   2293 if ( $+{op} eq ':' ) {
  2         716  
  2         1968  
  63         387  
80 28         125 $pkg->add_keyword( $+{name} => $+{value} );
81             }
82             else {
83 35         162 $pkg->add_variable( $+{name} => $+{value} );
84             }
85             }
86              
87 7 50       195 close $fh or croak;
88              
89 7         36 return $pkg;
90             }
91              
92              
93              
94              
95              
96              
97              
98              
99              
100              
101             sub variable {
102 13     13 1 16200 return $_[0]->_variables->{ $_[1] };
103             }
104              
105              
106              
107              
108              
109              
110              
111              
112              
113             sub variables {
114 11     11 1 19338 return values %{ $_[0]->_variables };
  11         75  
115             }
116              
117              
118              
119              
120              
121              
122              
123              
124              
125             sub keyword {
126 2     2 1 1900 return $_[0]->_keywords->{ $_[1] };
127             }
128              
129              
130              
131              
132              
133              
134              
135              
136              
137              
138             sub keywords {
139 8     8 1 1407 return values %{ $_[0]->_keywords };
  8         82  
140             }
141              
142              
143              
144              
145              
146              
147              
148              
149              
150              
151              
152             sub add_variable {
153 129     129 1 464 my ( $self, $name, $value ) = @_;
154              
155 129 50       299 croak ( "attempt to set $name to an undefined value\n" )
156             unless defined $name;
157 129         393 $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 631 my ( $self, $variables ) = @_;
170              
171             $self->add_variable( $_, $variables->{$_} )
172 14         25 for keys %{ $variables };
  14         76  
173             }
174              
175              
176              
177              
178              
179              
180              
181              
182              
183              
184             sub add_keyword {
185 50     50 1 241 my ( $self, $name, $value ) = @_;
186              
187 50 50       132 croak ( "attempt to set $name to an undefined value\n" )
188             unless defined $name;
189              
190 50         184 $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 4467 my ( $self, $keywords ) = @_;
203              
204             $self->add_keyword( $_, $keywords->{$_} )
205 8         24 for keys %{ $keywords };
  8         35  
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 23336 my ( $self, $file ) = ( shift, shift );
242              
243 9         44 my %options = (
244             vars => [],
245             write => 'all',
246             @_
247             );
248              
249 9 50       111 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     796 if ( $options{comments} && @{ $options{comments} } ) {
  6         34  
256 6         10 $fh->say( "# $_" ) for @{ $options{comments}};
  6         37  
257 6         260 $fh->say();
258             }
259              
260 9         59 my @entries = values %{ $self->_keywords };
  9         52  
261              
262 9 100       36 if ( $options{write} eq 'req' ) {
263              
264 7 50       30 if ( defined $options{vars} ) {
265             push @entries, $self->_variables->{$_}
266             // croak( "request for an undefined variable: $_\n" )
267 7   33     15 for @{ $options{vars} };
  7         34  
268             }
269             }
270             else {
271 2         6 push @entries, values %{ $self->_variables };
  2         17  
272             }
273              
274 9         34 my @vars_needed = $self->resolve_dependencies( @entries );
275              
276 43         446 $fh->say( "${_} = @{[ $self->_variables->{$_}->value ]}" )
277 9         33 foreach $self->order_variables( @vars_needed );
278              
279 9         110 $fh->say();
280              
281 25         195 $fh->say( "${_}: @{[ $self->_keywords->{$_}->value ]}" )
282 9         71 foreach order_keywords( keys %{ $self->_keywords } );
  9         54  
283              
284 9         568 return;
285             }
286              
287             sub _entry_type {
288 4 50   4   28 $_[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 3064 my ( $self, @entries ) = @_;
303              
304 18         34 my %validated;
305 2     2   1110 use Hash::Ordered;
  2         7215  
  2         1601  
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         45 for my $entry ( @entries ) {
317 64         196 my $track = Hash::Ordered->new;
318 64         700 $track->push( $entry->name, undef );
319 64         1081 my @depends = ( [ $entry->depends ] );
320              
321 64         165 while ( @depends ) {
322              
323             # check dependencies for last variable
324 138         193 while ( my $name = pop @{ $depends[-1] } ) {
  219         516  
325 85 50       184 next if $validated{$name};
326              
327 85 100       217 if ( $track->exists( $name ) ) {
328 3         18 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     477 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         202 $track->push( $name, undef );
345 81         1119 push @depends, [ $var->depends ];
346             }
347              
348 134         275 my ( $name ) = $track->pop;
349 134         1264 $validated{$name} = undef;
350 134         293 pop @depends;
351             }
352              
353 60 100       291 delete $validated{$entry->name} if $entry->isa( Keyword );
354             }
355              
356 14         63 return keys %validated;
357             }
358              
359              
360              
361              
362              
363              
364              
365              
366              
367              
368             sub order_variables {
369              
370 10     10 1 59 my ( $self, @needed ) = @_;
371              
372 10 50       28 return () unless @needed;
373              
374 10         18 @needed = do { my %uniqstr; @uniqstr{@needed} = (); keys %uniqstr; };
  10         19  
  10         40  
  10         46  
375              
376             my %dephash = map {
377 10   33     22 $_ => [ ( $self->_variables->{$_} // croak( "unknown variable: $_\n" ) )->depends ] }
  48         158  
378             @needed;
379              
380 10         1230 require Algorithm::Dependency::Ordered;
381 10         12186 require Algorithm::Dependency::Source::HoA;
382              
383 10         832 my $ordered;
384              
385 10         22 eval {
386 10 50       75 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         667 $ordered = $deps->schedule( @needed );
392              
393 10 50       6593 if ( !defined $ordered ) {
394 0         0 die( "error in variable dependencies: perhaps there's cycle?\n" ),;
395             }
396             };
397              
398 10 50       36 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         27 my @nodeps = sort grep { !@{ $dephash{$_} } } @$ordered;
  48         61  
  48         110  
407              
408              
409 10 50       33 if ( @nodeps ) {
410 10         17 my %nodeps;
411 10         27 @nodeps{@nodeps} = ();
412 10         25 $ordered = [ @nodeps, grep { !exists $nodeps{$_} } @$ordered ];
  48         105  
413             }
414              
415 10         19 return @{$ordered};
  10         63  
416             }
417              
418              
419              
420              
421              
422              
423              
424              
425              
426              
427             sub order_keywords {
428 10     10 1 4224 my ( @keywords ) = @_;
429              
430 10         17 my %keywords;
431 10         33 @keywords{ @keywords } = ();
432              
433             my @first_keys
434 10         23 = grep { exists $keywords{$_} } qw( Name Description Version );
  30         75  
435 10         16 my %last_keys;
436 10         26 @last_keys{ @keywords } = ();
437 10         27 delete @last_keys{@first_keys};
438              
439 10         55 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__