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   18540 use v5.20;
  6         22  
12 6     6   32 use utf8;
  6         12  
  6         40  
13 6     6   506 use Role::Commons -all;
  6         21180  
  6         54  
14 6     6   31728 use feature 'signatures';
  6         11  
  6         667  
15 6     6   436 use namespace::autoclean 0.16;
  6         9697  
  6         39  
16 6     6   820 use autodie;
  6         11056  
  6         53  
17              
18 6     6   34123 use YAML::XS;
  6         15554  
  6         330  
19 6     6   2125 use List::MoreUtils qw(uniq);
  6         31164  
  6         71  
20 6     6   8815 use Hash::Merge qw(merge);
  6         39599  
  6         307  
21 6     6   42 use Try::Tiny;
  6         11  
  6         317  
22              
23 6     6   494 use Moo;
  6         2226  
  6         39  
24 6     6   4110 use MooX::StrictConstructor;
  6         2136  
  6         96  
25             extends qw(Boxer::World);
26              
27 6     6   25204 use Types::Standard qw( ArrayRef InstanceOf Maybe );
  6         51702  
  6         81  
28 6     6   5796 use Boxer::Types qw( ClassDir NodeDir Suite );
  6         12  
  6         55  
29              
30 6     6   5572 use Boxer::Part::Reclass;
  6         17  
  6         211  
31 6     6   2220 use Boxer::World::Flat;
  6         18  
  6         246  
32              
33 6     6   41 use strictures 2;
  6         44  
  6         231  
34 6     6   1091 no warnings "experimental::signatures";
  6         13  
  6         15569  
