File Coverage

lib/Command/V2.pm
Criterion Covered Total %
statement 71 132 53.7
branch 14 34 41.1
condition 18 44 40.9
subroutine 15 19 78.9
pod 0 4 0.0
total 118 233 50.6


line stmt bran cond sub pod time code
1             package Command::V2;
2              
3 9     9   190 use strict;
  9         13  
  9         385  
4 9     9   32 use warnings;
  9         8  
  9         207  
5              
6 9     9   28 use UR;
  9         10  
  9         47  
7 9     9   27 use Data::Dumper;
  9         10  
  9         429  
8 9     9   33 use File::Basename;
  9         10  
  9         465  
9 9     9   3104 use Getopt::Long;
  9         37600  
  9         73  
10              
11 9     9   4549 use Command::View::DocMethods;
  9         22  
  9         134  
12 9     9   4520 use Command::Dispatch::Shell;
  9         18  
  9         71  
13              
14             our $VERSION = "0.46"; # UR $VERSION;
15              
16             our $entry_point_class;
17             our $entry_point_bin;
18              
19             UR::Object::Type->define(
20             class_name => __PACKAGE__,
21             is => ['Command', 'Command::Common'],
22             is_abstract => 1,
23             subclass_description_preprocessor => 'Command::V2::_preprocess_subclass_description',
24             attributes_have => [
25             is_param => { is => 'Boolean', is_optional => 1 },
26             is_input => { is => 'Boolean', is_optional => 1 },
27             is_output => { is => 'Boolean', is_optional => 1 },
28             shell_args_position => { is => 'Integer', is_optional => 1,
29             doc => 'when set, this property is a positional argument when run from a shell' },
30             completion_handler => { is => 'MethodName', is_optional => 1,
31             doc => 'to supply auto-completions for this parameter, call this class method' },
32             require_user_verify => { is => 'Boolean', is_optional => 1,
33             doc => 'when expanding user supplied values: 0 = never verify, 1 = always verify, undef = determine automatically', },
34             ],
35             has_optional => [
36             debug => { is => 'Boolean', doc => 'enable debug messages' },
37             is_executed => { is => 'Boolean' },
38             result => { is => 'Scalar', is_output => 1 },
39             original_command_line => { is => 'String', doc => 'null-byte separated list of command and arguments when run via execute_with_shell_params_and_exit'},
40             _total_command_count => { is => 'Integer', default => 0, is_transient => 1 },
41             _command_errors => {
42             is => 'HASH',
43             doc => 'Values can be an array ref is multiple errors occur during a command\'s execution',
44             default => {},
45             is_transient => 1,
46             },
47             ],
48             );
49              
50 2     2   5 sub _is_hidden_in_docs { return; }
51              
52             sub _preprocess_subclass_description {
53 27     27   50 my ($class, $desc) = @_;
54 27         46 while (my ($prop_name, $prop_desc) = each(%{ $desc->{has} })) {
  92         226  
55 65 100 66     354 unless (
      66        
      66        
      66        
56             $prop_desc->{'is_param'}
57             or $prop_desc->{'is_input'}
58             or $prop_desc->{'is_transient'}
59             or $prop_desc->{'is_calculated'},
60             or $prop_desc->{'is_output'}
61             ) {
62 37         48 $prop_desc->{'is_param'} = 1;
63             }
64             }
65 27         63 return $desc;
66             }
67              
68             sub _init_subclass {
69             # Each Command subclass has an automatic wrapper around execute().
70             # This ensures it can be called as a class or instance method,
71             # and that proper handling occurs around it.
72 27     27   51 my $subclass_name = $_[0];
73 9     9   1769 no strict;
  9         15  
  9         172  
74 9     9   33 no warnings;
  9         15  
  9         6469  
75 27 50       87 if ($subclass_name->can('execute')) {
76             # NOTE: manipulating %{ $subclass_name . '::' } directly causes ptkdb to segfault perl
77 27         258 my $new_symbol = "${subclass_name}::_execute_body";
78 27         57 my $old_symbol = "${subclass_name}::execute";
79 27         169 *$new_symbol = *$old_symbol;
80 27         71 undef *$old_symbol;
81             }
82             else {
83             #print "no execute in $subclass_name\n";
84             }
85              
86 27 50       82 if($subclass_name->can('shortcut')) {
87 27         293 my $new_symbol = "${subclass_name}::_shortcut_body";
88 27         52 my $old_symbol = "${subclass_name}::shortcut";
89 27         104 *$new_symbol = *$old_symbol;
90 27         60 undef *$old_symbol;
91             }
92              
93 27         96 my @p = $subclass_name->__meta__->properties();
94 27         42 my @e;
95 27         50 for my $p (@p) {
96 272 100       402 next if $p->property_name eq 'id';
97 245 100       382 next if $p->class_name eq __PACKAGE__;
98 83 50       137 next unless $p->class_name->isa('Command');
99 83 50 100     369 unless ($p->is_input or $p->is_output or $p->is_param or $p->is_transient or $p->is_calculated) {
      100        
      66        
      33        
100 0         0 my $modname = $subclass_name;
101 0         0 $modname =~ s|::|/|g;
102 0         0 $modname .= '.pm';
103 0         0 push @e, $modname . " property " . $p->property_name . " must be input, output, param, transient, or calculated!";
104             }
105             }
106 27 50       74 if (@e) {
107 0         0 for (@e) {
108 0         0 $subclass_name->error_message($_);
109             }
110 0         0 die "command classes like $subclass_name have properties without is_input/output/param/transient/calculated set!";
111             }
112              
113 27         104 return 1;
114             }
115              
116             sub __errors__ {
117 11     11   20 my ($self,@property_names) = @_;
118 11         56 my @errors1 =($self->SUPER::__errors__);
119              
120 11 100       34 if ($self->is_executed) {
121 2         8 return @errors1;
122             }
123              
124             # for Commands which have not yet been executed,
125             # only consider errors on inputs or params
126              
127 9         27 my $meta = $self->__meta__;
128 9         12 my @errors2;
129             ERROR:
130 9         23 for my $e (@errors1) {
131 1         31 for my $p ($e->properties) {
132 1         10 my $pm = $meta->property($p);
133 1 50 33     10 if ($pm->is_input or $pm->is_param) {
134 1         2 push @errors2, $e;
135 1         4 next ERROR;
136             }
137             }
138             }
139              
140 9         34 return @errors2;
141             }
142              
143             # For compatability with Command::V1 callers
144             sub is_sub_command_delegator {
145 4     4 0 18 return;
146             }
147              
148             sub _wrapper_has {
149 0     0     my ($class, $new_class_base) = @_;
150              
151 0   0       $new_class_base ||= __PACKAGE__;
152              
153 0           my $command_meta = $class->__meta__;
154 0           my @properties = $command_meta->properties();
155            
156 0           my %has;
157 0           for my $property (@properties) {
158 0           my %desc;
159 0 0 0       next unless $property->can("is_param") and $property->can("is_input") and $property->can("is_output");
      0        
160              
161 0           my $name = $property->property_name;
162              
163 0 0         next if $new_class_base->can($name);
164              
165 0 0         if ($property->is_param) {
    0          
166 0           $desc{is_param} = 1;
167             }
168             elsif ($property->is_input) {
169 0           $desc{is_input} = 1;
170             }
171             #elsif ($property->can("is_metric") and $property->is_metric) {
172             # $desc{is_metric} = 1;
173             #}
174             #elsif ($property->can("is_output") and $property->is_output) {
175             # $desc{is_output} = 1;
176             #}
177             else {
178 0           next;
179             }
180              
181 0           $has{$name} = \%desc;
182 0           $desc{is} = $property->data_type;
183 0           $desc{doc} = $property->doc;
184 0           $desc{is_many} = $property->is_many;
185 0           $desc{is_optional} = $property->is_optional;
186             }
187              
188 0           return %has;
189             }
190              
191             sub display_command_summary_report {
192 0     0 0   my $self = shift;
193 0           my $total_count = $self->_total_command_count;
194 0           my %command_errors = %{$self->_command_errors};
  0            
195              
196 0 0         if (keys %command_errors) {
197 0           $self->status_message("\n\nErrors Summary:");
198 0           for my $key (keys %command_errors) {
199 0           my $errors = $command_errors{$key};
200 0 0 0       $errors = [$errors] unless (ref($errors) and ref($errors) eq 'ARRAY');
201 0           my @errors = @{$errors};
  0            
202 0           print "$key: \n";
203 0           for my $error (@errors) {
204 0           $error = $self->truncate_error_message($error);
205 0           print "\t- $error\n";
206             }
207             }
208             }
209              
210 0 0         if ($total_count > 1) {
211 0           my $error_count = scalar(keys %command_errors);
212 0           $self->status_message("\n\nCommand Summary:");
213 0           $self->status_message(" Successful: " . ($total_count - $error_count));
214 0           $self->status_message(" Errors: " . $error_count);
215 0           $self->status_message(" Total: " . $total_count);
216             }
217             }
218              
219             sub append_error {
220 0     0 0   my $self = shift;
221 0   0       my $key = shift || die;
222 0   0       my $error = shift || die;
223              
224 0           my $command_errors = $self->_command_errors;
225 0           push @{$command_errors->{$key}}, $error;
  0            
226 0           $self->_command_errors($command_errors);
227              
228 0           return 1;
229             }
230              
231             sub truncate_error_message {
232 0     0 0   my $self = shift;
233 0   0       my $error = shift || die;
234              
235             # truncate errors so they are actually a summary
236 0           ($error) = split("\n", $error);
237              
238             # meant to truncate a callstack as this is meant for user/high-level
239 0           $error =~ s/\ at\ \/.*//;
240              
241 0           return $error;
242             }
243              
244              
245             1;
246              
247             __END__