File Coverage

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   3278 use 5.006;
  8         36  
2 8     8   44 use strict;
  8         13  
  8         198  
3 8     8   65 use warnings;
  8         26  
  8         703  
4              
5             package Dist::Zilla::Util::ConfigDumper;
6              
7             our $VERSION = '0.003007';
8              
9             # ABSTRACT: A Dist::Zilla plugin configuration extraction utility
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 8     8   72 use Carp qw( croak );
  8         10  
  8         715  
14 8     8   1133 use Try::Tiny qw( try catch );
  8         3505  
  8         526  
15 8     8   984 use Sub::Exporter::Progressive -setup => { exports => [qw( config_dumper dump_plugin )], };
  8         1660  
  8         106  
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 28 my ( $package, @methodnames ) = @_;
59 9 100 66     78 if ( not defined $package or ref $package ) {
60             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
61 1         164 croak('config_dumper(__PACKAGE__, @recipie ): Arg 1 must not be ref or undef');
62             ## use critic
63             }
64              
65 8         17 my (@tests) = map { _mk_test( $package, $_ ) } @methodnames;
  8         28  
66 8         16 my $CFG_PACKAGE = __PACKAGE__;
67             return sub {
68 9     9   592376 my ( $orig, $self, @rest ) = @_;
69 9         40 my $cnf = $self->$orig(@rest);
70 9         32 my $payload = {};
71 9         20 my @fails;
72 9         28 for my $test (@tests) {
73 11         345 $test->( $self, $payload, \@fails );
74             }
75 9 100       871 if ( keys %{$payload} ) {
  9         49  
76 7         21 $cnf->{$package} = $payload;
77             }
78 9 100       30 if (@fails) {
79 1 50       4 $cnf->{$CFG_PACKAGE} = {} unless exists $cnf->{$CFG_PACKAGE};
80 1 50       4 $cnf->{$CFG_PACKAGE}->{$package} = {} unless exists $cnf->{$CFG_PACKAGE};
81 1         2 $cnf->{$CFG_PACKAGE}->{$package}->{failed} = \@fails;
82             }
83 9         32 return $cnf;
84 8         55 };
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   6 my ( undef, $methodname ) = @_;
139             return sub {
140 2     2   4 my ( $instance, $payload, $fails ) = @_;
141             try {
142 2         183 my $value = $instance->$methodname();
143 1         4 $payload->{$methodname} = $value;
144             }
145             catch {
146 1         8 push @{$fails}, $methodname;
  1         9  
147 2         25 };
148 2         18 };
149             }
150              
151             sub _mk_attribute_test {
152 6     6   15 my ( undef, $attrname ) = @_;
153             return sub {
154 8     8   15 my ( $instance, $payload, $fails ) = @_;
155             try {
156 8         333 my $metaclass = $instance->meta;
157 8         178 my $attribute_metaclass = $metaclass->find_attribute_by_name($attrname);
158 8 100       541 if ( $attribute_metaclass->has_value($instance) ) {
159 6         284 $payload->{$attrname} = $attribute_metaclass->get_value($instance);
160             }
161             }
162             catch {
163 0         0 push @{$fails}, $attrname;
  0         0  
164 8         78 };
165 6         40 };
166             }
167              
168             sub _mk_hash_test {
169 5     5   11 my ( $package, $hash ) = @_;
170 5         7 my @out;
171 5 50 33     37 if ( exists $hash->{attrs} and 'ARRAY' eq ref $hash->{attrs} ) {
172 5         26 push @out, map { _mk_attribute_test( $package, $_ ) } @{ $hash->{attrs} };
  6         19  
  5         14  
173             }
174 5         38 return @out;
175             }
176              
177             sub _mk_test {
178 8     8   16 my ( $package, $methodname ) = @_;
179 8 100       48 return _mk_method_test( $package, $methodname ) if not ref $methodname;
180 6 100       26 return $methodname if 'CODE' eq ref $methodname;
181 5 50       26 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.003007
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) 2015 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