X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FPackage%2FVariant.pm;h=b0c269893ce2de4737d03b73fc6d5a975de1a9fb;hb=0a7db8d2582be4949a5c321ac449ff6c2e1c1e03;hp=e490bfb757113939a86bb91b97383d299bd59597;hpb=236a4386470d4edcbdda8a7d5564dd04401c7027;p=p5sagit%2FPackage-Variant.git diff --git a/lib/Package/Variant.pm b/lib/Package/Variant.pm index e490bfb..b0c2698 100644 --- a/lib/Package/Variant.pm +++ b/lib/Package/Variant.pm @@ -21,8 +21,10 @@ sub import { }; *{"${target}::import"} = sub { my $target = caller; + my (undef, %arg) = @_; + my $as = defined($arg{as}) ? $arg{as} : $last; no strict 'refs'; - *{"${target}::${last}"} = sub { + *{"${target}::${as}"} = sub { $me->build_variant_of($variable, @_); }; }; @@ -40,8 +42,14 @@ sub import { sub build_variant_of { my ($me, $variable, @args) = @_; my $variant_name = "${variable}::_Variant_".++$Variable{$variable}{anon}; - my @to_import = keys %{$Variable{$variable}{args}{importing}||{}}; - my $setup = join("\n", "package ${variant_name};", (map "use $_;", @to_import), "1;"); + my $import = $Variable{$variable}{args}{importing} || {}; + my $setup = join("\n", + "package ${variant_name};", + (map sprintf( + q!use %s @{$import->{'%s'}||[]};!, $_, quotemeta($_), + ), keys %$import), + "1;", + ); eval $setup or die "evaling ${setup} failed: $@"; my $subs = $Variable{$variable}{subs}; @@ -56,3 +64,247 @@ sub build_variant_of { } 1; + +__END__ + +=head1 NAME + +Package::Variant - Parameterizable packages + +=head1 SYNOPSIS + + # declaring a variable Moo role + package My::Role::ObjectAttr; + use strictures 1; + use Package::Variant + # what modules to 'use' + importing => { 'Moo::Role' => [] }, + # proxied subroutines + subs => [qw( has around before after extends )], + + sub make_variant { + my ($class, $target_package, %arguments) = @_; + # access arguments + my $name = $arguments{name}; + # use proxied 'has' to add an attribute + has $name => (is => 'lazy'); + # install a builder method + install "_build_${name}" => sub { + return $arguments{class}->new; + }; + } + + # using the role + package My::Class::WithObjectAttr; + use strictures 1; + use Moo; + use My::Role::ObjectAttr; + + with ObjectAttr(name => 'some_obj', class => 'Some::Class'); + + # using our class + my $obj = My::Class::WithObjectAttr->new; + $obj->some_obj; # returns a Some::Class instance + +=head1 DESCRIPTION + +This module allows you to build packages that return different variations +depending on what parameters are given. + +Users of your package will receive a subroutine able to take parameters +and return the name of a suitable variant package. The implmenetation does +not care about what kind of package it builds. + +=head2 Declaring a variable package + +There are two important parts to creating a variable package. You first +have to give C some basic information about what kind of +package you want to provide, and how. The second part is implementing a +method receiving the user's arguments and generating your variants. + +=head3 Setting up the environment for building variations + +When you C, you pass along some arguments that +describe how you intend to build your variations. + + use Package::Variant + importing => { $package => \@import_arguments, ... }, + subs => [ @proxied_subroutine_names ]; + +The L option needs to be a hash reference with package names +to be Cd as keys, and array references containing the import +arguments as values. These packages will be imported into every new +variant, and need to set up every declarative subroutine you require to +build your variable package. The next option will allow you to use these +functions. + +The L option is an array reference of subroutine names that are +exported by the packages specified with L. These subroutines +will be proxied from your declaration package to the variant to be +generated. + +With L initializing your package and L declaring what +subroutines you want to use to build a variant, you can now write a +L method building your variants. + +=head3 Declaring a method to produce variants + +Every time a user requests a new variant a method named L +will be called with the name of the target package and the arguments from +the user. + +It can then use the proxied subroutines declared with L to +customize the new package. An L subroutine is exported as well +allowing you to dynamically install methods into the new package. If these +options aren't flexible enough, you can use the passed name of the new +package to do any other kind of customizations. + + sub make_variant { + my ($class, $target, @arguments) = @_; + # ... + # customization goes here + # ... + } + +When the method is finished, the user will receive the name of the new +package variant you just set up. + +=head2 Using variable packages + +After your variable package is L +your users can get a variant generating subroutine by simply importing +your package. + + use My::Variant; + my $new_variant_package = Variant( @variant_arguments ); + +The package is now fully initialized and used. You can import the +subroutine under a different name by specifying an C argument. + +=head2 Dynamic creation of variant packages + +For regular uses, the L provides +more than enough flexibility. However, if you want to create variations of +dynamically determined packages, you can use the L +method. + +You can use this to create variations of other packages and pass arguments +on to them to allow more modular and extensible variations. + +=head1 OPTIONS + +These are the options that can be passed when importing +C. They describe the environment in which the variants +are created. + + use Package::Variant + importing => { $package => \@import_arguments, ... }, + subs => [ @proxied_subroutines ]; + +=head2 importing + +This option is a hash reference mapping package names to array references +containing import arguments. The packages will be Cd with the given +arguments by every variation before the L method is asked +to create the package. + +=head2 subs + +An array reference of strings listing the names of subroutines that should +be proxied. These subroutines are expected to be installed into the new +variant package by the modules imported with L. Subroutines +with the same name will be availabe in your declaration package, and will +proxy through to the newly created package when used within +L. + +=head1 VARIABLE PACKAGE METHODS + +These are methods on the variable package you declare when you import +C. + +=head2 make_variant + + Some::Variant::Package->make_variant( $target, @arguments ); + +B This method will be called for every +new variant of your package. This method should use the subroutines +declared in L to customize the new variant package. + +This is a class method receiving the C<$target> package and the +C<@arguments> defining the requested variant. + +=head2 import + + use Some::Variant::Package; + my $variant_package = Package( @arguments ); + +This method is provided for you. It will allow a user to C your +package and receive a subroutine taking C<@arguments> defining the variant +and returning the name of the newly created variant package. + +The following options can be specified when importing: + +=over + +=item * B + + use Some::Variant::Package as => 'Foo'; + my $variant_package = Foo( @arguments ); + +Exports the generator subroutine under a different name than the default. + +=back + +=head1 C METHODS + +These methods are available on C itself. + +=head2 build_variation_of + + my $variant_package = Package::Variant + ->build_variation_of( $variable_package, @arguments ); + +This is the dynamic method of creating new variants. It takes the +C<$variable_package>, which is a pre-declared variable package, and a set +of C<@arguments> passed to the package to generate a new +C<$variant_package>, which will be returned. + +=head2 import + + use Package::Variant @options; + +Sets up the environment in which you declare the variants of your +packages. See L for details on the available options and +L for a list of exported subroutines. + +=head1 EXPORTS + +Additionally to the proxies for subroutines provided in L, the +following exports will be available in your variable package: + +=head2 install + + install( $method_name, $code_reference ); + +Installs a method with the given C<$method_name> into the newly created +variant package. The C<$code_reference> will be used as the body for the +method. + +=head1 AUTHOR + +=over + +=item mst - Matt S. Trout (cpan:MSTROUT) + +=back + +=head1 COPYRIGHT + +Copyright (c) 2010-2011 the C L as listed above. + +=head1 LICENSE + +This library is free software and may be distributed under the same +terms as perl itself. + +=cut