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