File Coverage

blib/lib/App/Rad/Command.pm
Criterion Covered Total %
statement 9 101 8.9
branch 0 76 0.0
condition 0 43 0.0
subroutine 3 12 25.0
pod 1 6 16.6
total 13 238 5.4


line stmt bran cond sub pod time code
1             package App::Rad::Command;
2 1     1   10 use strict;
  1         1  
  1         60  
3 1     1   7 use warnings;
  1         2  
  1         38  
4              
5 1     1   570 use Carp ();
  1         5  
  1         3862  
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 0     0 0   my ($class, $options) = (@_);
20              
21             my $self = {
22             name => ($options->{name} || '' ),
23 0   0 0     code => ($options->{code} || sub {} ),
  0   0        
24             };
25 0           bless $self, $class;
26              
27 0 0         if ($options->{help} ) {
    0          
28 0           $self->{help} = $options->{help};
29             }
30             # if help for the command is not given, we try to
31             # get it from the :Help() attribute
32             elsif ($self->{name} ne '') {
33 0           require App::Rad::Help;
34 0           $self->{help} = App::Rad::Help->get_help_attr_for($self->{name});
35             }
36              
37 0 0         $self->set_arguments($options->{args})
38             if $options->{args};
39              
40 0           return $self;
41             }
42              
43              
44             # - "I gotta get a job that pays me to do this -- it's just too much fun"
45             # (SmokeMachine on Rad)
46             sub set_arguments {
47 0     0 0   my ($self, $arguments) = (@_);
48 0 0         return unless ref $arguments;
49            
50 0           foreach my $arg (keys %{ $arguments }) {
  0            
51 0           $self->set_arg($arg, $arguments->{$arg});
52             }
53             }
54              
55             sub set_arg {
56 0     0 0   my ($self, $arg, $options) = (@_);
57              
58 0           my $opt_type = ref $options;
59 0 0         if ($opt_type) {
60 0 0         Carp::croak "arguments can only receive HASH references"
61             unless $opt_type eq 'HASH';
62              
63 0           my %accepted = (
64             type => 1,
65             help => 1,
66             condition => 1,
67             aliases => 1,
68             to_stash => 1,
69             required => 1,
70             default => 1,
71             error_msg => 1,
72             conflicts_with => 1,
73             prompt => 1,
74             );
75 0           foreach my $value (keys %{$options}) {
  0            
76 0 0         Carp::croak "unknown attribute '$value' for argument '$arg'\n"
77             unless $accepted{$value};
78            
79             # stupid error checking
80 0           my $opt_ref = ref $options->{$value};
81 0 0 0       if ($value eq 'type') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
82 0 0 0       Carp::croak "Invalid type (should be 'num', 'str' or 'any')\n"
83             unless $opt_ref or $TYPES{ lc $options->{$value} };
84             }
85             elsif ($value eq 'condition' and (!$opt_ref or $opt_ref ne 'CODE')) {
86 0           Carp::croak "'condition' attribute must be a CODE reference\n"
87             }
88             elsif ($value eq 'help' and $opt_ref) {
89 0           Carp::croak "'help' attribute must be a string\n"
90             }
91             elsif ($value eq 'aliases' and ($opt_ref and $opt_ref ne 'ARRAY')) {
92 0           Carp::croak "'aliases' attribute must be a string or an ARRAY ref\n";
93             }
94             elsif ($value eq 'to_stash' and ($opt_ref and $opt_ref ne 'ARRAY')) {
95 0           Carp::croak "'to_stash' attribute must be a scalar or an ARRAY ref\n";
96             }
97             elsif($value eq 'required') {
98 0 0         if ($accepted{'default'}) {
99 0           $accepted{'required'} = 0;
100             }
101             else {
102 0           Carp::croak "'required' and 'default' attributes cannot be used at the same time\n";
103             }
104             }
105             elsif($value eq 'default') {
106 0 0         if ($accepted{'required'}) {
107 0           $accepted{'default'} = 0;
108             }
109             else {
110 0           Carp::croak "'required' and 'default' attributes cannot be used at the same time\n";
111             }
112             }
113             elsif ($value eq 'error_msg' and $opt_ref) {
114 0           Carp::croak "'error_msg' attribute must be a string\n"
115             }
116             elsif ($value eq 'conflicts_with' and ($opt_ref and $opt_ref ne 'ARRAY')) {
117 0           Carp::croak "'conflicts_with' attribute must be a scalar or an ARRAY ref\n";
118             }
119 0           $self->{args}->{$arg}->{$value} = $options->{$value};
120             }
121             }
122             # got a string. Set it as the help for the argument
123             else {
124 0           $self->{args}->{$arg}->{help} = $options;
125             }
126             }
127              
128             sub _set_default_values {
129 0     0     my ($self, $options_ref, $stash_ref) = (@_);
130            
131 0           foreach my $arg ( keys %{$self->{args}} ) {
  0            
132 0 0         if (my $default = $self->{args}->{$arg}->{default}) {
133            
134 0 0         unless (defined $options_ref->{$arg}) {
135 0           $options_ref->{$arg} = $default;
136            
137             # if the argument has a to_stash value or hashref,
138             # we fill the stash.
139 0 0         if (my $stashed = $self->{args}->{$arg}->{to_stash}) {
140 0 0         push my @keys, ref $stashed ? @{$stashed} : $arg;
  0            
141 0           foreach (@keys) {
142 0           $stash_ref->{$_} = $default;
143             }
144             }
145             }
146             }
147             }
148             }
149              
150              
151             # _parse_arg should return the options' name
152             # and its "to_stash" values
153             # code here should probably be separated in different subs
154             # for better segregation and testing
155             sub _parse_arg {
156 0     0     my ($self, $token, $val, $c) = (@_);
157              
158             # short circuit
159 0 0         return ($token, undef)
160             unless defined $self->{args};
161              
162             # first we see if it's a valid arg
163 0           my $arg_ref = undef;
164 0           my $arg_real_name = $token;
165 0 0         if (defined $self->{args}->{$token}) {
166 0           $arg_ref = $self->{args}->{$token};
167             }
168             else {
169 0           ALIAS_CHECK: # try to find if user given an alias instead
170 0           foreach my $valid_arg (keys %{$self->{args}}) {
171            
172             # get aliases list
173 0           my $aliases = $self->{args}->{$valid_arg}->{aliases};
174 0 0         $aliases = [$aliases] unless ref $aliases;
175              
176 0           foreach my $alias (@{$aliases}) {
  0            
177             # get token if it's inside alias list,
178 0 0 0       if ($alias and $token eq $alias) {
179 0           $arg_ref = $self->{args}->{$valid_arg};
180 0           $arg_real_name = $valid_arg;
181 0           last ALIAS_CHECK;
182             }
183             }
184             }
185             }
186 0           return (undef, "argument '$token' not accepted by command '" . $self->{name} . "'\n")
187 0 0         unless keys %{$arg_ref}; # al newkirk: changed from defined $arg_ref
188            
189             # al newkirk (awnstudio): arg option prompt support
190 0 0         if ( defined $self->{args}->{$arg_real_name}->{prompt}) {
191 0 0         unless ( $val ) {
192 0           my $ask = $self->{args}->{$arg_real_name}->{prompt};
193 0 0         my $set = ( defined $self->{args}->{$arg_real_name}->{default}
194             ? $self->{args}->{$arg_real_name}->{default} : '' );
195            
196 0           my $opt = $arg_real_name;
197            
198 0 0         $val = $c->prompt({
199             ask => $ask,
200             set => $set,
201             opt => $opt
202             }) if $c;
203             }
204             }
205            
206             # now that we have the argument name,
207             # we need to validate it.
208 0 0         if (defined $arg_ref->{type} ) {
209            
210             # al newkirk: when defaulting to a value of one, the type
211             # if exists, must be changed to "num" to avoid attempting to validate "1"
212             # as "any" or "str" and failing.
213             # ! Note: This changes the value for the duration of the request.
214 0 0         $arg_ref->{type} = "num" if $val eq "1";
215            
216 0 0 0       if (not defined $val or not $TYPES{$arg_ref->{type}}->($val)) {
217 0           return (undef, "argument '$token' expects a (" . $arg_ref->{type} . ") value\n");
218             }
219             }
220            
221            
222            
223             # al newkirk: arg option to_stash support
224             # current to_stash values must be in arrayref format [...]
225 0 0         if ( defined $self->{args}->{$arg_real_name}->{to_stash} ) {
226 0 0         if ( ref $self->{args}->{$arg_real_name}->{to_stash} eq "ARRAY" ) {
    0          
227 0           foreach my $var ( @{ $self->{args}->{$arg_real_name}->{to_stash} } ) {
  0            
228 0           $c->stash->{$var} = $val;
229             }
230             }
231             elsif ( $self->{args}->{$arg_real_name}->{to_stash} ne "" ) {
232 0           $c->stash->{$self->{args}->{$arg_real_name}->{to_stash}} = $val;
233             }
234             else {
235 0           die
236             "Error: $token to_stash option exists but contains an invalid value";
237             }
238             }
239            
240             # return argument and stash list ref
241 0           return ($arg_real_name, undef, $val);
242             }
243              
244              
245 0     0 0   sub name { return shift->{name} }
246 0     0 1   sub help { return shift->{help} }
247              
248             sub run {
249 0     0 0   my $self = shift;
250 0           my $c = shift;
251 0           $self->{code}->($c, @_);
252             }
253              
254              
255             #TODO: a.k.a. long help - called with ./myapp help command
256             #sub description {
257             # my $self = shift;
258             # return help . argument_help # or something like that
259             #}
260              
261             42;
262             __END__