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