File Coverage

blib/lib/CLI/Application.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1              
2             package CLI::Application;
3              
4 2     2   53216 use strict;
  2         5  
  2         87  
5 2     2   12 use warnings;
  2         5  
  2         64  
6              
7              
8 2     2   12 use Carp;
  2         9  
  2         206  
9              
10 2     2   3580 use Text::Table;
  2         48822  
  2         82  
11              
12 2     2   15748 use Attribute::Handlers;
  2         17075  
  2         15  
13              
14 2     2   1934 use Module::Pluggable;
  0            
  0            
15             use Module::Load;
16              
17              
18             our $VERSION = '0.03';
19              
20             our %ACTION;
21             our $FALLBACK;
22             our $AUTOLOAD;
23              
24              
25             sub new {
26             my ($class, %rc) = @_;
27             return bless \%rc, $class;
28             }
29              
30              
31             # Take a list of command line arguments and prepare the application for
32             # execution by parsing options, detecting the action to perform, loading
33             # plugins and so on.
34             sub prepare {
35             my ($self, @argv) = @_;
36              
37             my $wanted = $self->{options} || [];
38             my @rest;
39             my %option;
40             my $class = ref $self;
41              
42             # Get list of available plugins.
43             my %plugin = map { $_ => 0 } $self->plugins;
44              
45             $self->{plugged} = {};
46              
47             # Load plugins.
48             if($self->{plugins}) {
49             while(my ($plugin, $param) = each %{$self->{plugins}}) {
50             my $package = $class . '::Plugin';
51              
52             # Allow leaving 'CLI::Application::Plugin::' away.
53             if($plugin !~ /^$package/) {
54             $plugin = $class . '::Plugin::' . $plugin;
55             }
56              
57             die "Plugin $plugin not found.\n" unless exists($plugin{$plugin});
58              
59             load $plugin;
60              
61             my $instance = $plugin->new(%$param);
62              
63             # Assign the instance of the plugin to each exported method.
64             $self->{plugged}->{$_} = $instance for($plugin->export);
65             }
66             }
67              
68              
69             # Parse options from command line arguments.
70             while(my $arg = shift(@argv)) {
71              
72             # Save non-option arguments.
73             if($arg =~ /^[^-]/) {
74             push @rest, $arg;
75             }
76              
77             # Save everything after '--'.
78             elsif($arg eq '--') {
79             push @rest, @argv;
80             last;
81             }
82              
83             # Get long options.
84             elsif($arg =~ /^--(.+?)(?:=(.*))?$/) {
85             my ($key, $value) = ($1, $2);
86             my $option = $self->_option($key);
87              
88             if($option->[2]) {
89             $value = shift @argv unless defined $value;
90              
91             die $self->usage("Missing argument for option --$key.")
92             unless defined $value;
93              
94             if(!$self->_validate_option($option->[2], $value)) {
95             my $error = "Wrong argument for option --$key.";
96             $error .= ' ' . $option->[3] if($option->[3]);
97             die $self->usage($error);
98             }
99              
100             $option{$_} = $value for(@{$option->[0]});
101             }
102             else {
103             $option{$_} = !0 for(@{$option->[0]});
104             }
105             }
106              
107             # Get short options.
108             elsif($arg =~ /^-([^-].*)$/) {
109             my $all = $1;
110              
111             while($all) {
112             $all =~ s/^(.)//;
113             my $key = $1;
114              
115             my $option = $self->_option($key);
116            
117             if($option->[2]) {
118             if($all) {
119             $option{$_} = $all for(@{$option->[0]});
120             last;
121             }
122             else {
123             my $value = shift @argv;
124              
125             die $self->usage("Missing argument for option -$key.")
126             unless(defined $value);
127              
128             if(!$self->_validate_option($option->[2], $value)) {
129             my $error = "Wrong argument for option -$key.";
130             $error .= ' ' . $option->[3] if($option->[3]);
131             die $self->usage($error);
132             }
133              
134             $option{$_} = $value for(@{$option->[0]});
135             }
136             }
137             else {
138             $option{$_} = !0 for(@{$option->[0]});
139             }
140             }
141             }
142              
143             else {
144             die "Don't know what to do with '$arg'.\n";
145             }
146             }
147              
148             $self->{parsed} = \%option;
149             $self->{rest} = \@rest;
150              
151             delete $self->{action};
152             }
153              
154              
155             # Take an error message and print it together with our usage information.
156             sub usage {
157             my ($self, @message) = @_;
158              
159             my $usage = $self->_usage;
160              
161             local $" = ' ';
162             $usage = "@message\n\n$usage" if @message;
163              
164             return $usage . "\n";
165             }
166              
167              
168             # Return (and set) the action we're going to dispatch to.
169             sub action {
170             my ($self, $action) = @_;
171              
172             if(defined $action and !$ACTION{$action}) {
173             die "Unknown action '$action'.\n";
174             }
175              
176             if(defined $action) {
177             $self->{action} = $action;
178             }
179             elsif(!$self->{action}) {
180             # Get command from remaining arguments or take default action.
181             my $command = (shift @{$self->{rest}}) || $FALLBACK;
182              
183             die $self->usage("No action.") unless $command;
184              
185             if($ACTION{$command}) {
186             $self->{action} = $command;
187             }
188             else {
189             die $self->usage("No such command.");
190             }
191             }
192              
193             return $self->{action};
194             }
195              
196              
197             # Return (and set) the value of an option.
198             sub option {
199             my ($self, $option, $argument) = @_;
200              
201             if(@_ == 3) {
202             $self->{parsed}->{$option} = $argument;
203             }
204              
205             return $self->{parsed}->{$option};
206             }
207              
208              
209             # Dispatch to the given action or the action parsed from command line
210             # arguments.
211             sub dispatch {
212             my ($self, $action) = @_;
213              
214             $action ||= $self->action || $FALLBACK;
215              
216             my $code = $ACTION{$action}->{code};
217              
218             return &{$code}($self) if($code);
219              
220             die "Nothing to do.\n";
221             }
222              
223              
224             # Return the applications name.
225             sub name { $_[0]->{name} }
226              
227              
228             # Return anything from the command line that's left after parsing options and
229             # commands.
230             sub arguments { return @{$_[0]->{rest}} }
231              
232              
233             # Generate a help message with all valid commands and options and return it.
234             sub _usage {
235             my ($self) = @_;
236              
237             my $usage = "Usage: $0 [options] \n";
238              
239             if(%ACTION) {
240             my $table = new Text::Table;
241              
242             while(my ($name, $hash) = each %ACTION) {
243             $table->add("\t" . $name, '-- ' . $hash->{text});
244             }
245              
246             $usage .= "\nACTIONS\n" . $table->table . "\n";
247             }
248              
249             my $options = $self->_option_usage;
250             $usage .= "\nOPTIONS\n$options\n" if($options);
251              
252             return $usage;
253             }
254              
255              
256             # Return a formatted table of valid options using Text::Table.
257             sub _option_usage {
258             my ($self) = @_;
259              
260             if($self->{options} and @{$self->{options}}) {
261             my $table = new Text::Table;
262              
263             for my $option (@{$self->{options}}) {
264             my ($flags, $description, $validate) = @{$option};
265              
266             my @aliases;
267              
268             for my $flag (@{$flags}) {
269             push @aliases, (length($flag) < 2 ? '-' : '--') . $flag;
270             }
271              
272             $flags = join(' | ', @aliases);
273              
274             if($validate) {
275             if(ref($validate)) {
276             if(ref($validate) eq 'ARRAY') {
277             $validate = '[' . join(' | ', @{$validate}) . ']';
278             }
279             else {
280             $validate = '<...>';
281             }
282             }
283              
284             $flags .= ' ' . $validate;
285             }
286              
287             $description ||= "Don't know what this option is good for.";
288              
289             $table->add(
290             $flags,
291             ' -- ' . $description,
292             );
293             }
294              
295             return $table->table;
296             }
297              
298             return '';
299             }
300              
301              
302             # Searches the options array for an option matching the given string and
303             # returns the first option that has a matching option flag.
304             sub _option {
305             my ($self, $needle) = @_;
306              
307             my $list = $self->{options} || [];
308              
309             for my $option (@$list) {
310             return $option if grep { $_ eq $needle } @{$option->[0]};
311             }
312              
313             die $self->usage("Unknown option '$needle'.\n");
314             }
315              
316              
317             # Take an option from the arguments hash and a value from the command line,
318             # check if the value is vaid for the option.
319             sub _validate_option {
320             my ($self, $validate, $value) = @_;
321              
322             if(ref($validate)) {
323             my $type = uc ref $validate;
324              
325             if($type eq 'ARRAY') {
326             return grep { $_ eq $value } @{$validate};
327             }
328              
329             elsif($type eq 'REGEXP' or $type eq 'SCALAR') {
330             $validate = qr/${$validate}/ if($type eq 'SCALAR');
331              
332             return $value =~ $validate;
333             }
334              
335             elsif($type eq 'HASH') {
336             # Don't know what to do with hashes yet.
337             }
338              
339             elsif($type eq 'CODE') {
340             return &{$validate}($value);
341             }
342             }
343              
344             return !0;
345             }
346              
347              
348             # Attribute to mark functions as commands.
349             sub UNIVERSAL::Command : ATTR(CODE) {
350             my ($package, $symbol, $code, $attribute, $data, $phase) = @_;
351              
352             $ACTION{*{$symbol}{NAME}} = {
353             code => $code,
354             text => ref($data)
355             ? $data->[0]
356             : 'I have no idea what this action does.',
357             };
358             }
359              
360              
361             # Attribute to mark a default command.
362             sub UNIVERSAL::Fallback : ATTR(CODE) {
363             my ($package, $symbol, $code, $attribute, $data, $phase) = @_;
364              
365             $FALLBACK = *{$symbol}{NAME};
366             }
367              
368              
369             # AUTOLOAD method to call plugin methods.
370             sub AUTOLOAD {
371             my $self = shift;
372              
373             my ($method) = (split /::/, $AUTOLOAD)[-1];
374             my $module = $self->{plugged}->{$method};
375              
376             croak "Unknown method '$method' in ", ref($self) unless $module;
377              
378             return $module->$method($self, @_);
379             }
380              
381              
382             sub DESTROY {
383             my ($self) = @_;
384              
385             delete $self->{plugged};
386             }
387              
388              
389             !0;
390              
391             __END__