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