File Coverage

blib/lib/Dist/Zilla/Util/ConfigDumper.pm
Criterion Covered Total %
statement 65 78 83.3
branch 16 30 53.3
condition 3 9 33.3
subroutine 14 15 93.3
pod 2 2 100.0
total 100 134 74.6


line stmt bran cond sub pod time code
1 8     8   603 use 5.006;
  8         21  
2 8     8   31 use strict;
  8         9  
  8         152  
3 8     8   41 use warnings;
  8         14  
  8         503  
4              
5             package Dist::Zilla::Util::ConfigDumper;
6              
7             our $VERSION = '0.003009';
8              
9             # ABSTRACT: A Dist::Zilla plugin configuration extraction utility
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 8     8   29 use Carp qw( croak );
  8         10  
  8         483  
14 8     8   452 use Try::Tiny qw( try catch );
  8         1614  
  8         438  
15 8     8   455 use Sub::Exporter::Progressive -setup => { exports => [qw( config_dumper dump_plugin )], };
  8         1037  
  8         95  
16              
17              
18              
19              
20              
21              
22              
23              
24              
25              
26              
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37              
38              
39              
40              
41              
42              
43              
44              
45              
46              
47              
48              
49              
50              
51              
52              
53              
54              
55              
56              
57             sub config_dumper {
58 9     9 1 23 my ( $package, @methodnames ) = @_;
59 9 100 66     69 if ( not defined $package or ref $package ) {
60             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
61 1         182 croak('config_dumper(__PACKAGE__, @recipie ): Arg 1 must not be ref or undef');
62             ## use critic
63             }
64              
65 8         15 my (@tests) = map { _mk_test( $package, $_ ) } @methodnames;
  8         23  
66 8         13 my $CFG_PACKAGE = __PACKAGE__;
67             return sub {
68 9     9   421253 my ( $orig, $self, @rest ) = @_;
69 9         33 my $cnf = $self->$orig(@rest);
70 9         27 my $payload = {};
71 9         11 my @fails;
72 9         20 for my $test (@tests) {
73 11         245 $test->( $self, $payload, \@fails );
74             }
75 9 100       641 if ( keys %{$payload} ) {
  9         32  
76 7         15 $cnf->{$package} = $payload;
77             }
78 9 100       26 if (@fails) {
79 1 50       4 $cnf->{$CFG_PACKAGE} = {} unless exists $cnf->{$CFG_PACKAGE};
80 1 50       3 $cnf->{$CFG_PACKAGE}->{$package} = {} unless exists $cnf->{$CFG_PACKAGE};
81 1         4 $cnf->{$CFG_PACKAGE}->{$package}->{failed} = \@fails;
82             }
83 9         26 return $cnf;
84 8         61 };
85             }
86              
87              
88              
89              
90              
91              
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102              
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113              
114              
115              
116              
117              
118              
119              
120              
121              
122              
123              
124             sub dump_plugin {
125 0     0 1 0 my ($plugin) = @_;
126 0         0 my $object_config = {};
127 0 0 0     0 $object_config->{class} = $plugin->meta->name if $plugin->can('meta') and $plugin->meta->can('name');
128 0 0       0 $object_config->{name} = $plugin->plugin_name if $plugin->can('plugin_name');
129 0 0       0 $object_config->{version} = $plugin->VERSION if $plugin->can('VERSION');
130 0 0       0 if ( $plugin->can('dump_config') ) {
131 0         0 my $finder_config = $plugin->dump_config;
132 0 0       0 $object_config->{config} = $finder_config if keys %{$finder_config};
  0         0  
133             }
134 0         0 return $object_config;
135             }
136              
137             sub _mk_method_test {
138 2     2   5 my ( undef, $methodname ) = @_;
139             return sub {
140 2     2   3 my ( $instance, $payload, $fails ) = @_;
141             try {
142 2         155 my $value = $instance->$methodname();
143 1         3 $payload->{$methodname} = $value;
144             }
145             catch {
146 1         12 push @{$fails}, $methodname;
  1         9  
147 2         22 };
148 2         12 };
149             }
150              
151             sub _mk_attribute_test {
152 6     6   10 my ( undef, $attrname ) = @_;
153             return sub {
154 8     8   10 my ( $instance, $payload, $fails ) = @_;
155             try {
156 8         241 my $metaclass = $instance->meta;
157 8         104 my $attribute_metaclass = $metaclass->find_attribute_by_name($attrname);
158 8 100       336 if ( $attribute_metaclass->has_value($instance) ) {
159 6         182 $payload->{$attrname} = $attribute_metaclass->get_value($instance);
160             }
161             }
162             catch {
163 0         0 push @{$fails}, $attrname;
  0         0  
164 8         51 };
165 6         49 };
166             }
167              
168             sub _mk_hash_test {
169 5     5   7 my ( $package, $hash ) = @_;
170 5         7 my @out;
171 5 50 33     48 if ( exists $hash->{attrs} and 'ARRAY' eq ref $hash->{attrs} ) {
172 5         6 push @out, map { _mk_attribute_test( $package, $_ ) } @{ $hash->{attrs} };
  6         15  
  5         17  
173             }
174 5         15 return @out;
175             }
176              
177             sub _mk_test {
178 8     8   13 my ( $package, $methodname ) = @_;
179 8 100       43 return _mk_method_test( $package, $methodname ) if not ref $methodname;
180 6 100       22 return $methodname if 'CODE' eq ref $methodname;
181 5 50       20 return _mk_hash_test( $package, $methodname ) if 'HASH' eq ref $methodname;
182 0           croak "Don't know what to do with $methodname";
183             }
184              
185             1;
186              
187             __END__
188              
189             =pod
190              
191             =encoding UTF-8
192              
193             =head1 NAME
194              
195             Dist::Zilla::Util::ConfigDumper - A Dist::Zilla plugin configuration extraction utility
196              
197             =head1 VERSION
198              
199             version 0.003009
200              
201             =head1 SYNOPSIS
202              
203             ...
204              
205             with 'Dist::Zilla::Role::Plugin';
206             use Dist::Zilla::Util::ConfigDumper qw( config_dumper );
207              
208             around dump_config => config_dumper( __PACKAGE__, qw( foo bar baz ) );
209              
210             =head1 DESCRIPTION
211              
212             This module contains a utility function for use within the C<Dist::Zilla>
213             plugin ecosystem, to simplify extraction of plugin settings for plugin
214             authors, in order for plugins like C<Dist::Zilla::Plugin::MetaConfig> to expose
215             those values to consumers.
216              
217             Primarily, it specializes in:
218              
219             =over 4
220              
221             =item * Making propagating configuration from the plugins inheritance hierarchy
222             nearly foolproof.
223              
224             =item * Providing simple interfaces to extract values of lists of named methods
225             or accessors
226              
227             =item * Providing a way to intelligently and easily probe the value of lazy
228             attributes without triggering their vivification.
229              
230             =back
231              
232             =head1 FUNCTIONS
233              
234             =head2 C<config_dumper>
235              
236             config_dumper( __PACKAGE__, qw( method list ) );
237              
238             Returns a function suitable for use with C<around dump_config>.
239              
240             my $sub = config_dumper( __PACKAGE__, qw( method list ) );
241             around dump_config => $sub;
242              
243             Or
244              
245             around dump_config => sub {
246             my ( $orig, $self, @args ) = @_;
247             return config_dumper(__PACKAGE__, qw( method list ))->( $orig, $self, @args );
248             };
249              
250             Either way:
251              
252             my $function = config_dumper( $package_name_for_config, qw( methods to call on $self ));
253             my $hash = $function->( $function_that_returns_a_hash, $instance_to_call_methods_on, @somethinggoeshere );
254              
255             =~ All of this approximates:
256              
257             around dump_config => sub {
258             my ( $orig , $self , @args ) = @_;
259             my $conf = $self->$orig( @args );
260             my $payload = {};
261              
262             for my $method ( @methods ) {
263             try {
264             $payload->{ $method } = $self->$method();
265             };
266             }
267             $config->{+__PACKAGE__} = $payload;
268             }
269              
270             Except with some extra "things dun goofed" handling.
271              
272             =head2 C<dump_plugin>
273              
274             This function serves the other half of the equation, emulating C<dzil>'s own
275             internal behavior for extracting the C<plugin> configuration data.
276              
277             for my $plugin ( @{ $zilla->plugins } ) {
278             pp( dump_plugin( $plugin )); # could prove useful somewhere.
279             }
280              
281             Its not usually something you need, but its useful in:
282              
283             =over 4
284              
285             =item * Tests
286              
287             =item * Crazy Stuff like injecting plugins
288              
289             =item * Crazy Stuff like having "Child" plugins
290              
291             =back
292              
293             This serves to be a little more complicated than merely calling C<< ->dump_config >>,
294             as the structure C<dzil> uses is:
295              
296             {
297             class => ...
298             name => ...
299             version => ...
300             config => $dump_config_results_here
301             }
302              
303             And of course, there's a bunch of magic stuff with C<meta>, C<can> and C<if keys %$configresults>
304              
305             All that insanity is wrapped in this simple interface.
306              
307             =head1 ADVANCED USE
308              
309             =head2 CALLBACKS
310              
311             Internally
312              
313             config_dumper( $pkg, qw( method list ) );
314              
315             Maps to a bunch of subs, so its more like:
316              
317             config_dumper( $pkg, sub {
318             my ( $instance, $payload ) = @_;
319             $payload->{'method'} = $instance->method;
320             }, sub {
321             $_[1]->{'list'} = $_[0]->list;
322             });
323              
324             So if you want to use that because its more convenient for some problem, be my guest.
325              
326             around dump_config => config_dumper( __PACKAGE__, sub {
327             $_[1]->{'x'} = 'y'
328             });
329              
330             is much less ugly than
331              
332             around dump_config => sub {
333             my ( $orig, $self, @args ) = @_;
334             my $conf = $self->$orig(@args);
335             $config->{+__PACKAGE__} = { # if you forget the +, things break
336             'x' => 'y'
337             };
338             return $config;
339             };
340              
341             =head2 DETAILED CONFIGURATION
342              
343             There's an additional feature for advanced people:
344              
345             config_dumper( $pkg, \%config );
346              
347             =head3 C<attrs>
348              
349             config_dumper( $pkg, { attrs => [qw( foo bar baz )] });
350              
351             This is for cases where you want to deal with C<Moose> attributes,
352             but want added safety of B<NOT> loading attributes that have no value yet.
353              
354             For each item in C<attrs>, we'll call C<Moose> attribute internals to determine
355             if the attribute named has a value, and only then will we fetch it.
356              
357             =head1 AUTHOR
358              
359             Kent Fredric <kentnl@cpan.org>
360              
361             =head1 COPYRIGHT AND LICENSE
362              
363             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
364              
365             This is free software; you can redistribute it and/or modify it under
366             the same terms as the Perl 5 programming language system itself.
367              
368             =cut