File Coverage

blib/lib/Config/MVP/Assembler/WithBundles.pm
Criterion Covered Total %
statement 39 43 90.7
branch 11 14 78.5
condition n/a
subroutine 8 9 88.8
pod 1 4 25.0
total 59 70 84.2


line stmt bran cond sub pod time code
1             package Config::MVP::Assembler::WithBundles 2.200013;
2             # ABSTRACT: a role to make assemblers expand bundles
3              
4 2     2   3153 use Moose::Role;
  2         4  
  2         17  
5              
6 2     2   9081 use Params::Util qw(_HASHLIKE _ARRAYLIKE);
  2         4  
  2         140  
7 2     2   13 use Class::Load 0.17 ();
  2         51  
  2         1243  
8              
9             #pod =head1 DESCRIPTION
10             #pod
11             #pod Config::MVP::Assembler::WithBundles is a role to be composed into a
12             #pod Config::MVP::Assembler subclass. It allows some sections of configuration to
13             #pod be treated as bundles. When any section is ended, if that section represented
14             #pod a bundle, its bundle contents will be unrolled and will replace it in the
15             #pod sequence.
16             #pod
17             #pod A package is considered a bundle if C<package_bundle_method> returns a
18             #pod defined value (which is the name of a method that will be called on
19             #pod that package to retrieve its bundle config).
20             #pod
21             #pod my $method = $assembler->package_bundle_method($package);
22             #pod
23             #pod The default implementation looks for a method called C<mvp_bundle_config>, but
24             #pod C<package_bundle_method> can be replaced with one that returns the name of a
25             #pod different bundle-identifying method-name.
26             #pod
27             #pod Bundles are expanded by a call to the assembler's
28             #pod C<replace_bundle_with_contents> method, like this:
29             #pod
30             #pod $assembler->replace_bundle_with_contents($section, $method);
31             #pod
32             #pod =head2 replace_bundle_with_contents
33             #pod
34             #pod The default C<replace_bundle_with_contents> method deletes the section from the
35             #pod sequence. It then gets a description of the new sections to introduce, like
36             #pod this:
37             #pod
38             #pod my @new_config = $bundle_section->package->$method({
39             #pod name => $bundle_section->name,
40             #pod package => $bundle_section->package,
41             #pod payload => $bundle_section->payload,
42             #pod });
43             #pod
44             #pod (We pass a hashref rather than a section so that bundles can be expanded
45             #pod synthetically without having to laboriously create a new Section.)
46             #pod
47             #pod The returned C<@new_config> is a list of arrayrefs, each of which has three
48             #pod entries:
49             #pod
50             #pod [ $name, $package, $payload ]
51             #pod
52             #pod Each arrayref is converted into a section in the sequence. The C<$payload>
53             #pod should be an arrayref of name/value pairs to be added to the created section.
54             #pod
55             #pod =cut
56              
57             sub package_bundle_method {
58 25     25 0 50 my ($self, $pkg) = @_;
59 25 100       175 return unless $pkg->can('mvp_bundle_config');
60 6         23 return 'mvp_bundle_config';
61             }
62              
63             after end_section => sub {
64             my ($self) = @_;
65              
66             my $seq = $self->sequence;
67              
68             my ($last) = ($seq->sections)[-1];
69             return unless $last->package;
70             return unless my $method = $self->package_bundle_method($last->package);
71              
72             $self->replace_bundle_with_contents($last, $method);
73             };
74              
75             sub replace_bundle_with_contents {
76 4     4 1 12 my ($self, $bundle_sec, $method) = @_;
77              
78 4         93 my $seq = $self->sequence;
79              
80 4         84 $seq->delete_section($bundle_sec->name);
81              
82 4         182 $self->_add_bundle_contents($method, {
83             name => $bundle_sec->name,
84             package => $bundle_sec->package,
85             payload => $bundle_sec->payload,
86             });
87             };
88              
89             sub load_package {
90 16     16 0 25 my ($self, $package, $section_name) = @_;
91              
92 16 50       37 Class::Load::load_optional_class($package)
93             or $self->missing_package($package, $section_name);
94             }
95              
96             sub missing_package {
97 0     0 0 0 my ($self, $package, $section_name) = @_ ;
98              
99 0         0 my $class = Moose::Meta::Class->create_anon_class(
100             superclasses => [ 'Config::MVP::Error' ],
101             cached => 1,
102             attributes => [
103             Moose::Meta::Attribute->new(package => (
104             is => 'ro',
105             required => 1,
106             )),
107             Moose::Meta::Attribute->new(section_name => (
108             is => 'ro',
109             required => 1,
110             )),
111             ],
112             );
113              
114 0         0 $class->name->throw({
115             ident => 'package not installed',
116             message => "$package (for section $section_name) does not appear to be installed",
117             package => $package,
118             section_name => $section_name,
119             });
120             }
121              
122             sub _add_bundle_contents {
123 6     6   14 my ($self, $method, $arg) = @_;
124              
125 6         25 my @bundle_config = $arg->{package}->$method($arg);
126              
127 6         41 PLUGIN: for my $plugin (@bundle_config) {
128 16         35 my ($name, $package, $payload) = @$plugin;
129              
130 16         40 $self->load_package($package, $name);
131              
132 16 100       3778 if (my $method = $self->package_bundle_method( $package )) {
133 2         18 $self->_add_bundle_contents($method, {
134             name => $name,
135             package => $package,
136             payload => $payload,
137             });
138             } else {
139 14         385 my $section = $self->section_class->new({
140             name => $name,
141             package => $package,
142             });
143              
144 14 100       135 if (_HASHLIKE($payload)) {
    50          
145             # XXX: Clearly this is a hack. -- rjbs, 2009-08-24
146 8         31 for my $name (keys %$payload) {
147             my @v = ref $payload->{$name} eq ref []
148 2         5 ? @{$payload->{$name}}
149 8 100       31 : $payload->{$name};
150 8 50       22 Carp::confess("got impossible zero-value <$name> key")
151             unless @v;
152 8         26 $section->add_value($name => $_) for @v;
153             }
154             } elsif (_ARRAYLIKE($payload)) {
155 6         19 for (my $i = 0; $i < @$payload; $i += 2) {
156 10         27 $section->add_value(@$payload[ $i, $i + 1 ]);
157             }
158             } else {
159 0         0 Carp::confess("don't know how to interpret section payload $payload");
160             }
161              
162 14         308 $self->sequence->add_section($section);
163 14         202 $section->finalize;
164             }
165             }
166             }
167              
168 2     2   14 no Moose;
  2         11  
  2         16  
