File Coverage

blib/lib/Sub/Multi/Tiny/Util.pm
Criterion Covered Total %
statement 81 90 90.0
branch 9 18 50.0
condition 3 5 60.0
subroutine 16 18 88.8
pod n/a
total 109 131 83.2


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