Skip to content

Commit

Permalink
remove 'required' and add late 'validate'
Browse files Browse the repository at this point in the history
  • Loading branch information
xdg committed Nov 7, 2010
1 parent 0a9c934 commit dc181f7
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 35 deletions.
2 changes: 2 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Revision history for Perl module Getopt::Lucid

- new() takes optional hashref of parameters
- Remove global $STRICT and replace with 'strict' object parameter
- Remove 'required' modifier for parameters and provide a new
'validate' method for late checking of prerequisites

0.19 2010-11-05 17:07:26 EST5EDT

Expand Down
103 changes: 75 additions & 28 deletions lib/Getopt/Lucid.pm
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ my $VALID_NAME = qr/$VALID_LONG|$VALID_SHORT|$VALID_BARE/;
my $SHORT_BUNDLE = qr/-[$VALID_STARTCHAR]{2,}/;
my $NEGATIVE = qr/(?:--)?no-/;

my @valid_keys = qw( name type required default nocase valid needs canon );
my @valid_keys = qw( name type default nocase valid needs canon );
my @valid_types = qw( switch counter parameter list keypair);

sub Switch {
Expand All @@ -50,9 +50,9 @@ sub Keypair {
return bless $self, "Getopt::Lucid::Spec";
}

package # hide from PAUSE
package
Getopt::Lucid::Spec;
our $VERSION = $Getopt::Lucid::VERSION;
$Getopt::Lucid::Spec::VERSION = $Getopt::Lucid::VERSION;

# alternate way to specify validation
sub valid {
Expand All @@ -63,8 +63,6 @@ sub valid {
return $self;
}

sub required { my $self = shift; $self->{required} = 1; return $self };

sub default {
my $self = shift;
my $type = $self->{type};
Expand Down Expand Up @@ -214,15 +212,31 @@ sub getopt {
push @passthrough, $orig;
}
}
_check_required($self);
_check_prereqs($self);
_recalculate_options($self);
@{$self->{target}} = (@passthrough, @{$self->{target}});
return $self;
}

BEGIN { *getopts = \&getopt }; # handy alias

#--------------------------------------------------------------------------#
# validate
#--------------------------------------------------------------------------#

sub validate {
my ($self, $arg) = @_;
throw_usage("Getopt::Lucid->validate() takes an optional hashref argument")
unless $arg && ref($arg) eq 'HASH';

for my $p ( @{$arg->{requires}} ) {
throw_argv("Required option '$self->{spec}{$p}{canon}' not found")
if ( ! $self->{seen}{$p} );
}
_check_prereqs($self);

return $self;
}

#--------------------------------------------------------------------------#
# merge_defaults()
#--------------------------------------------------------------------------#
Expand Down Expand Up @@ -361,18 +375,6 @@ sub _check_prereqs {
}
}

#--------------------------------------------------------------------------#
# _check_required()
#--------------------------------------------------------------------------#

sub _check_required {
my ($self) = @_;
for ( keys %{$self->{spec}} ) {
throw_argv("Required option '$self->{spec}{$_}{canon}' not found")
if ( $self->{spec}{$_}{required} && ! $self->{seen}{$_} );
}
}

