File Coverage

blib/lib/optional.pm
Criterion Covered Total %
statement 63 65 96.9
branch 16 18 88.8
condition 4 5 80.0
subroutine 12 13 92.3
pod 0 1 0.0
total 95 102 93.1


line stmt bran cond sub pod time code
1             package optional;
2 1     1   238901 use strict;
  1         2  
  1         38  
3 1     1   14 use warnings;
  1         2  
  1         72  
4              
5             our $VERSION = '0.001';
6              
7 1     1   8 use Carp qw/confess croak/;
  1         2  
  1         878  
8              
9             sub import {
10 4     4   262412 my $class = shift;
11 4         16 my ($name, @args) = @_;
12              
13 4         11 my $caller = caller;
14              
15 4         16 $class->generate($caller, $name, \@args);
16             }
17              
18             sub generate {
19 4     4 0 7 my $class = shift;
20 4         8 my ($into, $name, $modules) = @_;
21              
22 4         10 my ($use, %out);
23 4         14 for my $module (@$modules) {
24 5         10 my $file = $module;
25 5         25 $file =~ s{::}{/}g;
26 5         10 $file .= ".pm";
27              
28 5 100       9 if (eval { require($file); 1 }) {
  5         1619  
  2         19  
29 2         5 my $mod = $module;
30 2     0   22 $out{uc($name)} = sub() { $mod };
  0         0  
31 2     3   39 $out{"if_$name"} = sub(&) { scalar $_[0]->(module => $module, name => $name, modules => $modules) };
  3         7382  
32 2     2   10 $out{"unless_$name"} = sub(&) { undef };
  2         3622  
33 2     2   8 $out{"need_$name"} = sub { undef };
  2         3700  
34              
35 2         4 $use = $module;
36 2         6 last;
37             }
38             else {
39 3         40 my $err = $@;
40 3 100       68 die $err unless $err =~ m/Can't locate \Q$file\E in \@INC/;
41             }
42             }
43              
44 3 100       9 unless ($use) {
45 1         8 $out{uc($name)} = sub() { undef };
46 1     1   6 $out{"if_$name"} = sub(&) { undef };
  1         554  
47 1     2   6 $out{"unless_$name"} = sub(&) { scalar $_[0]->(name => $name, modules => $modules, module => undef) };
  2         483  
48              
49             $out{"need_$name"} = sub {
50 7     7   3733 my ($msg, %params);
51              
52 7 50       52 if (@_ % 2) {
53 0         0 ($msg, %params) = @_;
54             }
55             else {
56 7         29 %params = @_;
57             }
58              
59 7 50       19 unless ($msg) {
60 7 100       20 if ($params{message}) {
61 4         9 $msg = $params{message};
62             }
63             else {
64 3   50     18 $params{append_modules} //= 1;
65 3 100       9 if (my $f = $params{feature}) {
66 1         4 $msg = "You must install one of the following modules to use the '$params{feature}' feature";
67             }
68             else {
69 2         4 $msg = "You must install one of the following modules to use this feature";
70             }
71             }
72             }
73              
74 7         14 chomp($msg);
75 7 100       30 $msg .= ' [' . join(', ' => @$modules) . ']' if $params{append_modules};
76              
77 7 100 100     1203 confess($msg) if $params{trace} || $params{confess};
78 4         30 die "$msg\n";
79 1         5 };
80             }
81              
82 3         15 while (my ($sym, $sub) = each %out) {
83 1     1   9 no strict 'refs';
  1         6  
  1         158  
84 12         16 *{"${into}\::${sym}"} = $sub;
  12         65  
85             }
86              
87 3         3216 return;
88             }
89              
90             1;
91              
92             =pod
93              
94             =encoding UTF-8
95              
96             =head1 NAME
97              
98             optional - Pragma to optionally load a module (or pick from a list of modules)
99             and provide a constant and some tools for taking action depending on if it
100             loaded or not.
101              
102             =head1 DESCRIPTION
103              
104             Helps write code that has optional dependencies. Will load (or not load) the
105             module, then provide you with some tools that help you write logic that depends
106             on the result.
107              
108             If a module fails to load for any reason other than its absence then the
109             exception will be rethrown to show the error.
110              
111             =head1 SYNOPSIS
112              
113             # Will try to load Optional::Module::Foo, naming tools with the 'opt_foo' name
114             use optional opt_foo => qw/Optional::Module::Foo/;
115              
116             # Will try to load Optional::Module::Bar first, but will fallback to
117             # Optional::Module::Foo if the first is not installed.
118             use optional opt_any => qw/Optional::Module::Bar Optional::Module::Foo/;
119              
120             # You get a constant (capitlized version of name) that you can use in conditionals
121             if (OPT_FOO) { ... }
122              
123             # The constant will return the module name that was loaded, if any, undef
124             # if it was not loaded.
125             if (my $mod = OPT_FOO) { ... }
126              
127             # Quickly write code that will only execute if the module was loaded
128             # If the module was not loaded this always returns undef, so it is safe to
129             # use in a hash building list.
130             my $result = if_opt_foo { ... };
131              
132             # Quickly write code that will only execute if the module was NOT loaded If
133             # the module was loaded this always returns undef, so it is safe to use in
134             # a hash building list.
135             my $result = unless_opt_foo { ... };
136              
137             sub feature_that_requires_foo {
138             need_opt_foo(); # Throws an error telling the user they need to install Optional::Module::Foo to use this feature
139             need_opt_foo(feature => 'widget'); # Same, but names the feature
140             need_opt_foo(trace => 1); # Add a stack trace
141             need_opt_foo(message => ...); # Write a custom message
142             need_opt_foo(message => ..., append_modules => 1); # Write a custom message, and add the module/s that need to be installed
143             }
144              
145             =head1 EXPORTS
146              
147             For each use of this module you will get 4 subs exported into your namespace, all contain the NAME intially provided.
148              
149             =over 4
150              
151             =item $module_or_undef = NAME()
152              
153             All caps version of the name. It is a constant, it always returns either the
154             used modules name, or undef if the optional module is not installed.
155              
156             =item $res = if_NAME { ... }
157              
158             Run a block if the module was installed and loaded. Returns undef if the module
159             was not loaded, otherwise it returns what your sub returns.
160              
161             =item $res = unless_NAME { ... }
162              
163             Run a block if the module was NOT installed and loaded. Returns undef if the module
164             was loaded, otherwise it returns what your sub returns.
165              
166             =item need_NAME()
167              
168             =item need_NAME(message => $MSG, trace => $BOOL, append_modules => $BOOL, feature => $FEATURE_NAME)
169              
170             No-op if the module was loaded.
171              
172             If the module was not loaded it will throw an exception like this:
173              
174             "You must install one of the following modules to use this feature [Module::A]\n"
175             "You must install one of the following modules to use this feature [Preferred::Module, Backup::Module]\n"
176              
177             You can also specify a feature name for a message like:
178              
179             "You must install one of the following modules to use the 'My Feature' feature [Module::A]\n"
180              
181             You can also add a custom message, and optionally append the module names by
182             setting C to true.
183              
184             In all forms you can set C to true to use confess for a stack trace.
185              
186             =back
187              
188             =head1 SOURCE
189              
190             The source code repository for 'optional' can be found at
191             L.
192              
193             =head1 MAINTAINERS
194              
195             =over 4
196              
197             =item Chad Granum Eexodist@cpan.orgE
198              
199             =back
200              
201             =head1 AUTHORS
202              
203             =over 4
204              
205             =item Chad Granum Eexodist@cpan.orgE
206              
207             =back
208              
209             =head1 COPYRIGHT
210              
211             Copyright Chad Granum Eexodist7@gmail.comE.
212              
213             This program is free software; you can redistribute it and/or
214             modify it under the same terms as Perl itself.
215              
216             See L
217              
218             =cut
219