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