169             1;
170              
171             __END__
172              
173             =pod
174              
175             =encoding UTF-8
176              
177             =head1 NAME
178              
179             Config::MVP::Assembler::WithBundles - a role to make assemblers expand bundles
180              
181             =head1 VERSION
182              
183             version 2.200013
184              
185             =head1 DESCRIPTION
186              
187             Config::MVP::Assembler::WithBundles is a role to be composed into a
188             Config::MVP::Assembler subclass. It allows some sections of configuration to
189             be treated as bundles. When any section is ended, if that section represented
190             a bundle, its bundle contents will be unrolled and will replace it in the
191             sequence.
192              
193             A package is considered a bundle if C<package_bundle_method> returns a
194             defined value (which is the name of a method that will be called on
195             that package to retrieve its bundle config).
196              
197             my $method = $assembler->package_bundle_method($package);
198              
199             The default implementation looks for a method called C<mvp_bundle_config>, but
200             C<package_bundle_method> can be replaced with one that returns the name of a
201             different bundle-identifying method-name.
202              
203             Bundles are expanded by a call to the assembler's
204             C<replace_bundle_with_contents> method, like this:
205              
206             $assembler->replace_bundle_with_contents($section, $method);
207              
208             =head2 replace_bundle_with_contents
209              
210             The default C<replace_bundle_with_contents> method deletes the section from the
211             sequence. It then gets a description of the new sections to introduce, like
212             this:
213              
214             my @new_config = $bundle_section->package->$method({
215             name => $bundle_section->name,
216             package => $bundle_section->package,
217             payload => $bundle_section->payload,
218             });
219              
220             (We pass a hashref rather than a section so that bundles can be expanded
221             synthetically without having to laboriously create a new Section.)
222              
223             The returned C<@new_config> is a list of arrayrefs, each of which has three
224             entries:
225              
226             [ $name, $package, $payload ]
227              
228             Each arrayref is converted into a section in the sequence. The C<$payload>
229             should be an arrayref of name/value pairs to be added to the created section.
230              
231             =head1 PERL VERSION
232              
233             This module should work on any version of perl still receiving updates from
234             the Perl 5 Porters. This means it should work on any version of perl released
235             in the last two to three years. (That is, if the most recently released
236             version is v5.40, then this module should work on both v5.40 and v5.38.)
237              
238             Although it may work on older versions of perl, no guarantee is made that the
239             minimum required version will not be increased. The version may be increased
240             for any reason, and there is no promise that patches will be accepted to lower
241             the minimum required perl.
242              
243             =head1 AUTHOR
244              
245             Ricardo Signes <cpan@semiotic.systems>
246              
247             =head1 COPYRIGHT AND LICENSE
248              
249             This software is copyright (c) 2022 by Ricardo Signes.
250              
251             This is free software; you can redistribute it and/or modify it under
252             the same terms as the Perl 5 programming language system itself.
253              
254             =cut