35              
36             =head1 VERSION
37              
38             Version v1.4.2
39              
40             =cut
41              
42             our $VERSION = "v1.4.2";
43              
44             =head1 DESCRIPTION
45              
46             Outside the box is a world of software.
47              
48             B<Boxer::World::Reclass> 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<Boxer>.
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   13 {
  1         2  
  1         1  
72 1 50       17 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   25 {
  1         3  
  1         2  
87 1 50       16 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   49029 {
  6         13  
  6         8  
121 1400         1964 my $classdata = $self->classdir->visit(
122 1400     1400   1457 sub ( $path, $state ) {
  1400         155044  
  1400         1541  
123 1400 100       3099 return if $path->is_dir;
124 920 50       14026 return unless ( $path->basename =~ /\.yml$/ );
125 920         22871 my $yaml = Load( $path->slurp_utf8 );
126 920         180936 my $class = $path->relative( $self->classdir ) =~ tr/\//./r
127             =~ s/\.yml$//r =~ s/\.init$//r;
128 920         250239 $state->{$class} = $yaml;
129             },
130 6         101 { recurse => 1 },
131             );
132 15         28 my $nodedata = $self->nodedir->visit(
133 15     15   19 sub ( $path, $state ) {
  15         2816  
  15         27  
134 15 50       41 return if $path->is_dir;
135 15 50       305 return unless ( $path->basename =~ /\.yml$/ );
136 15         401 my $yaml = Load( $path->slurp_utf8 );
137 15         2737 my $node = $path->basename(qr/\.yml$/);
138 15         419 $state->{$node} = $yaml;
139             },
140 6         588 );
141 6         346 my @parts;
142 6         19 for ( sort keys %{$nodedata} ) {
  6         43  
143 15         27498 my %params = ();
144             my @classes
145             = $nodedata->{$_}{classes}
146 15 100       72 ? @{ $nodedata->{$_}{classes} }
  10         48  
147             : ();
148 15         51 while ( my $next = shift @classes ) {
149 1270 50       26990 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     3839 if ( $classdata->{$next}{classes} and !$params{_seen}{$next} ) {
155 450         747 $params{_seen}{$next} = 1;
156 450         511 unshift @classes, @{ $classdata->{$next}{classes} }, $next;
  450         1120  
157 450         889 next;
158             }
159 725         1553 %params = %{ merge( \%params, $classdata->{$next}{parameters} ) }
160 820 100       1492 if $classdata->{$next}{parameters};
161             }
162 15         508 delete $params{_seen};
163 5         24 %params = %{ merge( \%params, $nodedata->{$_}{parameters} ) }
164 15 100       48 if $nodedata->{$_}{parameters};
165 15         519 push @parts,
166             Boxer::Part::Reclass->new(
167             id => $_,
168             epoch => $self->suite,
169             %params,
170             );
171             }
172 6         2040 return [@parts];
173             }
174              
175             sub list_parts ($self)
176 1     1 0 164 {
  1         2  
  1         2  
177 1         1 return map { $_->id } @{ $self->parts };
  3         14  
  1         19  
178             }
179              
180 11         25 sub get_part ( $self, $id )
181 11     11 0 272457 {
  11         22  
  11         19  
182 11 100       17 unless ( @{ $self->parts } ) {
  11         317  
183 1         24 $self->_logger->error("No parts exist.");
184 1         208 return;
185             }
186 10         268 foreach ( @{ $self->parts } ) {
  10         160  
187 15 100       131 if ( $_->id eq $id ) {
188 9         26 return $_;
189             }
190             }
191 1         23 $self->_logger->error("Part \"$id\" does not exist.");
192 1         338 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         13 sub map ( $self, $node_id, $nonfree )
  6         12  
209 6     6 0 10 {
  6         13  
  6         9  
210 6         27 my $node = $self->get_part($node_id);
211 6         14 my %desc;
212              
213             my @section_keys = sort {
214 113 0 50     436 ( $section_order{$a} // 1000 ) <=> ( $section_order{$b} // 1000 )
      50        
215             || $a cmp $b
216 6         10 } keys %{ $node->{doc} };
  6         50  
217              
218 6         24 foreach my $key (@section_keys) {
219 55   66     139 my $headline = $node->{doc}{$key}{headline}[0] || $key;
220 55 50 66     182 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         60 push @{ $desc{pkg} }, "# $headline";
  54         113  
227 54 50       110 if ( $node->{pkg} ) {
228 54         63 foreach ( @{ $node->{doc}{$key}{pkg} } ) {
  54         102  
229 187         206 push @{ $desc{pkg} }, "# * $_";
  187         353  
230             }
231             }
232 54 0 33     101 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     286 if ( $node->{tweak} and $node->{doc}{$key}{tweak} ) {
239 2         5 push @{ $desc{tweak} }, "# $headline";
  2         7  
240 2         3 foreach ( @{ $node->{doc}{$key}{tweak} } ) {
  2         6  
241 4         6 push @{ $desc{tweak} }, "# * $_";
  4         11  
242             }
243             }
244             }
245             my $pkgdesc
246             = defined( $desc{pkg} )
247 6 50       24 ? join( "\n", @{ $desc{pkg} } )
  6         240  
248             : '';
249             my $tweakdesc
250             = defined( $desc{tweak} )
251 6 100       25 ? join( "\n", @{ $desc{tweak} } )
  1         5  
252             : '';
253 6     6   354 my @pkg = try { @{ $node->{pkg} } }
  6         72  
254             catch {
255 0     0   0 $self->_logger->warning('No packages resolved');
256 0         0 return ();
257 6         72 };
258 6     6   208 my @pkgauto = try { @{ $node->{'pkg-auto'} } }
  6         32  
259             catch {
260 0     0   0 $self->_logger->warning('No package auto-markings resolved');
261 0         0 return ();
262 6         187 };
263 6     6   182 my @pkgavoid = try { @{ $node->{'pkg-avoid'} } }
  6         22  
264             catch {
265 0     0   0 $self->_logger->warning('No package avoidance resolved');
266 0         0 return ();
267 6         119 };
268 6     6   258 my @tweak = try { @{ $node->{tweak} } }
  6         62  
269             catch {
270 5     5   194 $self->_logger->warning('No tweaks resolved');
271 5         1158 return ();
272 6         105 };
273 6 50       53 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         16 chomp(@tweak);
279              
280 6         136 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<< <dr@jones.dk> >>.
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;