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             # ABSTRACT: a role to make assemblers expand bundles
3             $Config::MVP::Assembler::WithBundles::VERSION = '2.200012';
4 2     2   3720 use Moose::Role;
  2         5  
  2         21  
5              
6 2     2   11480 use Params::Util qw(_HASHLIKE _ARRAYLIKE);
  2         5  
  2         197  
7 2     2   20 use Class::Load 0.17 ();
  2         68  
  2         1579  
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 61 my ($self, $pkg) = @_;
59 25 100       203 return unless $pkg->can('mvp_bundle_config');
60 6         28 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 14 my ($self, $bundle_sec, $method) = @_;
77              
78 4         105 my $seq = $self->sequence;
79              
80 4         101 $seq->delete_section($bundle_sec->name);
81              
82 4         240 $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 36 my ($self, $package, $section_name) = @_;
91              
92 16 50       85 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   19 my ($self, $method, $arg) = @_;
124              
125 6         28 my @bundle_config = $arg->{package}->$method($arg);
126              
127 6         54 PLUGIN: for my $plugin (@bundle_config) {
128 16         46 my ($name, $package, $payload) = @$plugin;
129              
130 16         54 $self->load_package($package, $name);
131              
132 16 100       4669 if (my $method = $self->package_bundle_method( $package )) {
133 2         24 $self->_add_bundle_contents($method, {
134             name => $name,
135             package => $package,
136             payload => $payload,
137             });
138             } else {
139 14         490 my $section = $self->section_class->new({
140             name => $name,
141             package => $package,
142             });
143              
144 14 100       175 if (_HASHLIKE($payload)) {
    50          
145             # XXX: Clearly this is a hack. -- rjbs, 2009-08-24
146 8         34 for my $name (keys %$payload) {
147             my @v = ref $payload->{$name} eq ref []
148 2         6 ? @{$payload->{$name}}
149 8 100       39 : $payload->{$name};
150 8 50       27 Carp::confess("got impossible zero-value <$name> key")
151             unless @v;
152 8         31 $section->add_value($name => $_) for @v;
153             }
154             } elsif (_ARRAYLIKE($payload)) {
155 6         24 for (my $i = 0; $i < @$payload; $i += 2) {
156 10         34 $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         376 $self->sequence->add_section($section);
163 14         265 $section->finalize;
164             }
165             }
166             }
167              
168 2     2   25 no Moose;
  2         7  
  2         15  
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.200012
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 AUTHOR
232              
233             Ricardo Signes <rjbs@cpan.org>
234              
235             =head1 COPYRIGHT AND LICENSE
236              
237             This software is copyright (c) 2021 by Ricardo Signes.
238              
239             This is free software; you can redistribute it and/or modify it under
240             the same terms as the Perl 5 programming language system itself.
241              
242             =cut