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