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