Fix bug when handling upper/mixedcase accessors
[gitmo/MooseX-Getopt.git] / lib / MooseX / Getopt.pm
CommitLineData
5dac17c3 1
2package MooseX::Getopt;
3use Moose::Role;
4
8034a232 5use MooseX::Getopt::OptionTypeMap;
5dac17c3 6use MooseX::Getopt::Meta::Attribute;
0f8232b6 7use MooseX::Getopt::Meta::Attribute::NoGetopt;
5dac17c3 8
b56c8123 9use Carp ();
10
75a6449b 11use Getopt::Long (); # GLD uses it anyway, doesn't hurt
12use constant HAVE_GLD => not not eval { require Getopt::Long::Descriptive };
13
5581bb1c 14our $VERSION = '0.23';
8034a232 15our $AUTHORITY = 'cpan:STEVAN';
16
8f95e21d 17has ARGV => (is => 'rw', isa => 'ArrayRef', metaclass => "NoGetopt");
18has extra_argv => (is => 'rw', isa => 'ArrayRef', metaclass => "NoGetopt");
3899e5df 19
5dac17c3 20sub new_with_options {
bff3807b 21 my ($class, @params) = @_;
5dac17c3 22
4e086633 23 my $config_from_file;
b4a79051 24 if($class->meta->does_role('MooseX::ConfigFromFile')) {
4e086633 25 local @ARGV = @ARGV;
26
b4a79051 27 my $configfile;
4e086633 28 my $opt_parser = Getopt::Long::Parser->new( config => [ qw( pass_through ) ] );
29 $opt_parser->getoptions( "configfile=s" => \$configfile );
b4a79051 30
4e086633 31 if(!defined $configfile) {
9f1ec7c0 32 my $cfmeta = $class->meta->find_attribute_by_name('configfile');
b4a79051 33 $configfile = $cfmeta->default if $cfmeta->has_default;
aa03fdad 34 if (ref $configfile eq 'CODE') {
35 # not sure theres a lot you can do with the class and may break some assumptions
36 # warn?
37 $configfile = &$configfile($class);
38 }
2c379a5c 39 if (defined $configfile) {
40 $config_from_file = eval {
41 $class->get_config_from_file($configfile);
42 };
43 if ($@) {
44 die $@ unless $@ =~ /Specified configfile '\Q$configfile\E' does not exist/;
45 }
46 }
b4a79051 47 }
2c379a5c 48 else {
4e086633 49 $config_from_file = $class->get_config_from_file($configfile);
b4a79051 50 }
ee69c4ba 51 }
52
b56c8123 53 my $constructor_params = ( @params == 1 ? $params[0] : {@params} );
54
55 Carp::croak("Single parameters to new_with_options() must be a HASH ref")
56 unless ref($constructor_params) eq 'HASH';
57
4e086633 58 my %processed = $class->_parse_argv(
59 options => [
60 $class->_attrs_to_options( $config_from_file )
b56c8123 61 ],
62 params => $constructor_params,
4e086633 63 );
64
65 my $params = $config_from_file ? { %$config_from_file, %{$processed{params}} } : $processed{params};
66
47a89a8d 67 # did the user request usage information?
68 if ( $processed{usage} && ($params->{'?'} or $params->{help} or $params->{usage}) )
69 {
70 $processed{usage}->die();
71 }
72
ee211848 73 $class->new(
74 ARGV => $processed{argv_copy},
75 extra_argv => $processed{argv},
69061f7a 76 %$constructor_params, # explicit params to ->new
ee69c4ba 77 %$params, # params from CLI
ee211848 78 );
79}
80
81sub _parse_argv {
0e715336 82 my ( $class, %params ) = @_;
6bb4cb66 83
f7655c45 84 local @ARGV = @{ $params{params}{argv} || \@ARGV };
0e715336 85
75a6449b 86 my ( $opt_spec, $name_to_init_arg ) = ( HAVE_GLD ? $class->_gld_spec(%params) : $class->_traditional_spec(%params) );
ee211848 87
88 # Get a clean copy of the original @ARGV
89 my $argv_copy = [ @ARGV ];
90
6bb4cb66 91 my @err;
92
93 my ( $parsed_options, $usage ) = eval {
94 local $SIG{__WARN__} = sub { push @err, @_ };
75a6449b 95
96 if ( HAVE_GLD ) {
97 return Getopt::Long::Descriptive::describe_options($class->_usage_format(%params), @$opt_spec);
98 } else {
99 my %options;
100 Getopt::Long::GetOptions(\%options, @$opt_spec);
3cc60720 101 return ( \%options, undef );
75a6449b 102 }
6bb4cb66 103 };
104
105 die join "", grep { defined } @err, $@ if @err or $@;
ee211848 106
107 # Get a copy of the Getopt::Long-mangled @ARGV
108 my $argv_mangled = [ @ARGV ];
109
0e715336 110 my %constructor_args = (
4e086633 111 map {
112 $name_to_init_arg->{$_} => $parsed_options->{$_}
113 } keys %$parsed_options,
ee211848 114 );
0e715336 115
913e7020 116 return (
117 params => \%constructor_args,
118 argv_copy => $argv_copy,
119 argv => $argv_mangled,
3cc60720 120 ( defined($usage) ? ( usage => $usage ) : () ),
913e7020 121 );
0e715336 122}
123
9287e912 124sub _usage_format {
125 return "usage: %c %o";
126}
127
75a6449b 128sub _traditional_spec {
129 my ( $class, %params ) = @_;
4e086633 130
75a6449b 131 my ( @options, %name_to_init_arg, %options );
132
133 foreach my $opt ( @{ $params{options} } ) {
134 push @options, $opt->{opt_string};
41d0ab09 135
7b566394 136 my $identifier = lc($opt->{name});
41d0ab09 137 $identifier =~ s/\W/_/g; # Getopt::Long does this to all option names
138
139 $name_to_init_arg{$identifier} = $opt->{init_arg};
75a6449b 140 }
141
142 return ( \@options, \%name_to_init_arg );
143}
144
0e715336 145sub _gld_spec {
146 my ( $class, %params ) = @_;
147
148 my ( @options, %name_to_init_arg );
149
b56c8123 150 my $constructor_params = $params{params};
151
0e715336 152 foreach my $opt ( @{ $params{options} } ) {
153 push @options, [
154 $opt->{opt_string},
155 $opt->{doc} || ' ', # FIXME new GLD shouldn't need this hack
156 {
b56c8123 157 ( ( $opt->{required} && !exists($constructor_params->{$opt->{init_arg}}) ) ? (required => $opt->{required}) : () ),
630657d5 158 # NOTE:
159 # remove this 'feature' because it didn't work
160 # all the time, and so is better to not bother
161 # since Moose will handle the defaults just
162 # fine anyway.
163 # - SL
164 #( exists $opt->{default} ? (default => $opt->{default}) : () ),
0e715336 165 },
166 ];
167
7b566394 168 my $identifier = lc($opt->{name});
41d0ab09 169 $identifier =~ s/\W/_/g; # Getopt::Long does this to all option names
170
171 $name_to_init_arg{$identifier} = $opt->{init_arg};
0e715336 172 }
173
174 return ( \@options, \%name_to_init_arg );
ee211848 175}
176
bff3807b 177sub _compute_getopt_attrs {
178 my $class = shift;
bff3807b 179 grep {
adbe3e57 180 $_->does("MooseX::Getopt::Meta::Attribute::Trait")
bff3807b 181 or
182 $_->name !~ /^_/
adbe3e57 183 } grep {
184 !$_->does('MooseX::Getopt::Meta::Attribute::Trait::NoGetopt')
e2dcd71d 185 } $class->meta->get_all_attributes
bff3807b 186}
187
4ad81caf 188sub _get_cmd_flags_for_attr {
189 my ( $class, $attr ) = @_;
190
191 my $flag = $attr->name;
192
193 my @aliases;
194
adbe3e57 195 if ($attr->does('MooseX::Getopt::Meta::Attribute::Trait')) {
4ad81caf 196 $flag = $attr->cmd_flag if $attr->has_cmd_flag;
197 @aliases = @{ $attr->cmd_aliases } if $attr->has_cmd_aliases;
198 }
199
200 return ( $flag, @aliases );
201}
202
ee211848 203sub _attrs_to_options {
204 my $class = shift;
4e086633 205 my $config_from_file = shift || {};
ee211848 206
207 my @options;
208
bff3807b 209 foreach my $attr ($class->_compute_getopt_attrs) {
4ad81caf 210 my ( $flag, @aliases ) = $class->_get_cmd_flags_for_attr($attr);
ee211848 211
4ad81caf 212 my $opt_string = join(q{|}, $flag, @aliases);
de75868f 213
f4308031 214 if ($attr->name eq 'configfile') {
215 $opt_string .= '=s';
216 }
217 elsif ($attr->has_type_constraint) {
365e5784 218 my $type = $attr->type_constraint;
219 if (MooseX::Getopt::OptionTypeMap->has_option_type($type)) {
220 $opt_string .= MooseX::Getopt::OptionTypeMap->get_option_type($type)
5dac17c3 221 }
222 }
f63e6310 223
ee211848 224 push @options, {
4ad81caf 225 name => $flag,
ee211848 226 init_arg => $attr->init_arg,
227 opt_string => $opt_string,
4e086633 228 required => $attr->is_required && !$attr->has_default && !$attr->has_builder && !exists $config_from_file->{$attr->name},
630657d5 229 # NOTE:
230 # this "feature" was breaking because
231 # Getopt::Long::Descriptive would return
232 # the default value as if it was a command
233 # line flag, which would then override the
234 # one passed into a constructor.
235 # See 100_gld_default_bug.t for an example
236 # - SL
237 #( ( $attr->has_default && ( $attr->is_default_a_coderef xor $attr->is_lazy ) ) ? ( default => $attr->default({}) ) : () ),
ee211848 238 ( $attr->has_documentation ? ( doc => $attr->documentation ) : () ),
239 }
f63e6310 240 }
241
ee211848 242 return @options;
5dac17c3 243}
244
8034a232 245no Moose::Role; 1;
5dac17c3 246
247__END__
248
249=pod
250
251=head1 NAME
252
8034a232 253MooseX::Getopt - A Moose role for processing command line options
5dac17c3 254
255=head1 SYNOPSIS
256
4e086633 257 ## In your class
5dac17c3 258 package My::App;
259 use Moose;
4e086633 260
5dac17c3 261 with 'MooseX::Getopt';
4e086633 262
5dac17c3 263 has 'out' => (is => 'rw', isa => 'Str', required => 1);
264 has 'in' => (is => 'rw', isa => 'Str', required => 1);
4e086633 265
5dac17c3 266 # ... rest of the class here
4e086633 267
5dac17c3 268 ## in your script
269 #!/usr/bin/perl
4e086633 270
5dac17c3 271 use My::App;
4e086633 272
5dac17c3 273 my $app = My::App->new_with_options();
274 # ... rest of the script here
4e086633 275
5dac17c3 276 ## on the command line
277 % perl my_app_script.pl -in file.input -out file.dump
278
279=head1 DESCRIPTION
280
4e086633 281This is a role which provides an alternate constructor for creating
282objects using parameters passed in from the command line.
8034a232 283
4e086633 284This module attempts to DWIM as much as possible with the command line
285params by introspecting your class's attributes. It will use the name
286of your attribute as the command line option, and if there is a type
8034a232 287constraint defined, it will configure Getopt::Long to handle the option
3899e5df 288accordingly.
289
2814de27 290You can use the trait L<MooseX::Getopt::Meta::Attribute::Trait> or the
291attribute metaclass L<MooseX::Getopt::Meta::Attribute> to get non-default
292commandline option names and aliases.
3899e5df 293
2814de27 294You can use the trait L<MooseX::Getopt::Meta::Attribute::Trait::NoGetopt>
295or the attribute metaclass L<MooseX::Getopt::Meta::Attribute::NoGetopt>
0f8232b6 296to have C<MooseX::Getopt> ignore your attribute in the commandline options.
297
3899e5df 298By default, attributes which start with an underscore are not given
299commandline argument support, unless the attribute's metaclass is set
7f5f3d94 300to L<MooseX::Getopt::Meta::Attribute>. If you don't want your accessors
301to have the leading underscore in their name, you can do this:
3d9a716d 302
303 # for read/write attributes
304 has '_foo' => (accessor => 'foo', ...);
4e086633 305
3d9a716d 306 # or for read-only attributes
4e086633 307 has '_bar' => (reader => 'bar', ...);
3d9a716d 308
4e086633 309This will mean that Getopt will not handle a --foo param, but your
310code can still call the C<foo> method.
8034a232 311
ee69c4ba 312If your class also uses a configfile-loading role based on
313L<MooseX::ConfigFromFile>, such as L<MooseX::SimpleConfig>,
314L<MooseX::Getopt>'s C<new_with_options> will load the configfile
b4a79051 315specified by the C<--configfile> option (or the default you've
316given for the configfile attribute) for you.
317
318Options specified in multiple places follow the following
319precendence order: commandline overrides configfile, which
320overrides explicit new_with_options parameters.
ee69c4ba 321
8034a232 322=head2 Supported Type Constraints
323
324=over 4
325
326=item I<Bool>
327
4e086633 328A I<Bool> type constraint is set up as a boolean option with
8034a232 329Getopt::Long. So that this attribute description:
330
331 has 'verbose' => (is => 'rw', isa => 'Bool');
332
4e086633 333would translate into C<verbose!> as a Getopt::Long option descriptor,
8034a232 334which would enable the following command line options:
335
336 % my_script.pl --verbose
4e086633 337 % my_script.pl --noverbose
338
8034a232 339=item I<Int>, I<Float>, I<Str>
340
4e086633 341These type constraints are set up as properly typed options with
8034a232 342Getopt::Long, using the C<=i>, C<=f> and C<=s> modifiers as appropriate.
343
344=item I<ArrayRef>
345
346An I<ArrayRef> type constraint is set up as a multiple value option
347in Getopt::Long. So that this attribute description:
348
349 has 'include' => (
4e086633 350 is => 'rw',
351 isa => 'ArrayRef',
8034a232 352 default => sub { [] }
353 );
354
4e086633 355would translate into C<includes=s@> as a Getopt::Long option descriptor,
8034a232 356which would enable the following command line options:
357
358 % my_script.pl --include /usr/lib --include /usr/local/lib
359
360=item I<HashRef>
361
362A I<HashRef> type constraint is set up as a hash value option
363in Getopt::Long. So that this attribute description:
364
365 has 'define' => (
4e086633 366 is => 'rw',
367 isa => 'HashRef',
8034a232 368 default => sub { {} }
369 );
370
4e086633 371would translate into C<define=s%> as a Getopt::Long option descriptor,
8034a232 372which would enable the following command line options:
373
374 % my_script.pl --define os=linux --define vendor=debian
375
376=back
377
378=head2 Custom Type Constraints
379
4e086633 380It is possible to create custom type constraint to option spec
8034a232 381mappings if you need them. The process is fairly simple (but a
4e086633 382little verbose maybe). First you create a custom subtype, like
8034a232 383so:
384
385 subtype 'ArrayOfInts'
386 => as 'ArrayRef'
387 => where { scalar (grep { looks_like_number($_) } @$_) };
388
389Then you register the mapping, like so:
390
391 MooseX::Getopt::OptionTypeMap->add_option_type_to_map(
392 'ArrayOfInts' => '=i@'
393 );
394
4e086633 395Now any attribute declarations using this type constraint will
8034a232 396get the custom option spec. So that, this:
397
398 has 'nums' => (
399 is => 'ro',
400 isa => 'ArrayOfInts',
401 default => sub { [0] }
402 );
403
404Will translate to the following on the command line:
405
406 % my_script.pl --nums 5 --nums 88 --nums 199
407
4e086633 408This example is fairly trivial, but more complex validations are
8034a232 409easily possible with a little creativity. The trick is balancing
410the type constraint validations with the Getopt::Long validations.
411
412Better examples are certainly welcome :)
413
f63e6310 414=head2 Inferred Type Constraints
415
416If you define a custom subtype which is a subtype of one of the
417standard L</Supported Type Constraints> above, and do not explicitly
418provide custom support as in L</Custom Type Constraints> above,
419MooseX::Getopt will treat it like the parent type for Getopt
420purposes.
421
422For example, if you had the same custom C<ArrayOfInts> subtype
423from the examples above, but did not add a new custom option
424type for it to the C<OptionTypeMap>, it would be treated just
425like a normal C<ArrayRef> type for Getopt purposes (that is,
426C<=s@>).
427
5dac17c3 428=head1 METHODS
429
430=over 4
431
432=item B<new_with_options (%params)>
433
4e086633 434This method will take a set of default C<%params> and then collect
8034a232 435params from the command line (possibly overriding those in C<%params>)
436and then return a newly constructed object.
437
0e3f178a 438The special parameter C<argv>, if specified should point to an array
439reference with an array to use instead of C<@ARGV>.
440
f63e6310 441If L<Getopt::Long/GetOptions> fails (due to invalid arguments),
442C<new_with_options> will throw an exception.
443
47a89a8d 444If L<Getopt::Long::Descriptive> is installed and any of the following
445command line params are passed, the program will exit with usage
446information. You can add descriptions for each option by including a
447B<documentation> option for each attribute to document.
448
449 --?
450 --help
451 --usage
452
b766829d 453If you have L<Getopt::Long::Descriptive> the C<usage> param is also passed to
fad5da09 454C<new>.
455
3899e5df 456=item B<ARGV>
457
458This accessor contains a reference to a copy of the C<@ARGV> array
f63e6310 459as it originally existed at the time of C<new_with_options>.
460
461=item B<extra_argv>
462
463This accessor contains an arrayref of leftover C<@ARGV> elements that
464L<Getopt::Long> did not parse. Note that the real C<@ARGV> is left
465un-mangled.
3899e5df 466
5dac17c3 467=item B<meta>
468
8034a232 469This returns the role meta object.
470
5dac17c3 471=back
472
473=head1 BUGS
474
4e086633 475All complex software has bugs lurking in it, and this module is no
5dac17c3 476exception. If you find a bug please either email me, or add the bug
477to cpan-RT.
478
479=head1 AUTHOR
480
481Stevan Little E<lt>stevan@iinteractive.comE<gt>
482
e2911e34 483Brandon L. Black, E<lt>blblack@gmail.comE<gt>
484
630657d5 485Yuval Kogman, E<lt>nothingmuch@woobling.orgE<gt>
486
78a71ae5 487=head1 CONTRIBUTORS
488
489Ryan D Johnson, E<lt>ryan@innerfence.comE<gt>
490
47a89a8d 491Drew Taylor, E<lt>drew@drewtaylor.comE<gt>
492
5dac17c3 493=head1 COPYRIGHT AND LICENSE
494
adbe3e57 495Copyright 2007-2008 by Infinity Interactive, Inc.
5dac17c3 496
497L<http://www.iinteractive.com>
498
499This library is free software; you can redistribute it and/or modify
500it under the same terms as Perl itself.
501
502=cut