File Coverage

blib/lib/CatalystX/Imports/Context.pm
Criterion Covered Total %
statement 109 129 84.5
branch 22 36 61.1
condition 7 21 33.3
subroutine 19 23 82.6
pod 6 6 100.0
total 163 215 75.8


line stmt bran cond sub pod time code
1             package CatalystX::Imports::Context;
2              
3             =head1 NAME
4              
5             CatalystX::Imports::Context - Exports Context Helper Functions
6              
7             =cut
8              
9 1     1   4 use warnings;
  1         2  
  1         25  
10 1     1   16 use strict;
  1         4  
  1         24  
11              
12             =head1 BASE CLASSES
13              
14             L<CatalystX::Imports>
15              
16             =cut
17              
18 1     1   4 use base 'CatalystX::Imports';
  1         1  
  1         332  
19 1     1   733 use vars qw( $EXPORT_MAP_NAME $DEFAULT_LIBRARY );
  1         2  
  1         48  
20              
21 1     1   431 use List::MoreUtils qw( part apply uniq );
  1         6455  
  1         8  
22 1     1   600 use Scalar::Util qw( set_prototype );
  1         2  
  1         76  
23 1     1   4 use Carp::Clan qw{ ^CatalystX::Imports(?:::|$) };
  1         1  
  1         6  
24 1     1   126 use Filter::EOF;
  1         1  
  1         5  
