File Coverage

blib/lib/Exporter/ConditionalSubs.pm
Criterion Covered Total %
statement 71 75 94.6
branch 12 16 75.0
condition 6 10 60.0
subroutine 14 14 100.0
pod n/a
total 103 115 89.5


line stmt bran cond sub pod time code
1             package Exporter::ConditionalSubs;
2              
3 2     2   135642 use strict;
  2         19  
  2         58  
4 2     2   10 use warnings;
  2         4  
  2         98  
5              
6             require Exporter;
7             our @ISA = qw( Exporter );
8              
9             #------------------------------------------------------------
10             #
11             # This section lifted directly from Debug::Show v0.0
12             #
13             # https://metacpan.org/pod/Debug::Show
14             #
15             # Thanks to Zefram
16             #
17 2     2   972 use B::CallChecker qw( cv_set_call_checker );
  2         5161  
  2         261  
18             BEGIN {
19             # B::Generate provides a broken version of B::COP->warnings, which
20             # makes B::Deparse barf [rt.cpan.org #70396], and of B::SVOP->sv,
21             # which makes B::Concise emit rubbish [rt.cpan.org #70398].
22             # This works around it by restoring the non-broken versions,
23             # provided that B::Generate hasn't already been loaded. If it
24             # was loaded by someone else, better hope they worked around it
25             # the same way.
26 2     2   11 require B;
27 2         5 my $cop_warnings = \&B::COP::warnings;
28 2         4 my $svop_sv = \&B::SVOP::sv;
29 2         941 require B::Generate;
30 2     2   16 no warnings "redefine";
  2         4  
  2         102  
31 2         3535 *B::COP::warnings = $cop_warnings;
32 2         7 *B::SVOP::sv = $svop_sv;
33 2         86 B::Generate->VERSION(1.33);
34             }
35             #------------------------------------------------------------
36              
37 2     2   14 use Carp qw( croak );
  2         3  
  2         667  
