File Coverage

blib/lib/Boxer/World/Reclass.pm
Criterion Covered Total %
statement 175 193 90.6
branch 28 46 60.8
condition 16 28 57.1
subroutine 31 34 91.1
pod 0 3 0.0
total 250 304 82.2


line stmt bran cond sub pod time code
1             package Boxer::World::Reclass;
2              
3             =encoding UTF-8
4              
5             =head1 NAME
6              
7             Boxer::World::Reclass - software as serialized by reclass
8              
9             =cut
10              
11 6     6   18202 use v5.20;
  6         18  
12 6     6   29 use utf8;
  6         10  
  6         36  
13 6     6   579 use Role::Commons -all;
  6         30557  
  6         42  
14 6     6   36943 use feature 'signatures';
  6         11  
  6         528  
15 6     6   424 use namespace::autoclean 0.16;
  6         9749  
  6         34  
16 6     6   804 use autodie;
  6         12305  
  6         35  
17              
18 6     6   30621 use YAML::XS;
  6         15047  
  6         334  
19 6     6   2031 use List::MoreUtils qw(uniq);
  6         32330  
  6         60  
20 6     6   8188 use Hash::Merge qw(merge);
  6         37365  
  6         321  
21 6     6   41 use Try::Tiny;
  6         10  
  6         279  
22              
23 6     6   484 use Moo;
  6         2290  
  6         44  
24 6     6   3942 use MooX::StrictConstructor;
  6         2203  
  6         64  
25             extends qw(Boxer::World);
26              
27 6     6   23920 use Types::Standard qw( ArrayRef InstanceOf Maybe );
  6         53320  
  6         1471  
28 6     6   5284 use Boxer::Types qw( ClassDir NodeDir Suite );
  6         12  
  6         50  
29              
30 6     6   5245 use Boxer::Part::Reclass;
  6         18  
  6         182  
31 6     6   2037 use Boxer::World::Flat;
  6         17  
  6         225  
32              
33 6     6   41 use strictures 2;
  6         34  
  6         208  
34 6     6   918 no warnings "experimental::signatures";
  6         13  
  6         14774  
35              
36             =head1 VERSION
37              
38             Version v1.4.3
39              
40             =cut
41              
42             our $VERSION = "v1.4.3";
43              
44             =head1 DESCRIPTION
45              
46             Outside the box is a world of software.
47              
48             B is a class describing a collection of software
49             available for installation into (or as) an operating system.
50              
51             =head1 SEE ALSO
52              
53             L.
54              
55             =cut
56              
57             has suite => (
58             is => 'ro',
59             isa => Suite,
60             required => 1,
61             );
62              
63             has classdir => (
64             is => 'lazy',
65             isa => ClassDir,
66             coerce => 1,
67             required => 1,
68             );
69              
70             sub _build_classdir ($self)
71 1     1   15 {
  1         1  
  1         2  
72 1 50       16 if ( $self->data ) {
73 1         22 return $self->data->child('classes');
74             }
75 0         0 return;
76             }
77              
78             has nodedir => (
79             is => 'lazy',
80             isa => NodeDir,
81             coerce => 1,
82             required => 1,
83             );
84              
85             sub _build_nodedir ($self)
86 1     1   23 {
  1         3  
  1         2  
87 1 50       18 if ( $self->data ) {
88 1         23 return $self->data->child('nodes');
89             }
90 0         0 return;
91             }
92              
93             has parts => (
94             is => 'lazy',
95             isa => ArrayRef [ InstanceOf ['Boxer::Part::Reclass'] ],
96             init_arg => undef,
97             );
98              
99             # process only matching types, and skip duplicates is arrays
100             my $merge_spec = {
101             'SCALAR' => {
102             'SCALAR' => sub { $_[0] },
103             'ARRAY' => sub { die 'bad input data' },
104             'HASH' => sub { die 'bad input data' },
105             },
106             'ARRAY' => {
107             'SCALAR' => sub { die 'bad input data' },
108             'ARRAY' => sub { [ uniq @{ $_[0] }, @{ $_[1] } ] },
109             'HASH' => sub { die 'bad input data' },
110             },
111             'HASH' => {
112             'SCALAR' => sub { die 'bad input data' },
113             'ARRAY' => sub { die 'bad input data' },
114             'HASH' => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
115             },
116             };
117             Hash::Merge::add_behavior_spec($merge_spec);
118              
119             sub _build_parts ($self)
120 6     6   47964 {
  6         12  
  6         8  
121 1400         2030 my $classdata = $self->classdir->visit(
122 1400     1400   1472 sub ( $path, $state ) {
  1400         155592  
  1400         1536  
123 1400 100       3030 return if $path->is_dir;
124 920 50       13271 return unless ( $path->basename =~ /\.yml$/ );
125 920         22471 my $yaml = Load( $path->slurp_utf8 );
126 920         184202 my $class = $path->relative( $self->classdir ) =~ tr/\//./r
127             =~ s/\.yml$//r =~ s/\.init$//r;
128 920         244789 $state->{$class} = $yaml;
129             },
130 6         93 { recurse => 1 },
131             );
132 15         26 my $nodedata = $self->nodedir->visit(
133 15     15   24 sub ( $path, $state ) {
  15         2732  
  15         20  
134 15 50       37 return if $path->is_dir;
135 15 50       334 return unless ( $path->basename =~ /\.yml$/ );
136 15         396 my $yaml = Load( $path->slurp_utf8 );
137 15         2839 my $node = $path->basename(qr/\.yml$/);
138 15         455 $state->{$node} = $yaml;
139             },
140 6         620 );
141 6         374 my @parts;
142 6         13 for ( sort keys %{$nodedata} ) {
  6         48  
143 15         26103 my %params = ();
144             my @classes
145             = $nodedata->{$_}{classes}
146 15 100       61 ? @{ $nodedata->{$_}{classes} }
  10         44  
147             : ();
148 15         46 while ( my $next = shift @classes ) {
149 1270 50       26096 unless ( $classdata->{$next} ) {
150 0         0 $self->_logger->debug(
151             "Ignoring missing class $next for node $_.");
152 0         0 next;
153             }
154 1270 100 100     3756 if ( $classdata->{$next}{classes} and !$params{_seen}{$next} ) {
155 450         723 $params{_seen}{$next} = 1;
156 450         508 unshift @classes, @{ $classdata->{$next}{classes} }, $next;
  450         1053  
157 450         907 next;
158             }
159 725         1551 %params = %{ merge( \%params, $classdata->{$next}{parameters} ) }
160 820 100       1520 if $classdata->{$next}{parameters};
161             }
162 15         487 delete $params{_seen};
163 5         21 %params = %{ merge( \%params, $nodedata->{$_}{parameters} ) }
164 15 100       47 if $nodedata->{$_}{parameters};
165 15         531 push @parts,
166             Boxer::Part::Reclass->new(
167             id => $_,
168             epoch => $self->suite,
169             %params,
170             );
171             }
172 6         2108 return [@parts];
173             }
174              
175             sub list_parts ($self)
176 1     1 0 160 {
  1         2  
  1         2  
177 1         2 return map { $_->id } @{ $self->parts };
  3         16  
  1         19  
178             }
179              
180 11         25 sub get_part ( $self, $id )
181 11     11 0 276223 {
  11         20  
  11         17  
182 11 100       16 unless ( @{ $self->parts } ) {
  11         277  
183 1         22 $self->_logger->error("No parts exist.");
184 1         183 return;
185             }
186 10         266 foreach ( @{ $self->parts } ) {
  10         148  
187 15 100       131 if ( $_->id eq $id ) {
188 9         29 return $_;
189             }
190             }
191 1         24 $self->_logger->error("Part \"$id\" does not exist.");
192 1         335 return;
193             }
194              
195             my $pos = 1;
196             my @section_order = qw(
197             Administration
198             Service
199             Console
200             Desktop
201             Language
202             Framework
203             Task
204             Hardware
205             );
206             my %section_order = map { $_ => $pos++ } @section_order;
207              
208 6         11 sub map ( $self, $node_id, $nonfree )
  6         11  