25              
26             $EXPORT_MAP_NAME = 'CATALYSTX_IMPORTS_EXPORT_MAP';
27             $DEFAULT_LIBRARY = __PACKAGE__ . '::Default';
28              
29             =head1 SYNOPSIS
30              
31             package MyApp::Controller::Foo;
32             use base 'Catalyst::Controller';
33              
34             # Export everything minus the 'captures' function. Also load
35             # the additional 'Foo' library and a config value
36             use CatalystX::Imports
37             Context => {
38             Default => [qw( :all -captures +Foo )],
39             Config => [qw( model_name )],
40             };
41              
42             sub list: Local {
43             stash( rs => model(model_name)->search_rs );
44             }
45              
46             sub edit: Local {
47             stash(
48             foo => model(model_name)->find(args->[0]),
49             list_uri => uri_for(action('list')),
50             );
51             }
52              
53             1;
54              
55             =head1 DESCRIPTION
56              
57             This package represents the base class and export manager for all
58             libraries. The default library can be found under the package name
59             L<CatalystX::Imports::Context::Default>.
60              
61             The exports will be removed after compiletime. By then, the calls
62             to them in your controller will already be bound to the right code
63             slots by perl. This keeps these functions from being available as
64             methods on your controller object.
65              
66             =head1 IMPORT SYNTAX
67              
68             You can specify what library parts you want to import into your
69             controller on the C<use> line to L<CatalystX::Imports>:
70              
71             use CatalystX::Imports Context => [qw(:all -captures +Foo)];
72              
73             This would import all functions from the default library
74             L<CatalystX::Imports::Context::Default>, except the C<captures> function.
75             See L<CatalystX::Imports::Context::Default/Tags> for all available tags
76             in the default library.
77              
78             Additionally, it will search and load the C<Foo> library, which would be
79             C<CatalystX::Imports::Context::Foo>. This notation doesn't accept any
80             arguments, so the library specific default symbols will be exported.
81              
82             If you just want some specific functions imported, you can also specify
83             them explicitly:
84              
85             use CatalystX::Imports
86             Context => [qw(action uri_for model config stash)];
87              
88             At last, to be specific about more than one library, you can pass a
89             hash reference:
90              
91             use CatalystX::Imports
92             Context => { Default => ':all', Config => [qw(model_name)] };
93              
94             See the libraries documentation for further syntax information.
95              
96             =head1 ALIASES
97              
98             If documented, you can also import a function with one of it's aliases.
99             If you import a function via a tag, it will only be exported under its
100             real name, not its aliased names. Therefor, to use an aliase you have
101             to specify aliases explicitly at any time to use them:
102              
103             # load aliases for short forms of 'request' and 'response'
104             use CatalystX::Imports Context => [qw( req res )];
105              
106             =head1 INCLUDED LIBRARIES
107              
108             =over
109              
110             =item L<CatalystX::Imports::Context::Default>
111              
112             Contains default shortcuts and inline accessors.
113              
114             =item L<CatalystX::Imports::Context::Config>
115              
116             Allows you to import local controller (instance) configuration accessors
117             as inline functions into your namespace.
118              
119             =back
120              
121             =cut
122              
123             =head1 METHODS
124              
125             =cut
126              
127             =head2 register_export
128              
129             This method registers a new export in the library it's called upon. You
130             will mostly only need this function for creating your own libraries:
131              
132             package CatalystX::Imports::Context::MyOwnLibrary;
133             use base 'CatalystX::Imports::Context';
134              
135             __PACKAGE__->register_export(
136             name => 'double',
137             alias => 'times_two',
138             prototype => '$',
139             tags => [qw( math )],
140             code => sub {
141             my ($library, $ctrl, $ctx, $action_args, @args) = @_;
142             return $args[0] * 2;
143             },
144             );
145              
146             The C<code> and C<name> parameters are mandatory. If you specify an
147             alias, it can be imported explicitly, but will not be included in the
148             C<:all> tag.
149              
150             The prototype is the usual prototpe you could stuff on perl subroutines.
151             If you specify tags as an array reference, the export will be included
152             in those tag sets by it's name and aliases. It will be included in the
153             C<:all> tag in any case, but only under it's name, not it's aliases.
154              
155             The specified code reference will get the library class name, the
156             controller and context objects (like a L<Catalyst> action), and an array
157             reference of the arguments passed to the last action and then it's
158             own actual arguments passed in. You could call the above with
159              
160             double(23); # 46
161              
162             =cut
163              
164             sub register_export {
165 19     19 1 105 my ($class, @args) = @_;
166              
167             # we expect pairs of option keys and values as arguments
168 19 50       33 croak 'register_export expects key/value pairs as arguments'
169             if @args % 2;
170 19         28 my %options = @args;
171              
172             # check if every required option is there
173 19         19 for my $required (qw( code name )) {
174             croak "register_export: Missing required parameter: '$required'"
175             unless exists $options{ $required }
176 38 50 33     117 and defined $options{ $required };
177             }
178              
179             # optionals
180 19 100       15 my @tags = @{ $options{tags} || [] };
  19         39  
181 19 100       12 my @aliases = @{ $options{alias} || [] };
  19         52  
182              
183             # get the export map, we'll need it
184 19         24 my $export_map = $class->_export_map;
185              
186             # walk the names we want to register this under
187 19         19 for my $name ($options{name}, @aliases) {
188              
189             # register in tags, only name goes into :all by default
190 22 100       48 for my $t (uniq @tags, ($options{name} eq $name ? 'all' : ())) {
191 47   100     167 push @{ $export_map->{tag}{ $t } ||= [] }, $name;
  47         103  
192             }
193              
194             # save export information
195             $export_map->{export}{ $name } = {
196             name => $name,
197             code => $options{code},
198             ( exists $options{prototype}
199             ? ( prototype => $options{prototype} )
200 22 100       65 : () ),
201             };
202             }
203              
204 19         64 return 1;
205             }
206              
207             =head2 _export_map
208              
209             Returns the libraries export map as a hash reference. This will be stored
210             in your library class (if you build your own, otherwise you don't have to
211             care) in the C<%CATALYSTX_IMPORTS_EXPORT_MAP> package variable.
212              
213             =cut
214              
215             sub _export_map {
216 39     39   27 my ($class) = @_;
217 39         40 my $map_name = "${class}::${EXPORT_MAP_NAME}";
218 1     1   232 { no strict 'refs';
  1         1  
  1         24  
  39         32  
219 1     1   3 no warnings 'once';
  1         1  
  1         509  
220 39         28 return \%{ $map_name };
  39         110  
221             }
222             }
223              
224             =head2 get_export
225              
226             Expects the name of an export in the library and will return the
227             information it stored with it. An export will be stored under its actual
228             name as well as its aliases.
229              
230             =cut
231              
232 19     19 1 30 sub get_export { $_[0]->_export_map->{export}{ $_[1] } }
233              
234             =head2 export_into
235              
236             Called by L<CatalystX::Imports>' C<import> method. Takes a target and a
237             set of commands specified in L</IMPORT SYNTAX>. This will forward the
238             commands to the actual libraries and the L</context_export_into> method
239             in them.
240              
241             =cut
242              
243             sub export_into {
244 1     1 1 3 my ($class, $target, @args) = @_;
245 1         1 my %args;
246              
247             # we accept lists and array refs for default, and explicit
248             # hash refs for more control
249 1 50 33     18 if (@args == 1 and ref $args[0] eq 'ARRAY') {
    50 33        
250 0         0 %args = (Default => $args[0]);
251             }
252             elsif (@args == 1 and ref $args[0] eq 'HASH') {
253 0         0 %args = %{ $args[0] };
  0         0  
254             ref($args{ $_ }) eq 'ARRAY' or $args{ $_ } = [ $args{ $_ } ]
255 0   0     0 for keys %args;
256             }
257             else {
258 1         3 %args = (Default => \@args);
259             }
260              
261             # filter out additional libraries in Default arguments
262 1 50       9 my @default_args = @{ delete($args{Default}) || [] };
  1         7  
263 1         1 my %load_default;
264 1         4 for my $arg (@default_args) {
265 1 50       3 if ($arg =~ /^[+](.+)$/) {
266 0 0       0 next unless exists $args{ $1 };
267 0   0     0 $args{ $1 } ||= [];
268 0         0 $load_default{ $1 } = 1;
269 0         0 next;
270             }
271 1   50     1 push @{ $args{Default} ||= [] }, $arg;
  1         10  
272             }
273              
274             # load libraries and export symbols
275 1         3 for my $lib (keys %args) {
276 1         2 my $lib_class = __PACKAGE__ . '::' . $lib;
277 1         6 $class->_ensure_class_loaded($lib_class);
278 1         1 my @symbols = @{ $args{ $lib } };
  1         4  
279             push @symbols, $lib_class->default_exports
280 1 50       2 if $load_default{ $lib };
281 1         2 $lib_class->context_export_into($target, @{ $args{ $lib } });
  1         8  
282             }
283              
284 1         3 return 1;
285             }
286              
287             =head2 context_export_into
288              
289             Takes a target and an actual command set for a library (no C<+Foo> stuff)
290             and cleans that (flattens out tags, removes C<-substractions>). It will
291             utilise L</context_install_export_into> to actually export the final set
292             of functions.
293              
294             =cut
295              
296             sub context_export_into {
297 1     1 1 5 my ($class, $target, @exports) = @_;
298              
299             # part and clean different type of import arguments
300 1         3 my ($export_list, $tags, $substract) = map { [] } 1..3;
  3         8  
301 1         3 for my $export (@exports) {
302 1 50 0     3 push @$substract, $export and next
303             if $export =~ s/^-//;
304 1 50 50     53 push @$tags, $export and next
305             if $export =~ s/^://;
306 0         0 push @$export_list, $export;
307             }
308              
309             # fetch the export map, we're going to use it a bit
310 1         6 my $export_map = $class->_export_map;
311              
312             # resolve tags
313 1         3 for my $tag (@$tags) {
314 1 50       5 my $tag_exports = $export_map->{tag}{ $tag }
315             or croak "Unknown Context tag: ':$tag'";
316 1         9 push @$export_list, @$tag_exports;
317             }
318              
319             # remove doubles and substractions
320 1 50       1 my %substract_map = map { ($_ => 1) } @{ $substract || [] };
  0         0  
  1         4  
321             @$export_list
322 1         7 = grep { not exists $substract_map{ $_ } }
  19         60  
323             uniq @$export_list;
324              
325             # install the exports
326 1         3 for my $export (@$export_list) {
327 19         28 $class->context_install_export_into($target, $export);
328             }
329              
330             # register the exports to be removed after compile time
331             Filter::EOF->on_eof_call(sub {
332 0     0   0 for my $export (@$export_list) {
333 1     1   5 no strict 'refs';
  1         1  
  1         137  
334 0         0 delete ${ $target . '::' }{ $export };
  0         0  
335             }
336 1         9 });
337              
338 1         15 return 1;
339             }
340              
341             =head2 context_install_export_into
342              
343             Takes a target class and the name of an export to install the function
344             in the specified class.
345              
346             =cut
347              
348             sub context_install_export_into {
349 19     19 1 17 my ($class, $target, $export) = @_;
350              
351             # find the export information
352 19 50       24 my $export_info = $class->get_export($export)
353             or croak "Unknown Context export: '$export'";
354 19         13 my ($code, $prototype) = @{ $export_info }{qw( code prototype )};
  19         22  
355              
356             # the wrapper fetches the current objects
357             my $export_code = sub {
358 0     0   0 my ($controller, $context, $arguments) = do {
359 1     1   4 no strict 'refs';
  1         1  
  1         72  
360 0         0 map { ${ "${target}::" . ${ "CatalystX::Imports::$_" } } }
  0         0  
  0         0  
  0         0  
361             qw( STORE_CONTROLLER STORE_CONTEXT STORE_ARGUMENTS );
362             };
363 0         0 return $class->$code($controller, $context, $arguments, @_);
364 19         51 };
365              
366             # install the export, include prototype if specified
367 1     1   4 { no strict 'refs';
  1         1  
  1         118  
  19         14  
368 19         16 my $name = $export_info->{name};
369 19         59 *{ "${target}::${name}" }
370             = defined $prototype
371 19 100   0   33 ? set_prototype sub { $export_code->(@_) }, $prototype
  0         0  
372             : $export_code;
373             }
374              
375 19         23 return 1;
376             }
377              
378             =head2 default_exports
379              
380             Should be overridden by subclasses if they want to export something
381             by default. This will be used if the library is specified without any
382             arguments at all. E.g. this:
383              
384             use CatalystX::Imports Context => [qw( +Foo )];
385              
386             will export C<Foo>'s defaults, but
387              
388             use CatalystX::Imports Context => { Foo => [] };
389              
390             will not. Without an overriding method, the default is set to export
391             nothing at all.
392              
393             =cut
394              
395       0 1   sub default_exports { }
396              
397             =head1 DIAGNOSTICS
398              
399             =head2 register_export expects key/value pairs as arguments
400              
401             You passed an odd number of values into the C<register_export> method
402             call, but it expects key and value pairs of named options. See
403             L>/register_export> for available options and calling syntax.
404              
405             =head2 register_export: Missing required parameter: 'foo'
406              
407             The L</register_export> method expects a few parameters that can't be
408             omitted, including C<foo>. Pass in the parameter as specified in the
409             section about the L</register_export> method.
410              
411             =head2 Unknown Context tag: ':foo'
412              
413             You specified to import the functions in the tag C<:foo> on your C<use>
414             line, but no tag with the name C<:foo> was registered in the library.
415              
416             =head2 Unknown Context export: 'foo'
417              
418             You asked for export of the function C<foo>, but no function under this
419             name was registered in the library. Please consult your library
420             documentation for a list of available exports. The default library can
421             be found under L<CatalystX::Imports::Context::Default>.
422              
423             =head1 SEE ALSO
424              
425             L<Catalyst>,
426             L<Filter::EOF>,
427             L<CatalystX::Imports::Context::Default>,
428             L<CatalystX::Imports::Context::Config>,
429             L<CatalystX::Imports::Vars>,
430             L<CatalystX::Imports>
431              
432             =head1 AUTHOR AND COPYRIGHT
433              
434             Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>
435              
436             =head1 LICENSE
437              
438             This program is free software; you can redistribute it and/or modify
439             it under the same terms as perl itself.
440              
441             =cut
442              
443             1;