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