File Coverage

blib/lib/App/Rad/Command.pm
Criterion Covered Total %
statement 35 88 39.7
branch 20 68 29.4
condition 9 48 18.7
subroutine 8 14 57.1
pod 2 9 22.2
total 74 227 32.6


line stmt bran cond sub pod time code
1             package App::Rad::Command;
2 1     1   16101 use strict;
  1         2  
  1         25  
3 1     1   19 use warnings;
  1         2  
  1         32  
4              
5 1     1   4 use Carp ();
  1         1  
  1         1085  
6              
7             # yeah, I know, I know, this package needs some serious refactoring
8             my %TYPES = (
9             'num' => sub { require Scalar::Util;
10             return Scalar::Util::looks_like_number(shift)
11             },
12             'str' => sub { return 1 },
13             );
14              
15              
16             #TODO: improve this so it can be defined
17             # as a standalone command?
18             sub new {
19 2     2 0 13 my ($class, $options) = (@_);
20              
21             my $self = {
22             name => ($options->{name} || '' ),
23 2   50 0   30 code => ($options->{code} || sub {} ),
      50        
24             options => {},
25             };
26 2         3 bless $self, $class;
27              
28 2 50       12 if ($options->{help} ) {
    50          
29 0         0 $self->{help} = $options->{help};
30             }
31             # if help for the command is not given, we try to
32             # get it from the :Help() attribute
33             elsif ($self->{name} ne '') {
34 0         0 require App::Rad::Help;
35 0         0 $self->{help} = App::Rad::Help->get_help_attr_for($self->{name});
36             }
37              
38             $self->set_options($options->{opts})
39 2 100       7 if $options->{opts};
40              
41 2         4 return $self;
42             }
43              
44              
45             # - "I gotta get a job that pays me to do this -- it's just too much fun"
46             # (SmokeMachine on Rad)
47             sub set_options {
48 1     1 0 3 my ($self, $options) = (@_);
49 1 50       4 return unless ref $options;
50            
51 1         1 foreach my $opt (keys %{ $options }) {
  1         4  
52 3         7 $self->set_opt($opt, $options->{$opt});
53             }
54             }
55              
56              
57             # TODO: rename this
58             sub set_opt {
59 3     3 0 5 my ($self, $opt, $options) = (@_);
60              
61 3         4 my $opt_type = ref $options;
62 3 100       5 if ($opt_type) {
63 2 50       4 Carp::croak "options can only receive HASH references"
64             unless $opt_type eq 'HASH';
65              
66 2         13 my %accepted = (
67             type => 1,
68             help => 1,
69             condition => 1,
70             aliases => 1,
71             to_stash => 1,
72             required => 1,
73             default => 1,
74             error_msg => 1,
75             conflicts_with => 1,
76             arguments => 1,
77             );
78 2         3 foreach my $value (keys %{$options}) {
  2         5  
79             Carp::croak "unknown attribute '$value' for option '$opt'\n"
80 3 50       5 unless $accepted{$value};
81            
82             # stupid error checking
83 3         4 my $opt_ref = ref $options->{$value};
84 3 100 0     33 if ($value eq 'type') {
    50 33        
    50 33        
    50 0        
    50 33        
    50 0        
    50 33        
    50 33        
    50 0        
      33        
85             Carp::croak "Invalid type (should be 'num' or 'str')\n"
86 2 50 33     13 unless $opt_ref or $TYPES{ lc $options->{$value} };
87             }
88             elsif ($value eq 'condition' and (!$opt_ref or $opt_ref ne 'CODE')) {
89 0         0 Carp::croak "'condition' attribute must be a CODE reference\n"
90             }
91             elsif ($value eq 'help' and $opt_ref) {
92 0         0 Carp::croak "'help' attribute must be a string\n"
93             }
94             elsif ($value eq 'aliases' and ($opt_ref and $opt_ref ne 'ARRAY')) {
95 0         0 Carp::croak "'aliases' attribute must be a string or an ARRAY ref\n";
96             }
97             elsif ($value eq 'to_stash' and ($opt_ref and $opt_ref ne 'ARRAY')) {
98 0         0 Carp::croak "'to_stash' attribute must be a scalar or an ARRAY ref\n";
99             }
100             elsif($value eq 'required') {
101 0 0       0 if ($accepted{'default'}) {
102 0         0 $accepted{'required'} = 0;
103             }
104             else {
105 0         0 Carp::croak "'required' and 'default' attributes cannot be used at the same time\n";
106             }
107             }
108             elsif($value eq 'default') {
109 0 0       0 if ($accepted{'required'}) {
110 0         0 $accepted{'default'} = 0;
111             }
112             else {
113 0         0 Carp::croak "'required' and 'default' attributes cannot be used at the same time\n";
114             }
115             }
116             elsif ($value eq 'error_msg' and $opt_ref) {
117 0         0 Carp::croak "'error_msg' attribute must be a string\n"
118             }
119             elsif ($value eq 'conflicts_with' and ($opt_ref and $opt_ref ne 'ARRAY')) {
120 0         0 Carp::croak "'conflicts_with' attribute must be a scalar or an ARRAY ref\n";
121             }
122 3         11 $self->{opts}->{$opt}->{$value} = $options->{$value};
123             }
124             }
125             # got a string. Set it as the help for the option
126             else {
127 1         3 $self->{opts}->{$opt}->{help} = $options;
128             }
129             }
130              
131             sub options {
132 0     0 1 0 return $_[0]->{'options'};
133             }
134              
135             # this function is here to replace _parser_opt
136             # we should find a better name for it, but...later.
137              
138             # it returns the number of arguments left
139             sub setopt {
140 0     0 0 0 my ($self, $opt_name, $opt_val) = (@_);
141 0         0 my $arguments_left = 0;
142              
143             # if the app has custom options for that command,
144             # we check them now. Otherwise, just accept it.
145 0 0       0 if ( keys ( %{$self->{opts}} ) > 0 ) {
  0         0  
146 0   0     0 my $actual_opt_name = $self->_get_option_name($opt_name)
147             || die "invalid option '$opt_name'\n";
148              
149 0         0 $opt_name = $actual_opt_name;
150 0         0 my $opt = $self->{opts}->{$opt_name};
151              
152             # if no value was given to the option, here's what we do:
153 0 0       0 if ( not defined $opt_val ) {
154             # first, if we have a default value to use, use it.
155 0 0       0 if (defined $opt->{default} ) {
    0          
156             $opt_val = $opt->{default}
157 0         0 }
158             # if a required number of arguments was set
159             # for the option, we will not use the auto-increment
160             elsif ( defined $opt->{arguments} ) {
161 0         0 return $opt->{arguments};
162             }
163             # otherwise, do an auto-increment
164             else {
165             # TODO: on the test below, do a looks_like_number ?
166             $opt_val = defined $self->{options}->{$opt_name}
167 0 0       0 ? $self->{options}->{$opt_name} + 1
168             : 1
169             ;
170             }
171             }
172              
173             # type check (TODO: it would be nice if we allowed pluggable types)
174 0 0 0     0 if ( $opt->{type} and not $TYPES{$opt->{type}}->($opt_val) ) {
175 0         0 die "option '$opt_name' requires a value of type '" . $opt->{type} . "'\n";
176             }
177              
178             # condition check
179 0 0 0     0 if ( $opt->{condition} and not $opt->{condition}->($opt_val) ) {
180             die "incorrect value for option '$opt_name'" .
181 0 0       0 (defined $opt->{error_msg} ? ": " . $opt->{error_msg} : '')
182             . "\n";
183             }
184            
185             #TODO: conflict check?
186            
187             #TODO: arguments left check
188             }
189             else {
190             # no custom options, so we just make sure
191             # there is a value to set.
192 0 0       0 if (not defined $opt_val) {
193             $opt_val = defined $self->{options}->{$opt_name}
194 0 0       0 ? $self->{options}->{$opt_name} + 1
195             : 1
196             ;
197             }
198             }
199 0         0 $self->options->{$opt_name} = $opt_val;
200 0         0 return $arguments_left;
201             }
202              
203             # returns option name, or undef if it's not found
204             sub _get_option_name {
205 0     0   0 my ($self, $opt) = (@_);
206            
207 0 0       0 return $opt if exists $self->{opts}->{$opt};
208            
209             ALIAS_CHECK: # try to find whether we were given an alias instead
210 0         0 foreach my $valid_opt (keys %{$self->{opts}}) {
  0         0  
211            
212             # get aliases list
213 0         0 my $aliases = $self->{opts}->{$valid_opt}->{aliases};
214 0 0       0 $aliases = [$aliases] unless ref $aliases;
215              
216             # get token if it's inside alias list,
217 0         0 foreach my $alias ( @{$aliases} ) {
  0         0  
218 0 0 0     0 return $valid_opt if $alias and $opt eq $alias;
219             }
220             }
221 0         0 return;
222             }
223              
224             sub is_option {
225 0     0 0 0 my ($self, $opt) = (@_);
226            
227             # if there are no registered options, everything can be an option
228 0 0       0 return 1 unless scalar keys %{$self->{opts}};
  0         0  
229            
230 0 0       0 return (exists $self->{opts}->{$opt}) ? 1 : 0;
231             }
232              
233 1     1 0 9 sub name { return shift->{name} }
234 1     1 1 5 sub help { return shift->{help} }
235              
236             sub run {
237 0     0 0   my $self = shift;
238 0           my $c = shift;
239 0           $self->{code}->($c, @_);
240             }
241              
242              
243             #TODO: a.k.a. long help - called with ./myapp help command
244             #sub description {
245             # my $self = shift;
246             # return help . option_help # or something like that
247             #}
248              
249             42;
250             __END__