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