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