38              
39             #pod =head1 NAME
40             #pod
41             #pod Exporter::ConditionalSubs - Conditionally export subroutines
42             #pod
43             #pod =head1 VERSION
44             #pod
45             #pod Version 1.01
46             #pod
47             #pod =cut
48              
49             our $VERSION = '1.01';
50              
51             #pod =head1 SYNOPSIS
52             #pod
53             #pod Allows subroutines to be conditionally exported. If the condition
54             #pod is satisfied, the subroutine will be exported as usual. But if not,
55             #pod the subroutine will be replaced with a stub that gets optimized away
56             #pod by the compiler. When stubbed out, not even the arguments to the
57             #pod subroutine will get evaluated.
58             #pod
59             #pod This allows for e.g. assertion-like behavior, where subroutine calls
60             #pod can be left in the code but effectively ignored under certain conditions.
61             #pod
62             #pod First create a module that C L:
63             #pod
64             #pod package My::Assertions;
65             #pod
66             #pod require Exporter::ConditionalSubs;
67             #pod our @ISA = qw( Exporter::ConditionalSubs );
68             #pod
69             #pod our @EXPORT = ();
70             #pod our @EXPORT_OK = qw( _assert_non_empty );
71             #pod
72             #pod sub _assert_non_empty
73             #pod {
74             #pod carp "Found empty value" unless length(shift // '') > 0;
75             #pod }
76             #pod
77             #pod Then, specify an C<-if> or C<-unless> condition when Cing that module:
78             #pod
79             #pod package My::App;
80             #pod
81             #pod use My::Assertions qw( _assert_non_empty ), -if => $ENV{DEBUG};
82             #pod
83             #pod use My::MoreAssertions -unless => $ENV{RUNTIME} eq 'prod';
84             #pod
85             #pod # Coderefs work too:
86             #pod use My::OtherAssertions -if => sub { ... some logic ... };
87             #pod
88             #pod _assert_non_empty($foo); # this subroutine call might be a no-op
89             #pod
90             #pod
91             #pod This is a subclass of L and works just like it, with the
92             #pod addition of support for the C<-if> and C<-unless> import arguments.
93             #pod
94             #pod =head1 SUBROUTINES
95             #pod
96             #pod =head2 import
97             #pod
98             #pod Works like the L C function, except that it checks
99             #pod for an optional C<-if> or C<-unless> import arg, followed by either
100             #pod a boolean, or a coderef that returns true/false.
101             #pod
102             #pod If the condition evaluates to true for C<-if>, or false for C<-unless>,
103             #pod then any subs are exported as-is. Otherwise, any subs in C<@EXPORT_OK>
104             #pod are replaced with stubs that get optimized away by the compiler.
105             #pod
106             #pod You can specify either C<-if> or C<-unless>, but not both. Croaks if
107             #pod both are specified, or if you specify the same option more than once.
108             #pod
109             #pod =cut
110              
111             sub import
112             {
113 11     11   148 my ($package, @args) = @_;
114              
115             # By default we are going to export subs as-is and not optimize them away:
116 11         18 my $should_export_subs = 1;
117              
118 11         17 my @export_args = ($package);
119              
120             # Copy args until we come across "-if" or "-unless":
121             #
122 11   50     64 while (@args && ($args[0] || '') !~ /^-(?:if|unless)$/) {
      100        
123 18         99 push @export_args, shift @args;
124             }
125              
126             # If any args remain, it must be because we found "-if" or "-unless".
127             # We expect the next arg to be the condition boolean or coderef:
128             #
129 11 100       21 if (@args) {
130 8         14 my $thing = shift @args; # i.e. "-if" or "-unless"
131 8 50       12 unless (@args) {
132 0         0 croak "$package->import failed: " .
133             qq{"$thing" must be followed by boolean or coderef};
134             }
135 8         12 my $condition = shift @args;
136              
137             # Go ahead and evaluate the condition if it's a coderef:
138 8 100       21 $condition = $condition->() if ref($condition) eq 'CODE';
139              
140             # We might decide against importing subs, depending on the condition:
141 8 100       25 $should_export_subs = $thing eq '-if' ? $condition : !$condition;
142              
143             # Copy any remaining args:
144             #
145 8         17 while (@args) {
146 0 0 0     0 if ($args[0] && $args[0] =~ /^-(?:if|unless)$/) {
147 0         0 croak "$package->import failed: " .
148             qq{Cannot use "$args[0]" after "$thing"};
149             }
150 0         0 push @export_args, shift @args;
151             }
152             }
153              
154             # If the "if" condition is false, or the "unless" condition is true,
155             # replace any exportable subs with something that will get optimized away:
156             #
157 11         18 my %their_original_coderefs;
158 11 100       20 unless ($should_export_subs) {
159              
160 2     2   16 no strict 'refs';
  2         4  
  2         231  
161 4         5 my $stash = *{ $package . "::" };
  4         25  
162              
163 4 50       6 my @their_export_oks = @{ ($stash->{EXPORT_OK} || []) };
  4         20  
164 4         8 for my $export_name (@their_export_oks) {
165              
166 8         33 my $globref = $stash->{$export_name};
167 8 100 100     32 if ($globref && *$globref{CODE}) {
168              
169 4         10 my $symbol = $package . "::" . $export_name;
170              
171             # Save a copy of the original code:
172 4         13 $their_original_coderefs{$symbol} = \&$symbol;
173              
174             # Replace the sub being imported with a void prototype sub
175             # that gets optimized away:
176             #
177             {
178 2     2   15 no warnings 'redefine';
  2         3  
  2         92  
  4         4  
179 2     2   13 no warnings 'prototype';
  2         3  
  2         331  
180              
181 4         12 *$symbol = sub () {0};
182              
183             #---------------------------------------------------------
184             #
185             # This section lifted almost as-is from Debug::Show v0.0
186             #
187             # https://metacpan.org/pod/Debug::Show
188             #
189             # Thanks to Zefram
190             #
191             cv_set_call_checker(\&$symbol, sub ($$$) {
192 8     8   21 my($entersubop, undef, undef) = @_;
193             # B::Generate doesn't offer a way to explicitly free ops.
194             # We ought to be able to implicitly free $entersubop via
195             # constant folding, by something like
196             #
197             # return B::LOGOP->new("and", 0,
198             # B::SVOP->new("const", 0, !1),
199             # $entersubop);
200             #
201             # but empirically that causes memory corruption and it's
202             # not clear why. For the time being, leak $entersubop.
203 8         1732 return B::SVOP->new("const", 0, !1);
204 4         51 }, \!1);
205             #
206             #---------------------------------------------------------
207             }
208             }
209             }
210             }
211              
212             # Let Export handle everything else as usual:
213 11         742 $package->export_to_level(1, @export_args);
214              
215             # Restore coderefs in the original package with their saved version:
216             #
217 11         818 while (my ($symbol, $coderef) = each %their_original_coderefs) {
218 2     2   15 no strict 'refs';
  2         4  
  2         74  
219 2     2   12 no warnings 'redefine';
  2         3  
  2         95  
220 2     2   13 no warnings 'prototype';
  2         3  
  2         233  
221 4         108 *$symbol = $coderef;
222             }
223             }
224              
225             #pod =head1 SEE ALSO
226             #pod
227             #pod L
228             #pod
229             #pod L
230             #pod
231             #pod L
232             #pod
233             #pod =head1 AUTHOR
234             #pod
235             #pod Larry Leszczynski, C<< >>
236             #pod
237             #pod =head1 BUGS
238             #pod
239             #pod Please report any bugs or feature requests at:
240             #pod L
241             #pod
242             #pod =head1 SUPPORT
243             #pod
244             #pod You can find documentation for this module with the perldoc command.
245             #pod
246             #pod perldoc Exporter::ConditionalSubs
247             #pod
248             #pod You can also look for information at:
249             #pod
250             #pod =over 4
251             #pod
252             #pod =item * GitHub
253             #pod
254             #pod L
255             #pod
256             #pod =item * MetaCPAN
257             #pod
258             #pod L
259             #pod
260             #pod =item * AnnoCPAN, Annotated CPAN documentation
261             #pod
262             #pod L
263             #pod
264             #pod =item * CPAN Ratings
265             #pod
266             #pod L
267             #pod
268             #pod =back
269             #pod
270             #pod =head1 AUTHOR
271             #pod
272             #pod Larry Leszczynski, C<< >>
273             #pod
274             #pod =head1 ACKNOWLEDGEMENTS
275             #pod
276             #pod Thanks to Grant Street Group L for funding
277             #pod development of this code.
278             #pod
279             #pod Thanks to Tom Christiansen (C<< >>) for help with the
280             #pod symbol table hackery.
281             #pod
282             #pod Thanks to Zefram (C<< >>) for the pointer to his
283             #pod L hackery.
284             #pod
285             #pod =head1 LICENSE AND COPYRIGHT
286             #pod
287             #pod Copyright 2015 Grant Street Group
288             #pod
289             #pod This program is free software; you can redistribute it and/or modify it
290             #pod under the terms of the the Artistic License (2.0). You may obtain a
291             #pod copy of the full license at:
292             #pod
293             #pod L
294             #pod
295             #pod Any use, modification, and distribution of the Standard or Modified
296             #pod Versions is governed by this Artistic License. By using, modifying or
297             #pod distributing the Package, you accept this license. Do not use, modify,
298             #pod or distribute the Package, if you do not accept this license.
299             #pod
300             #pod If your Modified Version has been derived from a Modified Version made
301             #pod by someone other than you, you are nevertheless required to ensure that
302             #pod your Modified Version complies with the requirements of this license.
303             #pod
304             #pod This license does not grant you the right to use any trademark, service
305             #pod mark, tradename, or logo of the Copyright Holder.
306             #pod
307             #pod This license includes the non-exclusive, worldwide, free-of-charge
308             #pod patent license to make, have made, use, offer to sell, sell, import and
309             #pod otherwise transfer the Package with respect to any patent claims
310             #pod licensable by the Copyright Holder that are necessarily infringed by the
311             #pod Package. If you institute patent litigation (including a cross-claim or
312             #pod counterclaim) against any party alleging that the Package constitutes
313             #pod direct or contributory patent infringement, then this Artistic License
314             #pod to you shall terminate on the date that such litigation is filed.
315             #pod
316             #pod Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
317             #pod AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
318             #pod THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
319             #pod PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
320             #pod YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
321             #pod CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
322             #pod CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
323             #pod EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
324             #pod
325             #pod =cut
326              
327             1;
328              
329             __END__