summaryrefslogtreecommitdiff
path: root/lib/Data/OptList.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Data/OptList.pm')
-rw-r--r--lib/Data/OptList.pm258
1 files changed, 258 insertions, 0 deletions
diff --git a/lib/Data/OptList.pm b/lib/Data/OptList.pm
new file mode 100644
index 0000000..4f7ba35
--- /dev/null
+++ b/lib/Data/OptList.pm
@@ -0,0 +1,258 @@
+use strict;
+use warnings;
+package Data::OptList;
+{
+ $Data::OptList::VERSION = '0.109';
+}
+# ABSTRACT: parse and validate simple name/value option pairs
+
+use List::Util ();
+use Params::Util ();
+use Sub::Install 0.921 ();
+
+
+my %test_for;
+BEGIN {
+ %test_for = (
+ CODE => \&Params::Util::_CODELIKE, ## no critic
+ HASH => \&Params::Util::_HASHLIKE, ## no critic
+ ARRAY => \&Params::Util::_ARRAYLIKE, ## no critic
+ SCALAR => \&Params::Util::_SCALAR0, ## no critic
+ );
+}
+
+sub __is_a {
+ my ($got, $expected) = @_;
+
+ return List::Util::first { __is_a($got, $_) } @$expected if ref $expected;
+
+ return defined (
+ exists($test_for{$expected})
+ ? $test_for{$expected}->($got)
+ : Params::Util::_INSTANCE($got, $expected) ## no critic
+ );
+}
+
+sub mkopt {
+ my ($opt_list) = shift;
+
+ my ($moniker, $require_unique, $must_be); # the old positional args
+ my $name_test;
+
+ if (@_ == 1 and Params::Util::_HASHLIKE($_[0])) {
+ my $arg = $_[0];
+ ($moniker, $require_unique, $must_be, $name_test)
+ = @$arg{ qw(moniker require_unique must_be name_test) };
+ } else {
+ ($moniker, $require_unique, $must_be) = @_;
+ }
+
+ $moniker = 'unnamed' unless defined $moniker;
+
+ return [] unless $opt_list;
+
+ $name_test ||= sub { ! ref $_[0] };
+
+ $opt_list = [
+ map { $_ => (ref $opt_list->{$_} ? $opt_list->{$_} : ()) } keys %$opt_list
+ ] if ref $opt_list eq 'HASH';
+
+ my @return;
+ my %seen;
+
+ for (my $i = 0; $i < @$opt_list; $i++) { ## no critic
+ my $name = $opt_list->[$i];
+ my $value;
+
+ if ($require_unique) {
+ Carp::croak "multiple definitions provided for $name" if $seen{$name}++;
+ }
+
+ if ($i == $#$opt_list) { $value = undef; }
+ elsif (not defined $opt_list->[$i+1]) { $value = undef; $i++ }
+ elsif ($name_test->($opt_list->[$i+1])) { $value = undef; }
+ else { $value = $opt_list->[++$i] }
+
+ if ($must_be and defined $value) {
+ unless (__is_a($value, $must_be)) {
+ my $ref = ref $value;
+ Carp::croak "$ref-ref values are not valid in $moniker opt list";
+ }
+ }
+
+ push @return, [ $name => $value ];
+ }
+
+ return \@return;
+}
+
+
+sub mkopt_hash {
+ my ($opt_list, $moniker, $must_be) = @_;
+ return {} unless $opt_list;
+
+ $opt_list = mkopt($opt_list, $moniker, 1, $must_be);
+ my %hash = map { $_->[0] => $_->[1] } @$opt_list;
+ return \%hash;
+}
+
+
+BEGIN {
+ *import = Sub::Install::exporter {
+ exports => [qw(mkopt mkopt_hash)],
+ };
+}
+
+1;
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Data::OptList - parse and validate simple name/value option pairs
+
+=head1 VERSION
+
+version 0.109
+
+=head1 SYNOPSIS
+
+ use Data::OptList;
+
+ my $options = Data::OptList::mkopt([
+ qw(key1 key2 key3 key4),
+ key5 => { ... },
+ key6 => [ ... ],
+ key7 => sub { ... },
+ key8 => { ... },
+ key8 => [ ... ],
+ ]);
+
+...is the same thing, more or less, as:
+
+ my $options = [
+ [ key1 => undef, ],
+ [ key2 => undef, ],
+ [ key3 => undef, ],
+ [ key4 => undef, ],
+ [ key5 => { ... }, ],
+ [ key6 => [ ... ], ],
+ [ key7 => sub { ... }, ],
+ [ key8 => { ... }, ],
+ [ key8 => [ ... ], ],
+ ]);
+
+=head1 DESCRIPTION
+
+Hashes are great for storing named data, but if you want more than one entry
+for a name, you have to use a list of pairs. Even then, this is really boring
+to write:
+
+ $values = [
+ foo => undef,
+ bar => undef,
+ baz => undef,
+ xyz => { ... },
+ ];
+
+Just look at all those undefs! Don't worry, we can get rid of those:
+
+ $values = [
+ map { $_ => undef } qw(foo bar baz),
+ xyz => { ... },
+ ];
+
+Aaaauuugh! We've saved a little typing, but now it requires thought to read,
+and thinking is even worse than typing... and it's got a bug! It looked right,
+didn't it? Well, the C<< xyz => { ... } >> gets consumed by the map, and we
+don't get the data we wanted.
+
+With Data::OptList, you can do this instead:
+
+ $values = Data::OptList::mkopt([
+ qw(foo bar baz),
+ xyz => { ... },
+ ]);
+
+This works by assuming that any defined scalar is a name and any reference
+following a name is its value.
+
+=head1 FUNCTIONS
+
+=head2 mkopt
+
+ my $opt_list = Data::OptList::mkopt($input, \%arg);
+
+Valid arguments are:
+
+ moniker - a word used in errors to describe the opt list; encouraged
+ require_unique - if true, no name may appear more than once
+ must_be - types to which opt list values are limited (described below)
+ name_test - a coderef used to test whether a value can be a name
+ (described below, but you probably don't want this)
+
+This produces an array of arrays; the inner arrays are name/value pairs.
+Values will be either "undef" or a reference.
+
+Positional parameters may be used for compatibility with the old C<mkopt>
+interface:
+
+ my $opt_list = Data::OptList::mkopt($input, $moniker, $req_uni, $must_be);
+
+Valid values for C<$input>:
+
+ undef -> []
+ hashref -> [ [ key1 => value1 ] ... ] # non-ref values become undef
+ arrayref -> every name followed by a non-name becomes a pair: [ name => ref ]
+ every name followed by undef becomes a pair: [ name => undef ]
+ otherwise, it becomes [ name => undef ] like so:
+ [ "a", "b", [ 1, 2 ] ] -> [ [ a => undef ], [ b => [ 1, 2 ] ] ]
+
+By default, a I<name> is any defined non-reference. The C<name_test> parameter
+can be a code ref that tests whether the argument passed it is a name or not.
+This should be used rarely. Interactions between C<require_unique> and
+C<name_test> are not yet particularly elegant, as C<require_unique> just tests
+string equality. B<This may change.>
+
+The C<must_be> parameter is either a scalar or array of scalars; it defines
+what kind(s) of refs may be values. If an invalid value is found, an exception
+is thrown. If no value is passed for this argument, any reference is valid.
+If C<must_be> specifies that values must be CODE, HASH, ARRAY, or SCALAR, then
+Params::Util is used to check whether the given value can provide that
+interface. Otherwise, it checks that the given value is an object of the kind.
+
+In other words:
+
+ [ qw(SCALAR HASH Object::Known) ]
+
+Means:
+
+ _SCALAR0($value) or _HASH($value) or _INSTANCE($value, 'Object::Known')
+
+=head2 mkopt_hash
+
+ my $opt_hash = Data::OptList::mkopt_hash($input, $moniker, $must_be);
+
+Given valid C<L</mkopt>> input, this routine returns a reference to a hash. It
+will throw an exception if any name has more than one value.
+
+=head1 EXPORTS
+
+Both C<mkopt> and C<mkopt_hash> may be exported on request.
+
+=head1 AUTHOR
+
+Ricardo Signes <rjbs@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2006 by Ricardo Signes.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut