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