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