X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMooseX%2FGetopt.pm;h=98a72212ce3ab53342b4ce05f9d3062054af112c;hb=41d0ab091f0df3e2c1eae2401545c07c3798f76f;hp=8c2f4b0a3c84d38a7309cbc2974058a2b26415be;hpb=e2911e34263bf6cfcd0e81fe5c801bdd71e48a29;p=gitmo%2FMooseX-Getopt.git diff --git a/lib/MooseX/Getopt.pm b/lib/MooseX/Getopt.pm index 8c2f4b0..98a7221 100644 --- a/lib/MooseX/Getopt.pm +++ b/lib/MooseX/Getopt.pm @@ -2,68 +2,201 @@ package MooseX::Getopt; use Moose::Role; -use Getopt::Long; - use MooseX::Getopt::OptionTypeMap; use MooseX::Getopt::Meta::Attribute; +use MooseX::Getopt::Meta::Attribute::NoGetopt; + +use Getopt::Long (); # GLD uses it anyway, doesn't hurt +use constant HAVE_GLD => not not eval { require Getopt::Long::Descriptive }; -our $VERSION = '0.03'; +our $VERSION = '0.11'; our $AUTHORITY = 'cpan:STEVAN'; -has ARGV => (is => 'rw', isa => 'ArrayRef'); +has ARGV => (is => 'rw', isa => 'ArrayRef', metaclass => "NoGetopt"); +has extra_argv => (is => 'rw', isa => 'ArrayRef', metaclass => "NoGetopt"); sub new_with_options { - my ($class, %params) = @_; + my ($class, @params) = @_; - my (@options, %name_to_init_arg); - foreach my $attr ($class->meta->compute_all_applicable_attributes) { - my $name = $attr->name; + my %processed = $class->_parse_argv( + options => [ + $class->_attrs_to_options( @params ) + ] + ); - my $aliases; + my $params = $processed{params}; - if ($attr->isa('MooseX::Getopt::Meta::Attribute')) { - $name = $attr->cmd_flag if $attr->has_cmd_flag; - $aliases = $attr->cmd_aliases if $attr->has_cmd_aliases; + if($class->meta->does_role('MooseX::ConfigFromFile')) { + my $configfile; + + if(defined $params->{configfile}) { + $configfile = $params->{configfile} } else { - next if $name =~ /^_/; + my $cfmeta = $class->meta->get_attribute('configfile'); + $configfile = $cfmeta->default if $cfmeta->has_default; } - - $name_to_init_arg{$name} = $attr->init_arg; - - my $opt_string = $aliases - ? join(q{|}, $name, @$aliases) - : $name; - if ($attr->has_type_constraint) { - my $type_name = $attr->type_constraint->name; - if (MooseX::Getopt::OptionTypeMap->has_option_type($type_name)) { - $opt_string .= MooseX::Getopt::OptionTypeMap->get_option_type($type_name); - } + if(defined $configfile) { + %$params = ( + %{$class->get_config_from_file($configfile)}, + %$params, + ); } - - push @options => $opt_string; } - my $saved_argv = [ @ARGV ]; - my %options; - - GetOptions(\%options, @options); - - #use Data::Dumper; - #warn Dumper \@options; - #warn Dumper \%name_to_init_arg; - #warn Dumper \%options; - $class->new( - ARGV => $saved_argv, - %params, + ARGV => $processed{argv_copy}, + extra_argv => $processed{argv}, + @params, # explicit params to ->new + %$params, # params from CLI + ); +} + +sub _parse_argv { + my ( $class, %params ) = @_; + + local @ARGV = @{ $params{argv} || \@ARGV }; + + my ( $opt_spec, $name_to_init_arg ) = ( HAVE_GLD ? $class->_gld_spec(%params) : $class->_traditional_spec(%params) ); + + # Get a clean copy of the original @ARGV + my $argv_copy = [ @ARGV ]; + + my @err; + + my ( $parsed_options, $usage ) = eval { + local $SIG{__WARN__} = sub { push @err, @_ }; + + if ( HAVE_GLD ) { + return Getopt::Long::Descriptive::describe_options($class->_usage_format(%params), @$opt_spec); + } else { + my %options; + Getopt::Long::GetOptions(\%options, @$opt_spec); + return ( \%options, undef ); + } + }; + + die join "", grep { defined } @err, $@ if @err or $@; + + # Get a copy of the Getopt::Long-mangled @ARGV + my $argv_mangled = [ @ARGV ]; + + my %constructor_args = ( map { - $name_to_init_arg{$_} => $options{$_} - } keys %options, + $name_to_init_arg->{$_} => $parsed_options->{$_} + } keys %$parsed_options, + ); + + return ( + params => \%constructor_args, + argv_copy => $argv_copy, + argv => $argv_mangled, + ( defined($usage) ? ( usage => $usage ) : () ), ); } +sub _usage_format { + return "usage: %c %o"; +} + +sub _traditional_spec { + my ( $class, %params ) = @_; + + my ( @options, %name_to_init_arg, %options ); + + foreach my $opt ( @{ $params{options} } ) { + push @options, $opt->{opt_string}; + + my $identifier = $opt->{name}; + $identifier =~ s/\W/_/g; # Getopt::Long does this to all option names + + $name_to_init_arg{$identifier} = $opt->{init_arg}; + } + + return ( \@options, \%name_to_init_arg ); +} + +sub _gld_spec { + my ( $class, %params ) = @_; + + my ( @options, %name_to_init_arg ); + + foreach my $opt ( @{ $params{options} } ) { + push @options, [ + $opt->{opt_string}, + $opt->{doc} || ' ', # FIXME new GLD shouldn't need this hack + { + ( $opt->{required} ? (required => $opt->{required}) : () ), + ( exists $opt->{default} ? (default => $opt->{default}) : () ), + }, + ]; + + my $identifier = $opt->{name}; + $identifier =~ s/\W/_/g; # Getopt::Long does this to all option names + + $name_to_init_arg{$identifier} = $opt->{init_arg}; + } + + return ( \@options, \%name_to_init_arg ); +} + +sub _compute_getopt_attrs { + my $class = shift; + grep { + $_->isa("MooseX::Getopt::Meta::Attribute") + or + $_->name !~ /^_/ + && + !$_->isa('MooseX::Getopt::Meta::Attribute::NoGetopt') + } $class->meta->compute_all_applicable_attributes +} + +sub _get_cmd_flags_for_attr { + my ( $class, $attr ) = @_; + + my $flag = $attr->name; + + my @aliases; + + if ($attr->isa('MooseX::Getopt::Meta::Attribute')) { + $flag = $attr->cmd_flag if $attr->has_cmd_flag; + @aliases = @{ $attr->cmd_aliases } if $attr->has_cmd_aliases; + } + + return ( $flag, @aliases ); +} + +sub _attrs_to_options { + my $class = shift; + + my @options; + + foreach my $attr ($class->_compute_getopt_attrs) { + my ( $flag, @aliases ) = $class->_get_cmd_flags_for_attr($attr); + + my $opt_string = join(q{|}, $flag, @aliases); + + if ($attr->has_type_constraint) { + my $type_name = $attr->type_constraint->name; + if (MooseX::Getopt::OptionTypeMap->has_option_type($type_name)) { + $opt_string .= MooseX::Getopt::OptionTypeMap->get_option_type($type_name) + } + } + + push @options, { + name => $flag, + init_arg => $attr->init_arg, + opt_string => $opt_string, + required => $attr->is_required && !$attr->has_default && !$attr->has_builder, + ( ( $attr->has_default && ( $attr->is_default_a_coderef xor $attr->is_lazy ) ) ? ( default => $attr->default({}) ) : () ), + ( $attr->has_documentation ? ( doc => $attr->documentation ) : () ), + } + } + + return @options; +} + no Moose::Role; 1; __END__ @@ -112,9 +245,32 @@ accordingly. You can use the attribute metaclass L to get non-default commandline option names and aliases. +You can use the attribute metaclass L +to have C ignore your attribute in the commandline options. + By default, attributes which start with an underscore are not given commandline argument support, unless the attribute's metaclass is set -to L. +to L. If you don't want you accessors +to have the leading underscore in thier name, you can do this: + + # for read/write attributes + has '_foo' => (accessor => 'foo', ...); + + # or for read-only attributes + has '_bar' => (reader => 'bar', ...); + +This will mean that Getopt will not handle a --foo param, but your +code can still call the C method. + +If your class also uses a configfile-loading role based on +L, such as L, +L's C will load the configfile +specified by the C<--configfile> option (or the default you've +given for the configfile attribute) for you. + +Options specified in multiple places follow the following +precendence order: commandline overrides configfile, which +overrides explicit new_with_options parameters. =head2 Supported Type Constraints @@ -208,6 +364,20 @@ the type constraint validations with the Getopt::Long validations. Better examples are certainly welcome :) +=head2 Inferred Type Constraints + +If you define a custom subtype which is a subtype of one of the +standard L above, and do not explicitly +provide custom support as in L above, +MooseX::Getopt will treat it like the parent type for Getopt +purposes. + +For example, if you had the same custom C subtype +from the examples above, but did not add a new custom option +type for it to the C, it would be treated just +like a normal C type for Getopt purposes (that is, +C<=s@>). + =head1 METHODS =over 4 @@ -218,11 +388,22 @@ This method will take a set of default C<%params> and then collect params from the command line (possibly overriding those in C<%params>) and then return a newly constructed object. +If L fails (due to invalid arguments), +C will throw an exception. + +If you have L a the C param is also passed to +C. + =item B This accessor contains a reference to a copy of the C<@ARGV> array -which was copied before L mangled it, in case you want -to see your original options. +as it originally existed at the time of C. + +=item B + +This accessor contains an arrayref of leftover C<@ARGV> elements that +L did not parse. Note that the real C<@ARGV> is left +un-mangled. =item B