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