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