File Coverage

blib/lib/Data/OptList.pm
Criterion Covered Total %
statement 62 62 100.0
branch 33 34 97.0
condition 7 8 87.5
subroutine 12 12 100.0
pod 2 2 100.0
total 116 118 98.3


line stmt bran cond sub pod time code
1 2     2   74091 use strict;
  2         12  
  2         70  
2 2     2   11 use warnings;
  2         4  
  2         96  
3             package Data::OptList;
4             # ABSTRACT: parse and validate simple name/value option pairs
5             $Data::OptList::VERSION = '0.112';
6 2     2   11 use List::Util ();
  2         4  
  2         34  
7 2     2   1061 use Params::Util ();
  2         13019  
  2         62  
8 2     2   500 use Sub::Install 0.921 ();
  2         1765  
  2         175  
9              
10             #pod =head1 SYNOPSIS
11             #pod
12             #pod use Data::OptList;
13             #pod
14             #pod my $options = Data::OptList::mkopt([
15             #pod qw(key1 key2 key3 key4),
16             #pod key5 => { ... },
17             #pod key6 => [ ... ],
18             #pod key7 => sub { ... },
19             #pod key8 => { ... },
20             #pod key8 => [ ... ],
21             #pod ]);
22             #pod
23             #pod ...is the same thing, more or less, as:
24             #pod
25             #pod my $options = [
26             #pod [ key1 => undef, ],
27             #pod [ key2 => undef, ],
28             #pod [ key3 => undef, ],
29             #pod [ key4 => undef, ],
30             #pod [ key5 => { ... }, ],
31             #pod [ key6 => [ ... ], ],
32             #pod [ key7 => sub { ... }, ],
33             #pod [ key8 => { ... }, ],
34             #pod [ key8 => [ ... ], ],
35             #pod ]);
36             #pod
37             #pod =head1 DESCRIPTION
38             #pod
39             #pod Hashes are great for storing named data, but if you want more than one entry
40             #pod for a name, you have to use a list of pairs. Even then, this is really boring
41             #pod to write:
42             #pod
43             #pod $values = [
44             #pod foo => undef,
45             #pod bar => undef,
46             #pod baz => undef,
47             #pod xyz => { ... },
48             #pod ];
49             #pod
50             #pod Just look at all those undefs! Don't worry, we can get rid of those:
51             #pod
52             #pod $values = [
53             #pod map { $_ => undef } qw(foo bar baz),
54             #pod xyz => { ... },
55             #pod ];
56             #pod
57             #pod Aaaauuugh! We've saved a little typing, but now it requires thought to read,
58             #pod and thinking is even worse than typing... and it's got a bug! It looked right,
59             #pod didn't it? Well, the C<< xyz => { ... } >> gets consumed by the map, and we
60             #pod don't get the data we wanted.
61             #pod
62             #pod With Data::OptList, you can do this instead:
63             #pod
64             #pod $values = Data::OptList::mkopt([
65             #pod qw(foo bar baz),
66             #pod xyz => { ... },
67             #pod ]);
68             #pod
69             #pod This works by assuming that any defined scalar is a name and any reference
70             #pod following a name is its value.
71             #pod
72             #pod =func mkopt
73             #pod
74             #pod my $opt_list = Data::OptList::mkopt($input, \%arg);
75             #pod
76             #pod Valid arguments are:
77             #pod
78             #pod moniker - a word used in errors to describe the opt list; encouraged
79             #pod require_unique - if true, no name may appear more than once
80             #pod must_be - types to which opt list values are limited (described below)
81             #pod name_test - a coderef used to test whether a value can be a name
82             #pod (described below, but you probably don't want this)
83             #pod
84             #pod This produces an array of arrays; the inner arrays are name/value pairs.
85             #pod Values will be either "undef" or a reference.
86             #pod
87             #pod Positional parameters may be used for compatibility with the old C
88             #pod interface:
89             #pod
90             #pod my $opt_list = Data::OptList::mkopt($input, $moniker, $req_uni, $must_be);
91             #pod
92             #pod Valid values for C<$input>:
93             #pod
94             #pod undef -> []
95             #pod hashref -> [ [ key1 => value1 ] ... ] # non-ref values become undef
96             #pod arrayref -> every name followed by a non-name becomes a pair: [ name => ref ]
97             #pod every name followed by undef becomes a pair: [ name => undef ]
98             #pod otherwise, it becomes [ name => undef ] like so:
99             #pod [ "a", "b", [ 1, 2 ] ] -> [ [ a => undef ], [ b => [ 1, 2 ] ] ]
100             #pod
101             #pod By default, a I is any defined non-reference. The C parameter
102             #pod can be a code ref that tests whether the argument passed it is a name or not.
103             #pod This should be used rarely. Interactions between C and
104             #pod C are not yet particularly elegant, as C just tests
105             #pod string equality. B
106             #pod
107             #pod The C parameter is either a scalar or array of scalars; it defines
108             #pod what kind(s) of refs may be values. If an invalid value is found, an exception
109             #pod is thrown. If no value is passed for this argument, any reference is valid.
110             #pod If C specifies that values must be CODE, HASH, ARRAY, or SCALAR, then
111             #pod Params::Util is used to check whether the given value can provide that
112             #pod interface. Otherwise, it checks that the given value is an object of the kind.
113             #pod
114             #pod In other words:
115             #pod
116             #pod [ qw(SCALAR HASH Object::Known) ]
117             #pod
118             #pod Means:
119             #pod
120             #pod _SCALAR0($value) or _HASH($value) or _INSTANCE($value, 'Object::Known')
121             #pod
122             #pod =cut
123              
124             my %test_for;
125             BEGIN {
126 2     2   1283 %test_for = (
127             CODE => \&Params::Util::_CODELIKE, ## no critic
128             HASH => \&Params::Util::_HASHLIKE, ## no critic
129             ARRAY => \&Params::Util::_ARRAYLIKE, ## no critic
130             SCALAR => \&Params::Util::_SCALAR0, ## no critic
131             );
132             }
133              
134             sub mkopt {
135 37     37 1 7682 my ($opt_list) = shift;
136              
137 37         87 my ($moniker, $require_unique, $must_be); # the old positional args
138 37         0 my ($name_test, $is_a);
139              
140 37 100       83 if (@_) {
141 30 100 66     98 if (@_ == 1 and Params::Util::_HASHLIKE($_[0])) {
142             ($moniker, $require_unique, $must_be, $name_test)
143 3         5 = @{$_[0]}{ qw(moniker require_unique must_be name_test) };
  3         11  
144             } else {
145 27         55 ($moniker, $require_unique, $must_be) = @_;
146             }
147              
148             # Transform the $must_be specification into a closure $is_a
149             # that will check if a value matches the spec
150              
151 30 100       67 if (defined $must_be) {
152 20 100       46 $must_be = [ $must_be ] unless ref $must_be;
153             my @checks = map {
154 20         42 my $class = $_;
  31         47  
155             $test_for{$class}
156 7     7   48 || sub { Params::Util::_INSTANCE($_[0], $class) }
157 31 100       136 } @$must_be;
158              
159             $is_a = (@checks == 1)
160             ? $checks[0]
161             : sub {
162 6     6   10 my $value = $_[0];
163 12         92 List::Util::first { defined($_->($value)) } @checks
164 20 100       61 };
  6         27  
165              
166 20 50       49 $moniker = 'unnamed' unless defined $moniker;
167             }
168             }
169              
170 37 100       76 return [] unless $opt_list;
171              
172 36   100 59   277 $name_test ||= sub { ! ref $_[0] };
  59         142  
173              
174             $opt_list = [
175 36 100       104 map { $_ => (ref $opt_list->{$_} ? $opt_list->{$_} : ()) } keys %$opt_list
  15 100       42  
176             ] if ref $opt_list eq 'HASH';
177              
178 36         66 my @return;
179             my %seen;
180              
181 36         93 for (my $i = 0; $i < @$opt_list; $i++) { ## no critic
182 88         127 my $name = $opt_list->[$i];
183              
184 88 100       155 if ($require_unique) {
185 30 100       152 Carp::croak "multiple definitions provided for $name" if $seen{$name}++;
186             }
187              
188 87         115 my $value;
189              
190 87 100       147 if ($i < $#$opt_list) {
191 68 100       184 if (not defined $opt_list->[$i+1]) {
    100          
192 6         8 $i++
193             } elsif (! $name_test->($opt_list->[$i+1])) {
194 29         66 $value = $opt_list->[++$i];
195 29 100 100     99 if ($is_a && !$is_a->($value)) {
196 9         14 my $ref = ref $value;
197 9         952 Carp::croak "$ref-ref values are not valid in $moniker opt list";
198             }
199             }
200             }
201              
202 78         234 push @return, [ $name => $value ];
203             }
204              
205 26         234 return \@return;
206             }
207              
208             #pod =func mkopt_hash
209             #pod
210             #pod my $opt_hash = Data::OptList::mkopt_hash($input, $moniker, $must_be);
211             #pod
212             #pod Given valid C> input, this routine returns a reference to a hash. It
213             #pod will throw an exception if any name has more than one value.
214             #pod
215             #pod =cut
216              
217             sub mkopt_hash {
218 12     12 1 894 my ($opt_list, $moniker, $must_be) = @_;
219 12 100       36 return {} unless $opt_list;
220              
221 10         22 $opt_list = mkopt($opt_list, $moniker, 1, $must_be);
222 8         21 my %hash = map { $_->[0] => $_->[1] } @$opt_list;
  18         41  
223 8         52 return \%hash;
224             }
225              
226             #pod =head1 EXPORTS
227             #pod
228             #pod Both C and C may be exported on request.
229             #pod
230             #pod =cut
231              
232             BEGIN {
233 2     2   20 *import = Sub::Install::exporter {
234             exports => [qw(mkopt mkopt_hash)],
235             };
236             }
237              
238             1;
239              
240             __END__