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