use 5.006;
use strict;
use warnings;
package Dist::Zilla::Util::ConfigDumper;
our $VERSION = '0.003009';
# ABSTRACT: A Dist::Zilla plugin configuration extraction utility
our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
use Carp qw( croak );
use Try::Tiny qw( try catch );
use Sub::Exporter::Progressive -setup => { exports => [qw( config_dumper dump_plugin )], };
sub config_dumper {
my ( $package, @methodnames ) = @_;
if ( not defined $package or ref $package ) {
## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
croak('config_dumper(__PACKAGE__, @recipie ): Arg 1 must not be ref or undef');
## use critic
}
my (@tests) = map { _mk_test( $package, $_ ) } @methodnames;
my $CFG_PACKAGE = __PACKAGE__;
return sub {
my ( $orig, $self, @rest ) = @_;
my $cnf = $self->$orig(@rest);
my $payload = {};
my @fails;
for my $test (@tests) {
$test->( $self, $payload, \@fails );
}
if ( keys %{$payload} ) {
$cnf->{$package} = $payload;
}
if (@fails) {
$cnf->{$CFG_PACKAGE} = {} unless exists $cnf->{$CFG_PACKAGE};
$cnf->{$CFG_PACKAGE}->{$package} = {} unless exists $cnf->{$CFG_PACKAGE};
$cnf->{$CFG_PACKAGE}->{$package}->{failed} = \@fails;
}
return $cnf;
};
}
sub dump_plugin {
my ($plugin) = @_;
my $object_config = {};
$object_config->{class} = $plugin->meta->name if $plugin->can('meta') and $plugin->meta->can('name');
$object_config->{name} = $plugin->plugin_name if $plugin->can('plugin_name');
$object_config->{version} = $plugin->VERSION if $plugin->can('VERSION');
if ( $plugin->can('dump_config') ) {
my $finder_config = $plugin->dump_config;
$object_config->{config} = $finder_config if keys %{$finder_config};
}
return $object_config;
}
sub _mk_method_test {
my ( undef, $methodname ) = @_;
return sub {
my ( $instance, $payload, $fails ) = @_;
try {
my $value = $instance->$methodname();
$payload->{$methodname} = $value;
}
catch {
push @{$fails}, $methodname;
};
};
}
sub _mk_attribute_test {
my ( undef, $attrname ) = @_;
return sub {
my ( $instance, $payload, $fails ) = @_;
try {
my $metaclass = $instance->meta;
my $attribute_metaclass = $metaclass->find_attribute_by_name($attrname);
if ( $attribute_metaclass->has_value($instance) ) {
$payload->{$attrname} = $attribute_metaclass->get_value($instance);
}
}
catch {
push @{$fails}, $attrname;
};
};
}
sub _mk_hash_test {
my ( $package, $hash ) = @_;
my @out;
if ( exists $hash->{attrs} and 'ARRAY' eq ref $hash->{attrs} ) {
push @out, map { _mk_attribute_test( $package, $_ ) } @{ $hash->{attrs} };
}
return @out;
}
sub _mk_test {
my ( $package, $methodname ) = @_;
return _mk_method_test( $package, $methodname ) if not ref $methodname;
return $methodname if 'CODE' eq ref $methodname;
return _mk_hash_test( $package, $methodname ) if 'HASH' eq ref $methodname;
croak "Don't know what to do with $methodname";
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Dist::Zilla::Util::ConfigDumper - A Dist::Zilla plugin configuration extraction utility
=head1 VERSION
version 0.003009
=head1 SYNOPSIS
...
with 'Dist::Zilla::Role::Plugin';
use Dist::Zilla::Util::ConfigDumper qw( config_dumper );
around dump_config => config_dumper( __PACKAGE__, qw( foo bar baz ) );
=head1 DESCRIPTION
This module contains a utility function for use within the C<Dist::Zilla>
plugin ecosystem, to simplify extraction of plugin settings for plugin
authors, in order for plugins like C<Dist::Zilla::Plugin::MetaConfig> to expose
those values to consumers.
Primarily, it specializes in:
=over 4
=item * Making propagating configuration from the plugins inheritance hierarchy
nearly foolproof.
=item * Providing simple interfaces to extract values of lists of named methods
or accessors
=item * Providing a way to intelligently and easily probe the value of lazy
attributes without triggering their vivification.
=back
=head1 FUNCTIONS
=head2 C<config_dumper>
config_dumper( __PACKAGE__, qw( method list ) );
Returns a function suitable for use with C<around dump_config>.
my $sub = config_dumper( __PACKAGE__, qw( method list ) );
around dump_config => $sub;
Or
around dump_config => sub {
my ( $orig, $self, @args ) = @_;
return config_dumper(__PACKAGE__, qw( method list ))->( $orig, $self, @args );
};
Either way:
my $function = config_dumper( $package_name_for_config, qw( methods to call on $self ));
my $hash = $function->( $function_that_returns_a_hash, $instance_to_call_methods_on, @somethinggoeshere );
=~ All of this approximates:
around dump_config => sub {
my ( $orig , $self , @args ) = @_;
my $conf = $self->$orig( @args );
my $payload = {};
for my $method ( @methods ) {
try {
$payload->{ $method } = $self->$method();
};
}
$config->{+__PACKAGE__} = $payload;
}
Except with some extra "things dun goofed" handling.
=head2 C<dump_plugin>
This function serves the other half of the equation, emulating C<dzil>'s own
internal behavior for extracting the C<plugin> configuration data.
for my $plugin ( @{ $zilla->plugins } ) {
pp( dump_plugin( $plugin )); # could prove useful somewhere.
}
Its not usually something you need, but its useful in:
=over 4
=item * Tests
=item * Crazy Stuff like injecting plugins
=item * Crazy Stuff like having "Child" plugins
=back
This serves to be a little more complicated than merely calling C<< ->dump_config >>,
as the structure C<dzil> uses is:
{
class => ...
name => ...
version => ...
config => $dump_config_results_here
}
And of course, there's a bunch of magic stuff with C<meta>, C<can> and C<if keys %$configresults>
All that insanity is wrapped in this simple interface.
=head1 ADVANCED USE
=head2 CALLBACKS
Internally
config_dumper( $pkg, qw( method list ) );
Maps to a bunch of subs, so its more like:
config_dumper( $pkg, sub {
my ( $instance, $payload ) = @_;
$payload->{'method'} = $instance->method;
}, sub {
$_[1]->{'list'} = $_[0]->list;
});
So if you want to use that because its more convenient for some problem, be my guest.
around dump_config => config_dumper( __PACKAGE__, sub {
$_[1]->{'x'} = 'y'
});
is much less ugly than
around dump_config => sub {
my ( $orig, $self, @args ) = @_;
my $conf = $self->$orig(@args);
$config->{+__PACKAGE__} = { # if you forget the +, things break
'x' => 'y'
};
return $config;
};
=head2 DETAILED CONFIGURATION
There's an additional feature for advanced people:
config_dumper( $pkg, \%config );
=head3 C<attrs>
config_dumper( $pkg, { attrs => [qw( foo bar baz )] });
This is for cases where you want to deal with C<Moose> attributes,
but want added safety of B<NOT> loading attributes that have no value yet.
For each item in C<attrs>, we'll call C<Moose> attribute internals to determine
if the attribute named has a value, and only then will we fetch it.
=head1 AUTHOR
Kent Fredric <kentnl@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut