File Coverage

blib/lib/Sub/Multi/Tiny/Util.pm
Criterion Covered Total %
statement 149 155 96.1
branch 10 20 50.0
condition 3 5 60.0
subroutine 38 40 95.0
pod n/a
total 200 220 90.9


line stmt bran cond sub pod time code
1              
2             use 5.006;
3 19     19   1486070 use strict;
  19         178  
4 19     19   122 use warnings;
  19         40  
  19         363  
5 19     19   244  
  19         46  
  19         608  
6             use Exporter qw(import);
7 19     19   116 use vars::i [
  19         47  
  19         1016  
8             '$VERBOSE' => 0, # Set this to a positive int for extra output on STDERR
9 18         193 '@EXPORT' => [],
10             '@EXPORT_OK' => [qw(_carp _croak _hlog _line_mark_string
11             _make_positional_copier _complete_dispatcher
12             *VERBOSE)],
13             ];
14 19     18   2444 use vars::i '%EXPORT_TAGS' => { all => [@EXPORT, @EXPORT_OK] };
  18         4651  
15 18     18   3183  
  18         58  
  18         113  
16             our $VERSION = '0.000013';
17              
18              
19             # Documentation {{{1
20              
21             =head1 NAME
22              
23             Sub::Multi::Tiny::Util - Internal utilities for Sub::Multi::Tiny
24              
25             =head1 SYNOPSIS
26              
27             Used by L<Sub::Multi::Tiny>.
28              
29             =head1 VARIABLES
30              
31             =head2 $VERBOSE
32              
33             Set this truthy for extra debug output. L<Sub::Multi::Tiny/import> sets this
34             based on environment variable C<SUB_MULTI_TINY_VERBOSE>.
35              
36             =head1 FUNCTIONS
37              
38             =cut
39              
40             # }}}1
41              
42             =head2 _croak
43              
44             As L<Carp/croak>, but lazily loads L<Carp>.
45              
46             =cut
47              
48             require Carp;
49             goto &Carp::croak;
50 0     0   0 }
51 0         0  
52             =head2 _carp
53              
54             As L<Carp/carp>, but lazily loads L<Carp>.
55              
56             =cut
57              
58             require Carp;
59             goto &Carp::carp;
60             }
61 0     0   0  
62 0         0 =head2 _line_mark_string
63              
64             Add a C<#line> directive to a string. Usage:
65              
66             my $str = _line_mark_string <<EOT ;
67             $contents
68             EOT
69              
70             or
71              
72             my $str = _line_mark_string __FILE__, __LINE__, <<EOT ;
73             $contents
74             EOT
75              
76             In the first form, information from C<caller> will be used for the filename
77             and line number.
78              
79             The C<#line> directive will point to the line after the C<_line_mark_string>
80             invocation, i.e., the first line of C<$contents>. Generally, C<$contents> will
81             be source code, although this is not required.
82              
83             C<$contents> must be defined, but can be empty.
84              
85             =cut
86              
87             my ($contents, $filename, $line);
88             if(@_ == 1) {
89             $contents = $_[0];
90             (undef, $filename, $line) = caller;
91 305     305   602 } elsif(@_ == 3) {
92 305 50       715 ($filename, $line, $contents) = @_;
    0          
93 305         472 } else {
94 305         873 _croak "Invalid invocation";
95             }
96 0         0  
97             _croak "Need text" unless defined $contents;
98 0         0 die "Couldn't get location information" unless $filename && $line;
99              
100             $filename =~ s/"/-/g;
101 305 50       759 ++$line;
102 305 50 33     1013  
103             return <<EOT;
104 305         585 #line $line "$filename"
105 305         420 $contents
106             EOT
107 305         2445 } #_line_mark_string()
108              
109             =head2 _hlog
110              
111             Log information if L</$VERBOSE> is set. Usage:
112              
113             _hlog { <list of things to log> } [optional min verbosity level (default 1)];
114              
115             The items in the list are joined by C<' '> on output, and a C<'\n'> is added.
116             Each line is prefixed with C<'# '> for the benefit of test runs.
117              
118             The list is in C<{}> so that it won't be evaluated if logging is turned off.
119             It is a full block, so you can run arbitrary code to decide what to log.
120             If the block returns an empty list, C<_hlog> will not produce any output.
121             However, if the block returns at least one element, C<_hlog> will produce at
122             least a C<'# '>.
123              
124             The message will be output only if L</$VERBOSE> is at least the given minimum
125             verbosity level (1 by default).
126              
127             If C<< $VERBOSE > 2 >>, the filename and line from which C<_hlog> was called
128             will also be printed.
129              
130             B<Caution:> Within the C<{ }> block, C<@_> is the arguments I<to that block>,
131             not the arguments to the calling function. To log C<@_>, use something like:
132              
133             my $argref = \@_;
134             _hlog { @$argref };
135              
136             =cut
137              
138             return unless $VERBOSE >= ($_[1] || 1);
139              
140             my @log = &{$_[0]}();
141             return unless @log;
142              
143 285 100 100 285   1552 chomp $log[$#log] if $log[$#log];
144             # TODO add an option to number the lines of the output
145 227         358 (my $msg = join(' ', @log)) =~ s/^/# /gm;
  227         485  
146 227 50       21866 if($VERBOSE>2) {
147             my ($package, $filename, $line) = caller;
148 227 50       781 $msg .= " (at $filename:$line)";
149             }
150 227         6102 print STDERR "$msg\n";
151 227 50       649 } #_hlog()
152 227         752  
153 227         730 =head2 _complete_dispatcher
154              
155 227         17140 Makes a standard dispatcher, given code to initialize certain variables.
156             Usage:
157              
158             my $code = "..."; # See requirements below
159             my $subref = _complete_dispatcher($multisub_hashref, $code[, ...]);
160              
161             The C<$code> argument will be inlined as-is into the generated dispatcher.
162             The C<$code> must:
163              
164             =over
165              
166             =item *
167              
168             Pick which multisub candidate to use, given args in C<@_>;
169              
170             =item *
171              
172             Put the subref of that candidate in C<$candidate>; and
173              
174             =item *
175              
176             Put a subref in C<$copier> of a routine that will copy from C<@_> into
177             the package variables created by L<Sub::Multi::Tiny/import>.
178              
179             Any arguments to C<_complete_dispatcher> after C<$code> are saved in C<my @data>,
180             which C<$code> can access.
181              
182             =back
183              
184             C<$code> is run under L<strict> and L<warnings> by default. If you don't
185             want those, you need to expressly turn them off.
186              
187             =cut
188              
189             my ($hr, $inner_code, @data) = @_;
190             my $argref = \@_;
191             my $caller = caller;
192             _hlog { require Data::Dumper;
193             "_complete_dispatcher making $caller dispatcher with args:",
194             Data::Dumper->Dump($argref, [qw(multisub inner_code data)]) };
195 10     10   40  
196 10         27 # Make the dispatcher
197 10         25 my $code = _line_mark_string <<EOT;
198 10     10   63 use strict;
199 10         97 use warnings;
200 10         75 sub {
201             # Find the candidate
202             my (\$candidate, \$copier);
203 10         124  
204 7     7   49 $inner_code
  7     1   15  
  7     1   151  
  1         5  
  1         2  
  1         124  
  1         7  
  1         2  
  1         131  
205 33     7   82  
  33     1   178  
  33     1   1374  
  1         7  
  1         1  
  1         21  
206             # Save the present values of the parameters
207             EOT
208 27     1   76  
209             my $restore = '';
210             foreach(keys %{$hr->{possible_params}}) {
211             my ($sigil, $name) = /^(.)(.+)$/;
212             $code .= _line_mark_string
213             "my ${sigil}saved_${name} = ${sigil}$hr->{defined_in}\::${name};\n";
214             $restore .= _line_mark_string
215 10         27 "${sigil}$hr->{defined_in}\::${name} = ${sigil}saved_${name};\n";
216 10         24 }
  10         49  
217 15         122  
218 15         100 $code .= _line_mark_string <<EOT;
219 27         7889 # Create the guard
  27         140  
220 15         80 my \$guard = Guard::guard {
221 1     1   2  
  1         167  
222             $restore
223              
224 10         45 }; #End of guard
225 1         4 EOT
226              
227             $code .= _line_mark_string <<'EOT';
228              
229             # Copy the parameters into the variables the candidate
230             # will access them from
231             &$copier; # $copier gets @_ automatically
232              
233 10         34 # Pass the guard so the parameters will be reset once \$candidate
234             # finishes running.
235             @_ = ($guard);
236              
237 1         6 # Invoke the selected candidate
238             goto &$candidate;
239             } #dispatcher
240             EOT
241 1         2  
242             _hlog { $caller, "dispatcher for $hr->{defined_in}\():\n$code\n" } 2;
243             my $sub = eval $code;
244 1         20 die "Could not create dispatcher for $hr->{defined_in}: $@" if $@;
245             return $sub;
246             } # _complete_dispatcher
247              
248 10     47   72 =head2 _make_positional_copier
  10         79  
249 10         512  
250 10 50       55 Make a sub to copy from @_ into package variables. The resulting sub copies
251 10         95 positional parameters. Usage:
252              
253             my $coderef = _make_positional_copier($defined_in, $impl_hashref);
254              
255             The copier is run under L<strict> and L<warnings>, for what it's worth.
256              
257             =cut
258              
259             our $_positional_copier_invocation_number = 0; # DEBUG
260             my ($defined_in, $impl) = @_;
261             my $argref = \@_; # For hlogging
262              
263             my @vars; #DEBUG
264              
265             _hlog { require Data::Dumper;
266             Data::Dumper->Dump($argref,[qw(mpc_defined_in mpc_impl)]) } 2;
267 62     60   10552  
268 25         69 my $code = _line_mark_string <<'EOT';
269             use strict;
270 25         49 use warnings;
271             sub {
272 44     26   159 EOT
273 25         124  
  57         250  
274             # XXX DEBUG: Some extra output to try to debug failures on earlier Perls.
275 60         432 $code .= _line_mark_string <<'EOT';
276 14     10   104 if( $] lt '5.018' || $VERBOSE > 1) {
  13     7   1495  
  10     1   258  
  10     1   79  
  10     1   71  
  10     1   167  
  1     1   7  
  1     1   2  
  1         153  
  1         5  
  1         2  
  1         140  
  1         5  
  1         3  
  1         162  
  1         5  
  1         2  
  1         154  
  1         5  
  1         1  
  1         161  
  1         4  
  1         3  
  1         154  
277 10     10   70 require Data::Dumper;
  10     7   18  
  13     1   2282  
  10     1   1052  
  10     1   34  
  33     1   13594  
  1     1   7  
  1     1   2  
  1         20  
  1         7  
  1         3  
  1         20  
  1         7  
  1         2  
  1         22  
  1         6  
  1         3  
  1         21  
  1         7  
  1         2  
  1         20  
  1         6  
  1         3  
  1         24  
278             require Test::More;
279             Test::More::diag(sprintf("Positional copier invocation %d:\n%s",
280             ++$Sub::Multi::Tiny::Util::_positional_copier_invocation_number,
281             Data::Dumper->Dump([\@_],['copier_args'])));
282 60         260 }
283             EOT
284              
285             $code .= _line_mark_string <<'EOT';
286             (
287             EOT
288              
289             @vars = map {
290             my ($sigil, $name) = $_->{name} =~ m/^(.)(.+)$/;
291             "${sigil}$defined_in\::${name}"
292 60         5392 } @{$impl->{args}};
293              
294             $code .= join ",\n",
295             map { _line_mark_string
296             " $_" } @vars;
297 49         2502  
298 49         4446 $code .= _line_mark_string <<'EOT';
299 60         249 ) = @_;
  42         974  
300              
301             if( $] lt '5.018' || $VERBOSE > 1) {
302 25         71 Test::More::diag(sprintf("After positional copier invocation %d:",
  29         79  
303             $Sub::Multi::Tiny::Util::_positional_copier_invocation_number));
304             Test::More::diag(join "\n", map {
305 25         98 sprintf("%s = %s", $_, eval($_))
306             } @vars);
307             }
308              
309             } #copier
310             EOT
311              
312             _hlog { "Copier for $impl->{candidate_name}\():\n", $code } 2;
313             my $sub = eval $code;
314             die "Could not create copier for $impl->{candidate_name}: $@" if $@;
315             return $sub;
316             } #_make_positional_copier
317              
318             1;
319 29     22   186  
  26         106  
320 29         1353 # Rest of documentation {{{1
321 29 50       131  
322 29         1336 =head1 AUTHOR
323              
324             Chris White E<lt>cxw@cpan.orgE<gt>
325              
326             =head1 LICENSE
327              
328             Copyright (C) 2019 Chris White E<lt>cxw@cpan.orgE<gt>
329              
330             This library is free software; you can redistribute it and/or modify
331             it under the same terms as Perl itself.
332              
333             =cut
334              
335             # }}}1
336             # vi: set fdm=marker: #