#--------------------------------------------------------------------------#
# _counter()
#--------------------------------------------------------------------------#
Expand Down Expand Up @@ -781,7 +783,7 @@ __END__
Switch("help|h")
);
$opt = Getopt::Lucid->getopt( \@specs );
$opt = Getopt::Lucid->getopt( \@specs )->validate;
$verbosity = $opt->get_verbose;
@libs = $opt->get_lib;
Expand All @@ -792,16 +794,19 @@ __END__
# advanced option specifications
@adv_spec = (
Param("input")->required, # required
Param("input"),
Param("mode")->default("tcp"), # defaults
Param("host")->needs("port"), # dependencies
Param("port")->valid(qr/\d+/), # regex validation
Param("config")->valid(sub { -r }),# custom validation
Param("help")->anycase, # case insensitivity
);
$opt = Getopt::Lucid->getopt( \@adv_spec );
$opt->validate( 'requires' => ['input'] );
# example with a config file
$opt = Getopt::Lucid->getopt( \@adv_spec );
use Config::Std;
if ( -r $opt->get_config ) {
read_config( $opt->get_config() => my %config_hash );
Expand Down Expand Up @@ -997,6 +1002,12 @@ argument is needed.
== Validation
Validation happens in two stages. First, individual parameters may have
validation criteria added to them. Second, the parsed options object may be
validated by checking that all requirements or prerequires are met.
=== Parameter validation
The Param, List, and Keypair option types may be provided an optional
validation specification. Values provided on the command line will be
validated according to the specification or an exception will be thrown.
Expand All @@ -1014,14 +1025,13 @@ modifier or later modified with {append_defaults}, {merge_defaults}, or
If no default is explictly provided, validation is only applied if the option
appears on the command line. (In other words, the built-in defaults are always
considered valid if the option does not appear.) If this is not desired, the
{required()} modifier should be used to force users to provide an explicit
value.
{required} option to the {validate} method should be used to force users to
provide an explicit value.
# Must be provided and is thus always validated
Param("width")->valid(qr/\d+/)->required
# Can be left blank, but is validated if provided
Param("height")->valid(qr/\d+/)
@spec = ( Param("width")->valid(qr/\d+/) );
$opt = Getopt::Lucid->getopt(\@spec);
$opt->validate( {requires => ['width']} );
For validation subroutines, the value found on the command line is passed as
the first element of {@_}, and {$_} is also set equal to the first element.
Expand All @@ -1038,6 +1048,26 @@ may be removed in a future version of Getopt::Lucid.
# deprecated
Param("height", qr/\d+/)
=== Options object validation
The {validate} method should be called on the result of {getopt}. This will
check that all parameter prerequisites defined by {needs} have been met. It
also takes a hashref of arguments. The optional {requires} argument gives an
arrayref of parameters that must exist.
The reason that object validation is done separate from {getopt} is to allow
for better control over different options that might be required or to allow
some dependencies (i.e. from {needs}) to be met via a configuration file.
@spec = (
Param("action")->needs(qw/user password/),
Param("user"),
Param("password"),
);
$opt = Getopt::Lucid->getopt(\@spec);
$opt->merge_defaults( read_config() ); # provides 'user' & 'password'
$opt->validate({requires => ['action']});
== Parsing the Command Line
Technically, Getopt::Lucid scans an array for command line options, not a
Expand Down Expand Up @@ -1106,7 +1136,7 @@ For option names with dashes, underscores should be substitued in the accessor
calls. E.g.
@spec = (
Param("--input-file|-i")->required(),
Param("--input-file|-i")
);
$opt = Getopt::Long->getopt( \@spec );
Expand Down Expand Up @@ -1247,6 +1277,21 @@ The only valid parameter currently is:
For typical cases, users will likely prefer to call {getopt} instead, which
creates a new object and parses the command line with a single function call.
== validate()
$opt->validate();
$opt->validate( \%arguments );
Takes an optional argument hashref, validates that all requirements and
prerequisites are met or throws an error. Valid argument keys are:
* {requires} -- an arrayref of options that must exist in the options
object.
This method returns the object for convenient chaining:
$opt = Getopt::Lucid->getopt(\@spec)->validate;
== append_defaults()
%options = append_defaults( %config_hash );
Expand Down Expand Up @@ -1341,6 +1386,8 @@ In 1.00, the following API changes have been made:
argument
* The global {$STRICT} variable has been replaced with a per-object
parameter {strict}
* The {required} modifier has been removed and a new {validate} method
has been added to facilitate late/custom checks of required options
= SEE ALSO
Expand Down
13 changes: 10 additions & 3 deletions t/02-getopt.t
Original file line number Diff line number Diff line change
Expand Up @@ -436,22 +436,25 @@ BEGIN {
label => "required options",
spec => [
Counter("--verbose|-v"),
Param("--input|-i")->required,
Param("--input|-i")
],
cases => [
{
argv => [ qw( -v ) ],
exception => "Getopt::Lucid::Exception::ARGV",
error_msg => _required("--input"),
required => ['input'],
desc => "missing required option"
},
{
argv => [ qw( --input 42 -vv ) ],
required => ['input'],
result => { "verbose" => 2, "input" => 42 },
desc => "required option present"
},
{
argv => [ qw( --input info -v ) ],
required => ['input'],
result => { "verbose" => 1, "input" => 'info' },
desc => "required option param similar to option name"
},
Expand Down Expand Up @@ -648,18 +651,20 @@ BEGIN {
push @good_specs, {
label => "validate w/ string alternation regex",
spec => [
Param( "mode|m", qr/test|live/ )->required
Param( "mode|m", qr/test|live/ ),
],
cases => [
{
argv => [ qw( --mode test ) ],
required => ['mode'],
result => {
"mode" => 'test',
},
desc => "param input validates"
},
{
argv => [ qw( --mode foo ) ],
required => ['mode'],
exception => "Getopt::Lucid::Exception::ARGV",
error_msg => _param_invalid("mode","foo"),
desc => "param mode not validating"
Expand Down Expand Up @@ -983,7 +988,9 @@ while ( $trial = shift @good_specs ) {
my $gl = Getopt::Lucid->new($trial->{spec});
@ARGV = @{$case->{argv}};
my %opts;
try eval { %opts = $gl->getopt->options };
my $valid_args = $case->{required} ? {requires => $case->{required}}
: {};
try eval { %opts = $gl->getopt->validate($valid_args)->options };
catch my $err;
if (defined $case->{exception}) { # expected
ok( $err && $err->isa( $case->{exception} ),
Expand Down
7 changes: 5 additions & 2 deletions t/07-magic-names.t
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,13 @@ BEGIN {
push @good_specs, {
label => "avoid ambiguity (RT 33462)",
spec => [
Param("config|c")->required(),
Param("config|c"),
Switch("help|h")->anycase(),
],
cases => [
{
argv => [ qw( -c /home/newuat5/nas/Abilit/newuat6/test_home/Data/tdg/testdatengenerator.conf ) ],
required => ['config'],
result => {
"config" => "/home/newuat5/nas/Abilit/newuat6/test_home/Data/tdg/testdatengenerator.conf",
"help" => 0,
Expand Down Expand Up @@ -123,7 +124,9 @@ while ( $trial = shift @good_specs ) {
my $gl = Getopt::Lucid->new($trial->{spec}, \@cmd_line);
@cmd_line = @{$case->{argv}};
my %opts;
try eval { %opts = $gl->getopt->options };
my $valid_args = $case->{required} ? {requires => $case->{required}}
: {};
try eval { %opts = $gl->getopt->validate($valid_args)->options };
catch my $err;
if (defined $case->{exception}) { # expected
ok( $err && $err->isa( $case->{exception} ),
Expand Down
8 changes: 6 additions & 2 deletions t/09-negation.t
Original file line number Diff line number Diff line change
Expand Up @@ -132,21 +132,23 @@ BEGIN {
push @good_specs, {
label => "required/prereq",
spec => [
Switch("test")->required,
Switch("test"),
Param("input")->needs("output"),
Param("output"),
],
cases => [
{
argv => [ qw( --test --no-test ) ],
exception => "Getopt::Lucid::Exception::ARGV",
required => ['test'],
error_msg => _required("test"),
desc => "missing requirement after negation"
},
{
argv => [ qw( --test --input in.txt
--output out.txt --no-output ) ],
exception => "Getopt::Lucid::Exception::ARGV",
required => ['test'],
error_msg => _prereq_missing("input","output",),
desc => "missing prereq after negation"
},
Expand Down Expand Up @@ -180,7 +182,9 @@ while ( $trial = shift @good_specs ) {
my $gl = Getopt::Lucid->new($trial->{spec}, \@cmd_line);
@cmd_line = @{$case->{argv}};
my %opts;
try eval { %opts = $gl->getopt->options };
my $valid_args = $case->{required} ? {requires => $case->{required}}
: {};
try eval { %opts = $gl->getopt->validate($valid_args)->options };
catch my $err;
if (defined $case->{exception}) { # expected
ok( $err && $err->isa( $case->{exception} ),
Expand Down

0 comments on commit dc181f7

Please sign in to comment.