allow optional renaming of exported generator
[p5sagit/Package-Variant.git] / lib / Package / Variant.pm
CommitLineData
236a4386 1package Package::Variant;
2
3use strictures 1;
4
5our %Variable;
6
7sub import {
8 my $target = caller;
9 my $me = shift;
10 my $last = (split '::', $target)[-1];
11 my $anon = 'A000';
12 my $variable = $target;
13 my %args = @_;
14 no strict 'refs';
15 $Variable{$variable} = {
16 anon => $anon,
17 args => \%args,
18 subs => {
19 map +($_ => sub {}), @{$args{subs}||[]},
20 },
21 };
22 *{"${target}::import"} = sub {
23 my $target = caller;
0a7db8d2 24 my (undef, %arg) = @_;
25 my $as = defined($arg{as}) ? $arg{as} : $last;
236a4386 26 no strict 'refs';
0a7db8d2 27 *{"${target}::${as}"} = sub {
236a4386 28 $me->build_variant_of($variable, @_);
29 };
30 };
31 my $subs = $Variable{$variable}{subs};
32 foreach my $name (keys %$subs) {
33 *{"${target}::${name}"} = sub {
34 goto &{$subs->{$name}}
35 };
36 }
37 *{"${target}::install"} = sub {
38 goto &{$Variable{$variable}{install}};
39 }
40}
41
42sub build_variant_of {
43 my ($me, $variable, @args) = @_;
44 my $variant_name = "${variable}::_Variant_".++$Variable{$variable}{anon};
1abbe9d7 45 my $import = $Variable{$variable}{args}{importing} || {};
46 my $setup = join("\n",
47 "package ${variant_name};",
48 (map sprintf(
49 q!use %s @{$import->{'%s'}||[]};!, $_, quotemeta($_),
50 ), keys %$import),
51 "1;",
52 );
236a4386 53 eval $setup
54 or die "evaling ${setup} failed: $@";
55 my $subs = $Variable{$variable}{subs};
56 local @{$subs}{keys %$subs} = map $variant_name->can($_), keys %$subs;
57 local $Variable{$variable}{install} = sub {
58 my ($name, $ref) = @_;
59 no strict 'refs';
60 *{"${variant_name}::${name}"} = $ref;
61 };
62 $variable->make_variant($variant_name, @args);
63 return $variant_name;
64}
65
661;
0c378352 67
68__END__
69
70=head1 NAME
71
72Package::Variant - Parameterizable packages
73
74=head1 SYNOPSIS
75
76 # declaring a variable Moo role
77 package My::Role::ObjectAttr;
78 use strictures 1;
79 use Package::Variant
80 # what modules to 'use'
81 importing => { 'Moo::Role' => [] },
82 # proxied subroutines
83 subs => [qw( has around before after extends )],
84
85 sub make_variant {
86 my ($class, $target_package, %arguments) = @_;
87 # access arguments
88 my $name = $arguments{name};
89 # use proxied 'has' to add an attribute
90 has $name => (is => 'lazy');
91 # install a builder method
92 install "_build_${name}" => sub {
93 return $arguments{class}->new;
94 };
95 }
96
97 # using the role
98 package My::Class::WithObjectAttr;
99 use strictures 1;
100 use Moo;
101 use My::Role::ObjectAttr;
102
103 with ObjectAttr(name => 'some_obj', class => 'Some::Class');
104
105 # using our class
106 my $obj = My::Class::WithObjectAttr->new;
107 $obj->some_obj; # returns a Some::Class instance
108
109=head1 DESCRIPTION
110
111This module allows you to build packages that return different variations
112depending on what parameters are given.
113
114Users of your package will receive a subroutine able to take parameters
115and return the name of a suitable variant package. The implmenetation does
116not care about what kind of package it builds.
117
118=head2 Declaring a variable package
119
120There are two important parts to creating a variable package. You first
121have to give C<Package::Variant> some basic information about what kind of
122package you want to provide, and how. The second part is implementing a
123method receiving the user's arguments and generating your variants.
124
125=head3 Setting up the environment for building variations
126
127When you C<use Package::Variant>, you pass along some arguments that
128describe how you intend to build your variations.
129
130 use Package::Variant
131 importing => { $package => \@import_arguments, ... },
132 subs => [ @proxied_subroutine_names ];
133
134The L</importing> option needs to be a hash reference with package names
135to be C<use>d as keys, and array references containing the import
136arguments as values. These packages will be imported into every new
137variant, and need to set up every declarative subroutine you require to
138build your variable package. The next option will allow you to use these
139functions.
140
141The L</subs> option is an array reference of subroutine names that are
142exported by the packages specified with L</importing>. These subroutines
143will be proxied from your declaration package to the variant to be
144generated.
145
146With L</importing> initializing your package and L</subs> declaring what
147subroutines you want to use to build a variant, you can now write a
148L</make_variant> method building your variants.
149
150=head3 Declaring a method to produce variants
151
152Every time a user requests a new variant a method named L</make_variant>
153will be called with the name of the target package and the arguments from
154the user.
155
156It can then use the proxied subroutines declared with L</subs> to
157customize the new package. An L</install> subroutine is exported as well
158allowing you to dynamically install methods into the new package. If these
159options aren't flexible enough, you can use the passed name of the new
160package to do any other kind of customizations.
161
162 sub make_variant {
163 my ($class, $target, @arguments) = @_;
164 # ...
165 # customization goes here
166 # ...
167 }
168
169When the method is finished, the user will receive the name of the new
170package variant you just set up.
171
172=head2 Using variable packages
173
174After your variable package is L<created|/Declaring a variable package>
175your users can get a variant generating subroutine by simply importing
176your package.
177
178 use My::Variant;
179 my $new_variant_package = Variant( @variant_arguments );
180
0a7db8d2 181The package is now fully initialized and used. You can import the
182subroutine under a different name by specifying an C<as> argument.
0c378352 183
184=head2 Dynamic creation of variant packages
185
186For regular uses, the L<normal import|/Using variable packages> provides
187more than enough flexibility. However, if you want to create variations of
188dynamically determined packages, you can use the L</build_variation_of>
189method.
190
191You can use this to create variations of other packages and pass arguments
192on to them to allow more modular and extensible variations.
193
194=head1 OPTIONS
195
196These are the options that can be passed when importing
197C<Package::Variant>. They describe the environment in which the variants
198are created.
199
200 use Package::Variant
201 importing => { $package => \@import_arguments, ... },
202 subs => [ @proxied_subroutines ];
203
204=head2 importing
205
206This option is a hash reference mapping package names to array references
207containing import arguments. The packages will be C<use>d with the given
208arguments by every variation before the L</make_variant> method is asked
209to create the package.
210
211=head2 subs
212
213An array reference of strings listing the names of subroutines that should
214be proxied. These subroutines are expected to be installed into the new
215variant package by the modules imported with L</importing>. Subroutines
216with the same name will be availabe in your declaration package, and will
217proxy through to the newly created package when used within
218L</make_variant>.
219
220=head1 VARIABLE PACKAGE METHODS
221
222These are methods on the variable package you declare when you import
223C<Package::Variant>.
224
225=head2 make_variant
226
227 Some::Variant::Package->make_variant( $target, @arguments );
228
229B<You need to provide this method.> This method will be called for every
230new variant of your package. This method should use the subroutines
231declared in L</subs> to customize the new variant package.
232
233This is a class method receiving the C<$target> package and the
234C<@arguments> defining the requested variant.
235
236=head2 import
237
238 use Some::Variant::Package;
239 my $variant_package = Package( @arguments );
240
241This method is provided for you. It will allow a user to C<use> your
242package and receive a subroutine taking C<@arguments> defining the variant
243and returning the name of the newly created variant package.
244
0a7db8d2 245The following options can be specified when importing:
246
247=over
248
249=item * B<as>
250
251 use Some::Variant::Package as => 'Foo';
252 my $variant_package = Foo( @arguments );
253
254Exports the generator subroutine under a different name than the default.
255
256=back
257
0c378352 258=head1 C<Package::Variant> METHODS
259
260These methods are available on C<Package::Variant> itself.
261
262=head2 build_variation_of
263
264 my $variant_package = Package::Variant
265 ->build_variation_of( $variable_package, @arguments );
266
267This is the dynamic method of creating new variants. It takes the
268C<$variable_package>, which is a pre-declared variable package, and a set
269of C<@arguments> passed to the package to generate a new
270C<$variant_package>, which will be returned.
271
272=head2 import
273
274 use Package::Variant @options;
275
276Sets up the environment in which you declare the variants of your
277packages. See L</OPTIONS> for details on the available options and
278L</EXPORTS> for a list of exported subroutines.
279
280=head1 EXPORTS
281
282Additionally to the proxies for subroutines provided in L</subs>, the
283following exports will be available in your variable package:
284
285=head2 install
286
287 install( $method_name, $code_reference );
288
289Installs a method with the given C<$method_name> into the newly created
290variant package. The C<$code_reference> will be used as the body for the
291method.
292
293=head1 AUTHOR
294
295=over
296
297=item mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
298
299=back
300
301=head1 COPYRIGHT
302
303Copyright (c) 2010-2011 the C<Package::Stash> L</AUTHOR> as listed above.
304
305=head1 LICENSE
306
307This library is free software and may be distributed under the same
308terms as perl itself.
309
310=cut