File Coverage

blib/lib/Sub/Exporter/Util.pm
Criterion Covered Total %
statement 72 88 81.8
branch 23 28 82.1
condition 4 4 100.0
subroutine 16 20 80.0
pod 5 6 83.3
total 120 146 82.1


line stmt bran cond sub pod time code
1 4     4   62074 use strict;
  4         15  
  4         102  
2 4     4   18 use warnings;
  4         6  
  4         155  
3             package Sub::Exporter::Util;
4             # ABSTRACT: utilities to make Sub::Exporter easier
5             $Sub::Exporter::Util::VERSION = '0.988';
6 4     4   429 use Data::OptList ();
  4         8465  
  4         48  
7 4     4   18 use Params::Util ();
  4         5  
  4         415  
8              
9             #pod =head1 DESCRIPTION
10             #pod
11             #pod This module provides a number of utility functions for performing common or
12             #pod useful operations when setting up a Sub::Exporter configuration. All of the
13             #pod utilities may be exported, but none are by default.
14             #pod
15             #pod =head1 THE UTILITIES
16             #pod
17             #pod =head2 curry_method
18             #pod
19             #pod exports => {
20             #pod some_method => curry_method,
21             #pod }
22             #pod
23             #pod This utility returns a generator which will produce an invocant-curried version
24             #pod of a method. In other words, it will export a method call with the exporting
25             #pod class built in as the invocant.
26             #pod
27             #pod A module importing the code some the above example might do this:
28             #pod
29             #pod use Some::Module qw(some_method);
30             #pod
31             #pod my $x = some_method;
32             #pod
33             #pod This would be equivalent to:
34             #pod
35             #pod use Some::Module;
36             #pod
37             #pod my $x = Some::Module->some_method;
38             #pod
39             #pod If Some::Module is subclassed and the subclass's import method is called to
40             #pod import C, the subclass will be curried in as the invocant.
41             #pod
42             #pod If an argument is provided for C it is used as the name of the
43             #pod curried method to export. This means you could export a Widget constructor
44             #pod like this:
45             #pod
46             #pod exports => { widget => curry_method('new') }
47             #pod
48             #pod This utility may also be called as C, for backwards compatibility.
49             #pod
50             #pod =cut
51              
52             sub curry_method {
53 2     2 1 370 my $override_name = shift;
54             sub {
55 4     4   9 my ($class, $name) = @_;
56 4 100       7 $name = $override_name if defined $override_name;
57 4     4   16 sub { $class->$name(@_); };
  4         3381  
58             }
59 2         13 }
60              
61 4     4   1702 BEGIN { *curry_class = \&curry_method; }
62              
63             #pod =head2 curry_chain
64             #pod
65             #pod C behaves like C>, but is meant for generating
66             #pod exports that will call several methods in succession.
67             #pod
68             #pod exports => {
69             #pod reticulate => curry_chain(
70             #pod new => gather_data => analyze => [ detail => 100 ] => 'results'
71             #pod ),
72             #pod }
73             #pod
74             #pod If imported from Spliner, calling the C routine will be equivalent
75             #pod to:
76             #pod
77             #pod Spliner->new->gather_data->analyze(detail => 100)->results;
78             #pod
79             #pod If any method returns something on which methods may not be called, the routine
80             #pod croaks.
81             #pod
82             #pod The arguments to C form an optlist. The names are methods to be
83             #pod called and the arguments, if given, are arrayrefs to be dereferenced and passed
84             #pod as arguments to those methods. C returns a generator like those
85             #pod expected by Sub::Exporter.
86             #pod
87             #pod B at present, there is no way to pass arguments from the generated
88             #pod routine to the method calls. This will probably be solved in future revisions
89             #pod by allowing the opt list's values to be subroutines that will be called with
90             #pod the generated routine's stack.
91             #pod
92             #pod =cut
93              
94             sub curry_chain {
95             # In the future, we can make \%arg an optional prepend, like the "special"
96             # args to the default Sub::Exporter-generated import routine.
97 3     3 1 1327 my (@opt_list) = @_;
98              
99 3         13 my $pairs = Data::OptList::mkopt(\@opt_list, 'args', 'ARRAY');
100              
101             sub {
102 3     3   13 my ($class) = @_;
103              
104             sub {
105 3         9 my $next = $class;
106              
107 3         25 for my $i (0 .. $#$pairs) {
108 8         38 my $pair = $pairs->[ $i ];
109            
110 8 100       25 unless (Params::Util::_INVOCANT($next)) { ## no critic Private
111 2 100       14 my $str = defined $next ? "'$next'" : 'undef';
112 2         182 Carp::croak("can't call $pair->[0] on non-invocant $str")
113             }
114              
115 6         71 my ($method, $args) = @$pair;
116              
117 6 100       14 if ($i == $#$pairs) {
118 1 50       3 return $next->$method($args ? @$args : ());
119             } else {
120 5 100       22 $next = $next->$method($args ? @$args : ());
121             }
122             }
123 3         11 };
124             }
125 3         168 }
126              
127             # =head2 name_map
128             #
129             # This utility returns an list to be used in specify export generators. For
130             # example, the following:
131             #
132             # exports => {
133             # name_map(
134             # '_?_gen' => [ qw(fee fie) ],
135             # '_make_?' => [ qw(foo bar) ],
136             # ),
137             # }
138             #
139             # is equivalent to:
140             #
141             # exports => {
142             # name_map(
143             # fee => \'_fee_gen',
144             # fie => \'_fie_gen',
145             # foo => \'_make_foo',
146             # bar => \'_make_bar',
147             # ),
148             # }
149             #
150             # This can save a lot of typing, when providing many exports with similarly-named
151             # generators.
152             #
153             # =cut
154             #
155             # sub name_map {
156             # my (%groups) = @_;
157             #
158             # my %map;
159             #
160             # while (my ($template, $names) = each %groups) {
161             # for my $name (@$names) {
162             # (my $export = $template) =~ s/\?/$name/
163             # or Carp::croak 'no ? found in name_map template';
164             #
165             # $map{ $name } = \$export;
166             # }
167             # }
168             #
169             # return %map;
170             # }
171              
172             #pod =head2 merge_col
173             #pod
174             #pod exports => {
175             #pod merge_col(defaults => {
176             #pod twiddle => \'_twiddle_gen',
177             #pod tweak => \&_tweak_gen,
178             #pod }),
179             #pod }
180             #pod
181             #pod This utility wraps the given generator in one that will merge the named
182             #pod collection into its args before calling it. This means that you can support a
183             #pod "default" collector in multiple exports without writing the code each time.
184             #pod
185             #pod You can specify as many pairs of collection names and generators as you like.
186             #pod
187             #pod =cut
188              
189             sub merge_col {
190 1     1 1 508 my (%groups) = @_;
191              
192 1         2 my %merged;
193              
194 1         12 while (my ($default_name, $group) = each %groups) {
195 3         10 while (my ($export_name, $gen) = each %$group) {
196             $merged{$export_name} = sub {
197 5     5   8 my ($class, $name, $arg, $col) = @_;
198              
199             my $merged_arg = exists $col->{$default_name}
200 5 100       9 ? { %{ $col->{$default_name} }, %$arg }
  3         15  
201             : $arg;
202              
203 5 100       10 if (Params::Util::_CODELIKE($gen)) { ## no critic Private
204 4         17 $gen->($class, $name, $merged_arg, $col);
205             } else {
206 1         4 $class->$$gen($name, $merged_arg, $col);
207             }
208             }
209 5         32 }
210             }
211              
212 1         9 return %merged;
213             }
214              
215             #pod =head2 mixin_installer
216             #pod
217             #pod use Sub::Exporter -setup => {
218             #pod installer => Sub::Exporter::Util::mixin_installer,
219             #pod exports => [ qw(foo bar baz) ],
220             #pod };
221             #pod
222             #pod This utility returns an installer that will install into a superclass and
223             #pod adjust the ISA importing class to include the newly generated superclass.
224             #pod
225             #pod If the target of importing is an object, the hierarchy is reversed: the new
226             #pod class will be ISA the object's class, and the object will be reblessed.
227             #pod
228             #pod B: This utility requires that Package::Generator be installed.
229             #pod
230             #pod =cut
231              
232             sub __mixin_class_for {
233 0     0   0 my ($class, $mix_into) = @_;
234 0         0 require Package::Generator;
235 0         0 my $mixin_class = Package::Generator->new_package({
236             base => "$class\:\:__mixin__",
237             });
238              
239             ## no critic (ProhibitNoStrict)
240 4     4   40 no strict 'refs';
  4         7  
  4         1750  
241 0 0       0 if (ref $mix_into) {
242 0         0 unshift @{"$mixin_class" . "::ISA"}, ref $mix_into;
  0         0  
243             } else {
244 0         0 unshift @{"$mix_into" . "::ISA"}, $mixin_class;
  0         0  
245             }
246 0         0 return $mixin_class;
247             }
248              
249             sub mixin_installer {
250             sub {
251 0     0   0 my ($arg, $to_export) = @_;
252              
253 0         0 my $mixin_class = __mixin_class_for($arg->{class}, $arg->{into});
254 0 0       0 bless $arg->{into} => $mixin_class if ref $arg->{into};
255              
256 0         0 Sub::Exporter::default_installer(
257             { %$arg, into => $mixin_class },
258             $to_export,
259             );
260 0     0 1 0 };
261             }
262              
263             sub mixin_exporter {
264 0     0 0 0 Carp::cluck "mixin_exporter is deprecated; use mixin_installer instead; it behaves identically";
265 0         0 return mixin_installer;
266             }
267              
268             #pod =head2 like
269             #pod
270             #pod It's a collector that adds imports for anything like given regex.
271             #pod
272             #pod If you provide this configuration:
273             #pod
274             #pod exports => [ qw(igrep imap islurp exhausted) ],
275             #pod collectors => { -like => Sub::Exporter::Util::like },
276             #pod
277             #pod A user may import from your module like this:
278             #pod
279             #pod use Your::Iterator -like => qr/^i/; # imports igre, imap, islurp
280             #pod
281             #pod or
282             #pod
283             #pod use Your::Iterator -like => [ qr/^i/ => { -prefix => 'your_' } ];
284             #pod
285             #pod The group-like prefix and suffix arguments are respected; other arguments are
286             #pod passed on to the generators for matching exports.
287             #pod
288             #pod =cut
289              
290             sub like {
291             sub {
292 6     6   1033 my ($value, $arg) = @_;
293 6 100       130 Carp::croak "no regex supplied to regex group generator" unless $value;
294              
295             # Oh, qr//, how you bother me! See the p5p thread from around now about
296             # fixing this problem... too bad it won't help me. -- rjbs, 2006-04-25
297 5 100       6 my @values = eval { $value->isa('Regexp') } ? ($value, undef)
  5         35  
298             : @$value;
299              
300 5         18 while (my ($re, $opt) = splice @values, 0, 2) {
301             Carp::croak "given pattern for regex group generater is not a Regexp"
302 6 100       8 unless eval { $re->isa('Regexp') };
  6         109  
303 5         8 my @exports = keys %{ $arg->{config}->{exports} };
  5         15  
304 5         6 my @matching = grep { $_ =~ $re } @exports;
  25         80  
305              
306 5 100       14 my %merge = $opt ? %$opt : ();
307 5   100     17 my $prefix = (delete $merge{-prefix}) || '';
308 5   100     15 my $suffix = (delete $merge{-suffix}) || '';
309              
310 5         10 for my $name (@matching) {
311 13         21 my $as = $prefix . $name . $suffix;
312 13         14 push @{ $arg->{import_args} }, [ $name => { %merge, -as => $as } ];
  13         48  
313             }
314             }
315              
316 4         15 1;
317             }
318 2     2 1 3804 }
319              
320 4         49 use Sub::Exporter -setup => {
321             exports => [ qw(
322             like
323             name_map
324             merge_col
325             curry_method curry_class
326             curry_chain
327             mixin_installer mixin_exporter
328             ) ]
329 4     4   552 };
  4         8  
330              
331             1;
332              
333             __END__