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