209 6     6 0 13 {
  6         8  
  6         10  
210 6         21 my $node = $self->get_part($node_id);
211 6         23 my %desc;
212              
213             my @section_keys = sort {
214 117 0 50     372 ( $section_order{$a} // 1000 ) <=> ( $section_order{$b} // 1000 )
      50        
215             || $a cmp $b
216 6         11 } keys %{ $node->{doc} };
  6         53  
217              
218 6         28 foreach my $key (@section_keys) {
219 55   66     131 my $headline = $node->{doc}{$key}{headline}[0] || $key;
220 55 50 66     168 if (( $node->{pkg} and $node->{doc}{$key}{pkg} )
      33        
      0        
      66        
221             or ( $nonfree
222             and $node->{'pkg-nonfree'}
223             and $node->{doc}{$key}{'pkg-nonfree'} )
224             )
225             {
226 54         58 push @{ $desc{pkg} }, "# $headline";
  54         107  
227 54 50       92 if ( $node->{pkg} ) {
228 54         55 foreach ( @{ $node->{doc}{$key}{pkg} } ) {
  54         96  
229 187         185 push @{ $desc{pkg} }, "# * $_";
  187         342  
230             }
231             }
232 54 0 33     90 if ( $nonfree and $node->{'pkg-nonfree'} ) {
233 0         0 foreach ( @{ $node->{doc}{$key}{'pkg-nonfree'} } ) {
  0         0  
234 0         0 push @{ $desc{pkg} }, "# * [non-free] $_";
  0         0  
235             }
236             }
237             }
238 55 100 100     315 if ( $node->{tweak} and $node->{doc}{$key}{tweak} ) {
239 2         3 push @{ $desc{tweak} }, "# $headline";
  2         5  
240 2         3 foreach ( @{ $node->{doc}{$key}{tweak} } ) {
  2         4  
241 4         5 push @{ $desc{tweak} }, "# * $_";
  4         10  
242             }
243             }
244             }
245             my $pkgdesc
246             = defined( $desc{pkg} )
247 6 50       23 ? join( "\n", @{ $desc{pkg} } )
  6         213  
248             : '';
249             my $tweakdesc
250             = defined( $desc{tweak} )
251 6 100       21 ? join( "\n", @{ $desc{tweak} } )
  1         4  
252             : '';
253 6     6   345 my @pkg = try { @{ $node->{pkg} } }
  6         73  
254             catch {
255 0     0   0 $self->_logger->warning('No packages resolved');
256 0         0 return ();
257 6         73 };
258 6     6   182 my @pkgauto = try { @{ $node->{'pkg-auto'} } }
  6         25  
259             catch {
260 0     0   0 $self->_logger->warning('No package auto-markings resolved');
261 0         0 return ();
262 6         195 };
263 6     6   174 my @pkgavoid = try { @{ $node->{'pkg-avoid'} } }
  6         19  
264             catch {
265 0     0   0 $self->_logger->warning('No package avoidance resolved');
266 0         0 return ();
267 6         100 };
268 6     6   246 my @tweak = try { @{ $node->{tweak} } }
  6         71  
269             catch {
270 5     5   176 $self->_logger->warning('No tweaks resolved');
271 5         1170 return ();
272 6         90 };
273 6 50       48 if ($nonfree) {
274 0 0       0 push @pkg, @{ $node->{'pkg-nonfree'} } if ( $node->{'pkg-nonfree'} );
  0         0  
275 0         0 push @pkgauto, @{ $node->{'pkg-nonfree-auto'} }
276 0 0       0 if ( $node->{'pkg-nonfree-auto'} );
277             }
278 6         15 chomp(@tweak);
279              
280 6         125 return Boxer::World::Flat->new(
281             node => $node_id,
282             epoch => $node->epoch,
283             pkgs => \@pkg,
284             pkgs_auto => \@pkgauto,
285             pkgs_avoid => \@pkgavoid,
286             tweaks => \@tweak,
287             pkgdesc => $pkgdesc,
288             tweakdesc => $tweakdesc,
289             nonfree => $nonfree, # TODO: unset if none resolved
290             );
291             }
292              
293             =head1 AUTHOR
294              
295             Jonas Smedegaard C<< >>.
296              
297             =cut
298              
299             our $AUTHORITY = 'cpan:JONASS';
300              
301             =head1 COPYRIGHT AND LICENCE
302              
303             Copyright © 2013-2016 Jonas Smedegaard
304              
305             This is free software; you can redistribute it and/or modify it under
306             the same terms as the Perl 5 programming language system itself.
307              
308             =head1 DISCLAIMER OF WARRANTIES
309              
310             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
311             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
312             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
313              
314             =cut
315              
316             1;