File Coverage

lib/Command/Dispatch/Shell.pm
Criterion Covered Total %
statement 324 699 46.3
branch 117 370 31.6
condition 40 144 27.7
subroutine 26 40 65.0
pod 0 6 0.0
total 507 1259 40.2


line stmt bran cond sub pod time code
1             package Command::V2; # additional methods to dispatch from a command-line
2 9     9   195 use strict;
  9         10  
  9         230  
3 9     9   32 use warnings;
  9         11  
  9         224  
4              
5 9     9   33 use IO::File;
  9         10  
  9         1316  
6 9     9   40 use List::MoreUtils;
  9         12  
  9         121  
7              
8             # instead of tacking these methods onto general Command::V2 objects
9             # they could be put on the Command::Shell class, which is a wrapper/adaptor Command for translating from
10             # command-line shell to purely functional commands.
11              
12             # old entry point
13             # new cmds will call Command::Shell->run("MyClass",@ARGV)
14             # which goes straight into _cmdline_run for now...
15             sub execute_with_shell_params_and_exit {
16 0     0 0 0 my $class = shift;
17 0 0       0 if (@_) {
18 0         0 die "No params expected for execute_with_shell_params_and_exit()!";
19             }
20 0         0 my @argv = @ARGV;
21 0         0 @ARGV = ();
22 0         0 my $exit_code = $class->_cmdline_run(@argv);
23 0         0 exit $exit_code;
24             }
25              
26             sub _cmdline_run {
27             # This automatically parses command-line options and "does the right thing":
28             # TODO: abstract out all dispatchers for commands into a given API
29 0     0   0 my $class = shift;
30 0         0 my @argv = @_;
31              
32 0   0     0 $Command::entry_point_class ||= $class;
33 0   0     0 $Command::entry_point_bin ||= File::Basename::basename($0);
34              
35 0 0       0 if ($ENV{COMP_CWORD}) {
36 0         0 require Getopt::Complete;
37 0         0 my @spec = $class->resolve_option_completion_spec();
38 0         0 my $options = Getopt::Complete::Options->new(@spec);
39 0         0 $options->handle_shell_completion;
40 0         0 die "error: failed to exit after handling shell completion!";
41             }
42              
43 0         0 my $exit_code;
44 0         0 eval {
45 0         0 $exit_code = $class->_execute_with_shell_params_and_return_exit_code(@argv);
46 0 0       0 UR::Context->commit or die "Failed to commit!: " . UR::Context->error_message();
47             };
48 0 0       0 if ($@) {
49 0         0 $class->error_message($@);
50 0 0       0 UR::Context->rollback or die "Failed to rollback changes after failed commit!!!\n";
51 0 0       0 $exit_code = 255 unless ($exit_code);
52             }
53 0         0 return $exit_code;
54             }
55              
56             sub _execute_with_shell_params_and_return_exit_code {
57 1     1   1047 my $class = shift;
58 1         3 my @argv = @_;
59              
60 1         3 my $original_cmdline = join("\0",$0,@argv);
61              
62             # make --foo=bar equivalent to --foo bar
63 1 0       3 @argv = map { ($_ =~ /^(--\w+?)\=(.*)/) ? ($1,$2) : ($_) } @argv;
  0         0  
64 1         8 my ($delegate_class, $params, $errors) = $class->resolve_class_and_params_for_argv(@argv);
65              
66 1         1 my $exit_code;
67 1 50 33     5 if ($errors and @$errors) {
68 0         0 $delegate_class->dump_status_messages(1);
69 0         0 $delegate_class->dump_warning_messages(1);
70 0         0 $delegate_class->dump_error_messages(1);
71 0         0 for my $error (@$errors) {
72 0         0 $delegate_class->error_message(join(' ', $error->property_names) . ": " . $error->desc);
73             }
74 0         0 $exit_code = 1;
75             }
76             else {
77 1         7 my $rv = $class->_execute_delegate_class_with_params($delegate_class,$params,$original_cmdline);
78 1         9 $exit_code = $delegate_class->exit_code_for_return_value($rv);
79             }
80              
81 1         3 return $exit_code;
82             }
83              
84              
85             sub _execute_delegate_class_with_params {
86             # this is called by both the shell dispatcher and http dispatcher for now
87 3     3   1433 my ($class, $delegate_class, $params, $original_cmdline) = @_;
88              
89 3 50       10 unless ($delegate_class) {
90 0         0 $class->dump_status_messages(1);
91 0         0 $class->dump_warning_messages(1);
92 0         0 $class->dump_error_messages(1);
93 0         0 $class->dump_usage_messages(1);
94 0         0 $class->dump_debug_messages(0);
95 0         0 $class->usage_message($class->help_usage_complete_text);
96 0         0 return;
97             }
98              
99 3         20 $delegate_class->dump_status_messages(1);
100 3         15 $delegate_class->dump_warning_messages(1);
101 3         14 $delegate_class->dump_error_messages(1);
102 3         13 $delegate_class->dump_usage_messages(1);
103 3         15 $delegate_class->dump_debug_messages(0);
104              
105             # FIXME There should be a better check for params that are there because they came from the
106             # command line, and params that exist for infrastructural purposes. 'original_command_line'
107             # won't ever be given on the command line and shouldn't count toward the next test.
108             # maybe check the is_input properties...
109 3 100       10 if ( !defined($params) ) {
110 1         13 my $command_name = $delegate_class->command_name;
111 1         7 $delegate_class->status_message($delegate_class->help_usage_complete_text);
112 1         7 $delegate_class->error_message("Please specify valid params for '$command_name'.");
113 1         3 return;
114             }
115              
116 2 50       5 if ( $params->{help} ) {
117 0         0 $delegate_class->usage_message($delegate_class->help_usage_complete_text);
118 0         0 return 1;
119             }
120              
121 2 50       5 $params->{'original_command_line'} = $original_cmdline if (defined $original_cmdline);
122 2         10 my $command_object = $delegate_class->create(%$params);
123              
124 2 50       7 unless ($command_object) {
125             # The delegate class should have emitted an error message.
126             # This is just in case the developer is sloppy, and the user will think the task did not fail.
127 0         0 print STDERR "Exiting.\n";
128 0         0 return;
129             }
130              
131 2         7 $command_object->dump_status_messages(1);
132 2         8 $command_object->dump_warning_messages(1);
133 2         4 $command_object->dump_error_messages(1);
134 2         6 $command_object->dump_debug_messages($command_object->debug);
135 2 100       4 if ($command_object->debug) {
136 1         62 UR::ModuleBase->dump_debug_messages($command_object->debug);
137             }
138              
139 2         45 my $rv = $command_object->execute($params);
140              
141 2 50       6 if ($command_object->__errors__) {
142 0         0 $command_object->delete;
143             }
144              
145 2         4 return $rv;
146             }
147              
148             sub resolve_class_and_params_for_argv {
149             # This is used by execute_with_shell_params_and_exit, but might be used within an application.
150 20     20 0 19692 my $self = shift;
151 20         47 my @argv = @_;
152              
153 20         64 my ($params_hash,@spec) = $self->_shell_args_getopt_specification;
154 20 50       31 unless (grep { /^help\W/ } @spec) {
  100         124  
155 20         28 push @spec, "help!";
156             }
157              
158 20         18 my @error_tags;
159              
160             # Thes nasty GetOptions modules insist on working on
161             # the real @ARGV, while we like a little more flexibility.
162             # Not a problem in Perl. :) (which is probably why it was never fixed)
163 20         25 local @ARGV;
164 20         34 @ARGV = @argv;
165              
166 20         17 do {
167             # GetOptions also likes to emit warnings instead of return a list of errors :(
168 20         17 my @errors;
169             my $rv;
170             {
171 20     0   8 local $SIG{__WARN__} = sub { push @errors, @_ };
  20         114  
  0         0  
172              
173             ## Change the pattern to be '--', '-' followed by a non-digit, or '+'.
174             ## This s the effect of treating a negative number as a value of an option.
175             ## This means that we won't be allowed to have an option named, say, -1.
176             ## But since command modules' properties have to be allowable function names,
177             ## and "1" is not a valid function name, it's not really a problem
178             #Getopt::Long::Configure('prefix_pattern=--|-(?!\D)|\+');
179 20         65 $rv = GetOptions($params_hash,@spec);
180             }
181 20 50       7972 unless ($rv) {
182 0         0 for my $error (@errors) {
183 0         0 $self->error_message($error);
184             }
185 0         0 return($self, undef);
186             }
187             };
188              
189             # Q: Is there a standard getopt spec for capturing non-option paramters?
190             # Perhaps that's not getting "options" :)
191             # A: Yes. Use '<>'. But we need to process this anyway, so it won't help us.
192              
193 20 50       57 if (my @names = $self->_bare_shell_argument_names) {
194 0         0 for (my $n=0; $n < @ARGV; $n++) {
195 0         0 my $name = $names[$n];
196 0 0       0 unless ($name) {
197 0         0 $self->error_message("Unexpected bare arguments: @ARGV[$n..$#ARGV]!");
198 0         0 return($self, undef);
199             }
200 0         0 my $value = $ARGV[$n];
201 0         0 my $meta = $self->__meta__->property_meta_for_name($name);
202 0 0 0     0 if ($meta->is_many and $n == $#names) {
203             # slurp the rest
204 0         0 $params_hash->{$name} = [@ARGV[$n..$#ARGV]];
205 0         0 last;
206             }
207             else {
208 0         0 $params_hash->{$name} = $value;
209             }
210             }
211             }
212              
213 20 50 33     53 if (@ARGV and not $self->_bare_shell_argument_names) {
214             ## argv but no names
215 0         0 $self->error_message("Unexpected bare arguments: @ARGV!");
216 0         0 return($self, undef);
217             }
218              
219 20         57 for my $key (keys %$params_hash) {
220             # handle any has-many comma-sep values
221 29         33 my $value = $params_hash->{$key};
222 29 50 100     126 if (ref($value)) {
    100          
223 0         0 my @new_value;
224 0         0 for my $v (@$value) {
225 0         0 my @parts = split(/,\s*/,$v);
226 0         0 push @new_value, @parts;
227             }
228 0         0 @$value = @new_value;
229              
230             } elsif ($value eq q('') or $value eq q("")) {
231             # Handle the special values '' and "" to mean undef/NULL
232 4         4 $params_hash->{$key} = '';
233             }
234              
235             # turn dashes into underscores
236 29         25 my $new_key = $key;
237              
238 29 100       68 next unless ($new_key =~ tr/-/_/);
239 28 0 33     47 if (exists $params_hash->{$new_key} && exists $params_hash->{$key}) {
240             # this corrects a problem where is_many properties badly interact
241             # with bare args leaving two entries in the hash like:
242             # a-bare-opt => [], a_bare_opt => ['with','vals']
243 0         0 delete $params_hash->{$key};
244 0         0 next;
245             }
246 28         51 $params_hash->{$new_key} = delete $params_hash->{$key};
247             }
248              
249             # futher work is looking for errors, and may display them
250             # if help is set, return now
251             # we might have returned sooner, but having full info available
252             # allows for dynamic help
253 20 50       40 if ($params_hash->{help}) {
254 0         0 return ($self, $params_hash);
255             }
256              
257             ##
258 20         19 my $params = $params_hash;
259 20         61 my $class = $self->class;
260              
261 20 100       48 if (my @errors = $self->_errors_from_missing_parameters($params)) {
262 17         84 return ($class, $params, \@errors);
263             }
264              
265 3 50       7 unless (@_) {
266 0         0 return ($class, $params);
267             }
268              
269             # should this be moved up into the methods which are only called
270             # directly from the shell, or is it okay everywhere in this module to
271             # presume we're a direct cmdline call? -ssmith
272             local $ENV{UR_COMMAND_DUMP_STATUS_MESSAGES} = (!exists($ENV{UR_COMMAND_DUMP_STATUS_MESSAGES})
273 3   33     32 or $ENV{UR_COMMAND_DUMP_STATUS_MESSAGES});
274              
275 3         21 my @params_to_resolve = $self->_params_to_resolve($params);
276 3         5 for my $p (@params_to_resolve) {
277 0         0 my $param_arg_str = join(',', @{$p->{value}});
  0         0  
278 0         0 my $pmeta = $self->__meta__->property($p->{name});
279              
280 0         0 my @params;
281 0         0 eval {
282 0         0 @params = $self->resolve_param_value_from_cmdline_text($p);
283             };
284              
285 0 0       0 if ($@) {
286             push @error_tags, UR::Object::Tag->create(
287             type => 'invalid',
288 0         0 properties => [$p->{name}],
289             desc => "Errors while resolving from $param_arg_str: $@",
290             );
291             }
292 0 0 0     0 if (@params and $params[0]) {
293 0 0       0 if ($pmeta->{'is_many'}) {
294 0         0 $params->{$p->{name}} = \@params;
295             }
296             else {
297 0         0 $params->{$p->{name}} = $params[0];
298             }
299             }
300             else {
301             push @error_tags, UR::Object::Tag->create(
302             type => 'invalid',
303 0         0 properties => [$p->{name}],
304             desc => "Problem resolving from $param_arg_str.",
305             );
306             }
307             }
308              
309 3 50       9 if (@error_tags) {
310 0         0 return ($class, undef, \@error_tags);
311             }
312             else {
313 3         29 return ($class, $params);
314             }
315             }
316              
317             sub resolve_option_completion_spec {
318 6     6 0 11 my $class = shift;
319 6         35 my @completion_spec = $class->_shell_args_getopt_complete_specification;
320 9     9   13157 no warnings;
  9         16  
  9         8226  
321 6 50       13 unless (grep { /^help\W/ } @completion_spec) {
  72         74  
322 6         11 push @completion_spec, "help!" => undef;
323             }
324             return \@completion_spec
325 6         14 }
326              
327             sub _errors_from_missing_parameters {
328 20     20   17 my ($self, $params) = @_;
329              
330 20         63 my $class_meta = $self->__meta__;
331              
332 20         448 my @all_property_metas = $class_meta->properties();
333 20         40 my @specified_property_metas = grep { exists $params->{$_->property_name} } @all_property_metas;
  260         360  
334              
335 20         26 my %specified_property_metas = map { $_->property_name => $_ } @specified_property_metas;
  29         39  
336 20         24 my %set_indirectly;
337 20         26 my @todo = @specified_property_metas;
338 20         46 while (my $property_meta = shift @todo) {
339 29 50       53 if (my $via = $property_meta->via) {
    50          
340 0 0       0 if (not $property_meta->is_mutable) {
341 0   0     0 my $list = $set_indirectly{$via} ||= [];
342 0         0 push @$list, $property_meta;
343             }
344 0 0       0 unless ($specified_property_metas{$via}) {
345 0         0 my $via_meta = $specified_property_metas{$via} = $class_meta->property($via);
346 0         0 push @specified_property_metas, $via_meta;
347 0         0 push @todo, $via_meta;
348             }
349             }
350             elsif (my $id_by = $property_meta) {
351 29   50     128 my $list = $set_indirectly{$id_by} ||= [];
352 29         40 push @$list, $property_meta;
353 29 50       55 unless ($specified_property_metas{$id_by}) {
354 29         71 my $id_by_meta = $specified_property_metas{$id_by} = $class_meta->property($id_by);
355 29         29 push @specified_property_metas, $id_by_meta;
356 29         66 push @todo, $id_by_meta;
357             }
358             }
359             }
360              
361             # TODO: this should use @all_property_metas, and filter down to is_param and is_input
362             # This old code just ignores things inherited from a base class.
363             # We will need to be careful fixing this because it could add checks to tools which
364             # work currently and lead to unexpected failures.
365 20         19 my @property_names;
366 20 50       42 if (my $has = $class_meta->{has}) {
367 20         153 @property_names = List::MoreUtils::uniq(keys %$has);
368             }
369 20         47 my @property_metas = map { $class_meta->property_meta_for_name($_); } @property_names;
  120         179  
370              
371 20         23 my @error_tags;
372 20         30 for my $property_meta (@property_metas) {
373 120         191 my $pn = $property_meta->property_name;
374              
375 120 100       168 next if $property_meta->is_optional;
376 60 50       86 next if $property_meta->implied_by;
377 60 50       111 next if defined $property_meta->default_value;
378 60 100       91 next if defined $params->{$pn};
379 50 50       70 next if $set_indirectly{$pn};
380              
381 50 50       81 if (my $via = $property_meta->via) {
382 0 0 0     0 if ($params->{$via} or $set_indirectly{$via}) {
383 0         0 next;
384             }
385             }
386              
387 50         53 my $arg = $pn;
388 50         120 $arg =~ s/_/-/g;
389 50         63 $arg = "--$arg";
390              
391 50 100 66     183 if ($property_meta->is_output and not $property_meta->is_input and not $property_meta->is_param) {
      66        
392 20 50 33     57 if ($property_meta->_data_type_as_class_name->__meta__->data_source
    50          
393             and not $property_meta->_data_type_as_class_name->isa("UR::Value")
394             ) {
395             # outputs with a data source do not need a specification
396             # on the cmdline to "store" them after execution
397 0         0 next;
398             }
399             elsif ($property_meta->is_calculated) {
400             # outputs that are calculated don't need to be specified on
401             # the command line
402 20         43 next;
403             }
404             else {
405 0         0 push @error_tags, UR::Object::Tag->create(
406             type => 'invalid',
407             properties => [$pn],
408             desc => "Output requires specified destination: " . $arg . "."
409             );
410             }
411             }
412             else {
413 30         49 $DB::single = 1;
414 30         136 push @error_tags, UR::Object::Tag->create(
415             type => 'invalid',
416             properties => [$pn],
417             desc => "Missing required parameter: " . $arg . "."
418             );
419             }
420             }
421              
422 20         117 return @error_tags;
423             }
424              
425             sub _params_to_resolve {
426 3     3   5 my ($self, $params) = @_;
427 3         4 my @params_to_resolve;
428 3 50       9 if ($params) {
429 3         11 my $cmeta = $self->__meta__;
430 3         5 my @params_will_require_verification;
431             my @params_may_require_verification;
432              
433 3         7 for my $param_name (keys %$params) {
434 6         20 my $pmeta = $cmeta->property($param_name);
435 6 50       11 unless ($pmeta) {
436             # This message was a die after a next, so I guess it isn't supposed to be fatal?
437 0         0 $self->warning_message("No metadata for property '$param_name'");
438 0         0 next;
439             }
440              
441 6         16 my $param_type = $pmeta->data_type;
442 6 50       17 next unless($self->_can_resolve_type($param_type));
443              
444 0         0 my $param_arg = $params->{$param_name};
445 0 0       0 if (my $arg_type = ref($param_arg)) {
446 0 0       0 next if $arg_type eq $param_type; # param is already the right type
447 0 0       0 if ($arg_type ne 'ARRAY') {
448 0         0 $self->error_message("no handler for property '$param_name' with argument type " . ref($param_arg));
449 0         0 next;
450             }
451             } else {
452 0         0 $param_arg = [$param_arg];
453             }
454 0 0       0 next unless (@$param_arg);
455              
456 0         0 my $resolve_info = {
457             name => $param_name,
458             class => $param_type,
459             value => $param_arg,
460             };
461 0         0 push(@params_to_resolve, $resolve_info);
462              
463 0         0 my $require_user_verify = $pmeta->{'require_user_verify'};
464 0 0       0 if ( defined($require_user_verify) ) {
465 0 0       0 push @params_will_require_verification, "'$param_name'" if ($require_user_verify);
466             } else {
467 0         0 push @params_may_require_verification, "'$param_name'";
468             }
469             }
470              
471 3         8 my @adverbs = ('will', 'may');
472 3         8 my @params_adverb_require_verification = (
473             \@params_will_require_verification,
474             \@params_may_require_verification,
475             );
476 3         15 for (my $i = 0; $i < @adverbs; $i++) {
477 6         8 my $adverb = $adverbs[$i];
478 6         6 my @param_adverb_require_verification = @{$params_adverb_require_verification[$i]};
  6         10  
479 6 50       20 next unless (@param_adverb_require_verification);
480              
481 0 0       0 if (@param_adverb_require_verification > 1) {
482 0         0 $param_adverb_require_verification[-1] = 'and ' . $param_adverb_require_verification[-1];
483             }
484 0         0 my $param_str = join(', ', @param_adverb_require_verification);
485 0         0 $self->status_message($param_str . " $adverb require verification...");
486             }
487             }
488 3         9 return @params_to_resolve;
489             }
490              
491             sub _can_resolve_type {
492 6     6   11 my ($self, $type) = @_;
493              
494 6 50       9 return 0 unless($type);
495              
496 6         9 my $non_classes = 0;
497 6 50       12 if (ref($type) ne 'ARRAY') {
498 6         12 $non_classes = $type !~ m/::/;
499             } else {
500 0         0 $non_classes = scalar grep { ! m/::/ } @$type;
  0         0  
501             }
502 6         20 return $non_classes == 0;
503             }
504              
505             sub _shell_args_property_meta {
506 58     58   59 my $self = shift;
507 58         149 my $class_meta = $self->__meta__;
508              
509             # Find which property metas match the rules. We have to do it this way
510             # because just calling 'get_all_property_metas()' will product multiple matches
511             # if a property is overridden in a child class
512 58         197 my ($rule, %extra) = UR::Object::Property->define_boolexpr(@_);
513 58         60 my %seen;
514 58         54 my (@positional,@required_input,@required_param,@optional_input,@optional_param, @output);
515              
516 58         1479 my @property_meta = $class_meta->properties();
517             PROP:
518 58         111 foreach my $property_meta (@property_meta) {
519 749         2832 my $property_name = $property_meta->property_name;
520              
521 749 50       1364 next if $seen{$property_name}++;
522 749 100       1496 next unless $rule->evaluate($property_meta);
523 725 100 100     1337 next unless $property_meta->can("is_param") and ($property_meta->is_param or $property_meta->is_input or $property_meta->is_output);
      66        
524 377 100       709 if (%extra) {
525 9     9   52 no warnings;
  9         12  
  9         30651  
526 6         9 for my $key (keys %extra) {
527 6 100       18 if ($property_meta->$key ne $extra{$key}) {
528 4         8 next PROP;
529             }
530             }
531             }
532              
533 373 50       557 next if $property_name eq 'id';
534 373 100       553 next if $property_name eq 'result';
535 320 50       390 next if $property_name eq 'is_executed';
536 320 50       383 next if $property_name eq 'original_command_line';
537 320 50       469 next if $property_name =~ /^_/;
538              
539 320 50       620 next if $property_meta->implied_by;
540 320 100       479 next if $property_meta->is_calculated;
541             # Kept commented out from UR's Command.pm, I believe is_output is a workflow property
542             # and not something we need to exclude (counter to the old comment below).
543             #next if $property_meta->{is_output}; # TODO: This was breaking the G::M::T::Annotate::TranscriptVariants annotator. This should probably still be here but temporarily roll back
544 280 50       445 next if $property_meta->is_transient;
545 280 100       423 next if $property_meta->is_constant;
546 279 50 66     443 if (($property_meta->is_delegated) || (defined($property_meta->data_type) and $property_meta->data_type =~ /::/)) {
      33        
547 0 0       0 next unless($self->can('resolve_param_value_from_cmdline_text'));
548             }
549             else {
550 279 50       445 next unless($property_meta->is_mutable);
551             }
552              
553 279 100       653 if ($property_meta->{shell_args_position}) {
    100          
554 16         33 push @positional, $property_meta;
555             }
556             elsif ($property_meta->is_optional) {
557 168 100 66     534 if ($property_meta->is_input or $property_meta->is_output) {
    50          
558 15         27 push @optional_input, $property_meta;
559             }
560             elsif ($property_meta->is_param) {
561 153         245 push @optional_param, $property_meta;
562             }
563             }
564             else {
565 95 100 66     303 if ($property_meta->is_input or $property_meta->is_output) {
    50          
566 8         23 push @required_input, $property_meta;
567             }
568             elsif ($property_meta->is_param) {
569 87         139 push @required_param, $property_meta;
570             }
571             }
572             }
573              
574 58         45 my @result;
575             @result = (
576 46         120 (sort { $a->position_in_module_header cmp $b->position_in_module_header } @required_param),
577 145         235 (sort { $a->position_in_module_header cmp $b->position_in_module_header } @optional_param),
578 0         0 (sort { $a->position_in_module_header cmp $b->position_in_module_header } @required_input),
579 7         18 (sort { $a->position_in_module_header cmp $b->position_in_module_header } @optional_input),
580 58         188 (sort { $a->shell_args_position <=> $b->shell_args_position } @positional),
  6         23  
581             );
582              
583 58         315 return @result;
584             }
585              
586              
587             sub _shell_arg_name_from_property_meta {
588 137     137   114 my ($self, $property_meta,$singularize) = @_;
589 137 50       288 my $property_name = ($singularize ? $property_meta->singular_name : $property_meta->property_name);
590 137         114 my $param_name = $property_name;
591 137         220 $param_name =~ s/_/-/g;
592 137         174 return $param_name;
593             }
594              
595             sub _shell_arg_getopt_qualifier_from_property_meta {
596 136     136   103 my ($self, $property_meta) = @_;
597              
598 136 100       206 my $many = ($property_meta->is_many ? '@' : '');
599 136 100 100     195 if (defined($property_meta->data_type) and $property_meta->data_type =~ /Boolean/) {
600 9         28 return '!' . $many;
601             }
602             #elsif ($property_meta->is_optional) {
603             # return ':s' . $many;
604             #}
605             else {
606 127         289 return '=s' . $many;
607             }
608             }
609              
610             sub _shell_arg_usage_string_from_property_meta {
611 0     0   0 my ($self, $property_meta) = @_;
612 0         0 my $string = $self->_shell_arg_name_from_property_meta($property_meta);
613 0 0       0 if ($property_meta->{shell_args_position}) {
614 0         0 $string = uc($string);
615             }
616              
617 0 0       0 if ($property_meta->{shell_args_position}) {
618 0 0       0 if ($property_meta->is_optional) {
619 0         0 $string = "[$string]";
620             }
621             }
622             else {
623 0         0 $string = "--$string";
624 0 0 0     0 if (defined($property_meta->data_type) and $property_meta->data_type =~ /Boolean/) {
625 0         0 $string = "[$string]";
626             }
627             else {
628 0 0       0 if ($property_meta->is_many) {
629 0         0 $string .= "=?[,?]";
630             }
631             else {
632 0         0 $string .= '=?';
633             }
634 0 0       0 if ($property_meta->is_optional) {
635 0         0 $string = "[$string]";
636             }
637             }
638             }
639 0         0 return $string;
640             }
641              
642             sub _shell_arg_getopt_specification_from_property_meta {
643 100     100   80 my ($self,$property_meta) = @_;
644 100         138 my $arg_name = $self->_shell_arg_name_from_property_meta($property_meta);
645             return (
646 100         140 $arg_name . $self->_shell_arg_getopt_qualifier_from_property_meta($property_meta),
647             #this prevents defaults from being used for is_many properties
648             #($property_meta->is_many ? ($arg_name => []) : ())
649             );
650             }
651              
652              
653             sub _shell_arg_getopt_complete_specification_from_property_meta {
654 36     36   32 my ($self,$property_meta) = @_;
655 36         60 my $arg_name = $self->_shell_arg_name_from_property_meta($property_meta);
656 36         56 my $completions = $property_meta->valid_values;
657 36 100       43 if ($completions) {
658 3 50       8 if (ref($completions) eq 'ARRAY') {
659 3         13 $completions = [ @$completions ];
660             }
661             }
662             else {
663 33         44 my $type = $property_meta->data_type;
664 33         60 my @complete_as_files = (
665             'File','FilePath','Filesystem','FileSystem','FilesystemPath','FileSystemPath',
666             'Text','String',
667             );
668 33         38 my @complete_as_directories = (
669             'Directory','DirectoryPath','Dir','DirPath',
670             );
671 33 100       65 if (!defined($type)) {
672 1         2 $completions = 'files';
673             }
674             else {
675 32         29 for my $pattern (@complete_as_files) {
676 238 100 66     580 if (!$type || $type eq $pattern) {
677 19         14 $completions = 'files';
678 19         16 last;
679             }
680             }
681 32         26 for my $pattern (@complete_as_directories) {
682 128 50 33     319 if ( $type && $type eq $pattern) {
683 0         0 $completions = 'directories';
684 0         0 last;
685             }
686             }
687             }
688             }
689             return (
690 36         72 $arg_name . $self->_shell_arg_getopt_qualifier_from_property_meta($property_meta),
691             $completions,
692             # ($property_meta->is_many ? ($arg_name => []) : ())
693             );
694             }
695              
696             sub _shell_args_getopt_specification {
697 20     20   23 my $self = shift;
698 20         20 my @getopt;
699             my @params;
700 20         52 for my $meta ($self->_shell_args_property_meta) {
701 100         149 my ($spec, @params_addition) = $self->_shell_arg_getopt_specification_from_property_meta($meta);
702 100         104 push @getopt,$spec;
703 100         95 push @params, @params_addition;
704             }
705 20         51 @getopt = sort @getopt;
706 20         63 return { @params}, @getopt;
707             }
708              
709             sub _shell_args_getopt_complete_specification {
710 6     6   11 my $self = shift;
711 6         9 my @getopt;
712 6         41 for my $meta ($self->_shell_args_property_meta) {
713 36         102 my ($spec, $completions) = $self->_shell_arg_getopt_complete_specification_from_property_meta($meta);
714 36         55 push @getopt, $spec, $completions;
715             }
716 6         28 return @getopt;
717             }
718              
719              
720             sub _bare_shell_argument_names {
721 20     20   24 my $self = shift;
722 20         59 my $meta = $self->__meta__;
723             my @ordered_names =
724 0         0 map { $_->property_name }
725 0         0 sort { $a->{shell_args_position} <=> $b->{shell_args_position} }
726 20         41 grep { $_->{shell_args_position} }
  100         108  
727             $self->_shell_args_property_meta();
728 20         52 return @ordered_names;
729             }
730              
731             #
732             # Logic to turn command-line text into objects for parameter/input values
733             #
734              
735             our %ALTERNATE_FROM_CLASS = ();
736              
737             # This will prevent infinite loops during recursion.
738             our %SEEN_FROM_CLASS = ();
739             our $MESSAGE;
740              
741             sub resolve_param_value_from_cmdline_text {
742 3     3 0 5 my ($self, $param_info) = @_;
743 3         10 my $param_name = $param_info->{name};
744 3         5 my $param_class = $param_info->{class};
745 3         3 my @param_args = @{$param_info->{value}};
  3         6  
746 3         8 my $param_str = join(',', @param_args);
747              
748 3 50       8 if (ref($param_class) eq 'ARRAY') {
749 0         0 my @param_class = @$param_class;
750 0 0       0 if (@param_class > 1) {
751 0         0 die 'Multiple data types on command arguments are not supported.';
752             } else {
753 0         0 $param_class = $param_class[0];
754             }
755             }
756              
757 3         6 my $param_resolve_message = "Resolving parameter '$param_name' from command argument '$param_str'...";
758 3         10 my $pmeta = $self->__meta__->property($param_name);
759 3         5 my $require_user_verify = $pmeta->{'require_user_verify'};
760              
761 3         6 my @results;
762 3         4 my $bx = eval { UR::BoolExpr->resolve_for_string($param_class, $param_str) };
  3         10  
763 3         5 my $bx_error = $@;
764 3 100       6 if ($bx) {
765 2         14 @results = $param_class->get($bx);
766 2 50 33     9 if (@results > 1 && !defined($require_user_verify)) {
767 0         0 $require_user_verify = 1;
768             }
769             } else {
770 1         2 for my $arg (@param_args) {
771 2         5 %SEEN_FROM_CLASS = ();
772              
773             # call resolve_param_value_from_text without a via_method to "bootstrap" recursion
774 2         8 my @arg_results = $self->resolve_param_value_from_text($arg, $param_class);
775              
776 2 50 33     6 if (@arg_results != 1 && !defined($require_user_verify)) {
777 0         0 $require_user_verify = 1;
778             }
779              
780 2         3 push @results, @arg_results;
781             }
782             }
783 3 50       9 if (@results) {
784             # the ALTERNATE_FROM_CLASS stuff leads to non $param_class objects in results
785 3         17 @results = List::MoreUtils::uniq(@results);
786 3         6 @results = grep { $_->isa($param_class) } @results;
  6         21  
787              
788 3         30 $self->status_message($param_resolve_message . " found " . @results);
789             }
790             else {
791 0 0       0 if ($bx_error) {
792 0         0 $self->status_message($bx_error);
793             }
794 0         0 $self->status_message($param_resolve_message . " none found.");
795             }
796              
797 3 50       15 return unless (@results);
798              
799 3         38 my $limit_results_method = "_limit_results_for_$param_name";
800 3 50       9 if ( $self->can($limit_results_method) ) {
801 0         0 @results = $self->$limit_results_method(@results);
802 0 0       0 return unless (@results);
803             }
804 3         212 @results = List::MoreUtils::uniq(@results);
805 3 50       8 if ($require_user_verify) {
806 0 0 0     0 if (!$pmeta->{'is_many'} && @results > 1) {
807 0 0       0 $MESSAGE .= "\n" if ($MESSAGE);
808 0         0 $MESSAGE .= "'$param_name' expects only one result.";
809              
810 0 0       0 if ($ENV{UR_NO_REQUIRE_USER_VERIFY}) {
811 0         0 die "$MESSAGE\n";
812             }
813             }
814 0         0 @results = $self->_get_user_verification_for_param_value($param_name, @results);
815             }
816 3   33     10 while (!$pmeta->{'is_many'} && @results > 1) {
817 0 0       0 $MESSAGE .= "\n" if ($MESSAGE);
818 0         0 $MESSAGE .= "'$param_name' expects only one result, not many!";
819 0         0 @results = $self->_get_user_verification_for_param_value($param_name, @results);
820             }
821              
822 3 50       5 if (wantarray) {
    0          
    0          
823 3         12 return @results;
824             }
825             elsif (not defined wantarray) {
826 0         0 return;
827             }
828             elsif (@results > 1) {
829 0         0 Carp::confess("Multiple matches found!");
830             }
831             else {
832 0         0 return $results[0];
833             }
834             }
835              
836             sub resolve_param_value_from_text {
837 2     2 0 4 my ($self, $param_arg, $param_class, $via_method) = @_;
838              
839 2 50       4 unless ($param_class) {
840 0         0 $param_class = $self->class;
841             }
842              
843 2         3 $SEEN_FROM_CLASS{$param_class} = 1;
844 2         3 my @results;
845             # try getting BoolExpr, otherwise fallback on '_resolve_param_value_from_text_by_name_or_id' parser
846 2         3 eval { @results = $self->_resolve_param_value_from_text_by_bool_expr($param_class, $param_arg); };
  2         6  
847 2 50 33     13 Carp::croak($@) if ($@ and $@ !~ m/Not a valid BoolExpr/);
848 2 50 33     9 if (!@results && !$@) {
849             # no result and was valid BoolExpr then we don't want to break it apart because we
850             # could query enormous amounts of info
851 0         0 return;
852             }
853             # the first param_arg is all param_args to try BoolExpr so skip if it has commas
854 2 50 33     8 if (!@results && $param_arg !~ /,/) {
855 2         3 my @results_by_string;
856 2 50       6 if ($param_class->can('_resolve_param_value_from_text_by_name_or_id')) {
857 0         0 @results_by_string = $param_class->_resolve_param_value_from_text_by_name_or_id($param_arg);
858             }
859             else {
860 2         101 @results_by_string = $self->_resolve_param_value_from_text_by_name_or_id($param_class, $param_arg);
861             }
862 2         3 push @results, @results_by_string;
863             }
864             # if we still don't have any values then try via alternate class
865 2 0 33     3 if (!@results && $param_arg !~ /,/) {
866 0         0 @results = $self->_resolve_param_value_via_related_class_method($param_class, $param_arg, $via_method);
867             }
868              
869 2 50       4 if ($via_method) {
870 0         0 @results = map { $_->$via_method } @results;
  0         0  
871             }
872              
873 2 50       35 if (wantarray) {
    0          
    0          
874 2         4 return @results;
875             }
876             elsif (not defined wantarray) {
877 0         0 return;
878             }
879             elsif (@results > 1) {
880 0         0 Carp::confess("Multiple matches found!");
881             }
882             else {
883 0         0 return $results[0];
884             }
885             }
886              
887             sub _resolve_param_value_via_related_class_method {
888 0     0   0 my ($self, $param_class, $param_arg, $via_method) = @_;
889 0         0 my @results;
890             my $via_class;
891 0 0       0 if (exists($ALTERNATE_FROM_CLASS{$param_class})) {
892 0         0 $via_class = $param_class;
893             }
894             else {
895 0         0 for my $class (keys %ALTERNATE_FROM_CLASS) {
896 0 0       0 if ($param_class->isa($class)) {
897 0 0       0 if ($via_class) {
898 0         0 $self->error_message("Found additional via_class $class but already found $via_class!");
899             }
900 0         0 $via_class = $class;
901             }
902             }
903             }
904 0 0       0 if ($via_class) {
905 0         0 my @from_classes = sort keys %{$ALTERNATE_FROM_CLASS{$via_class}};
  0         0  
906 0   0     0 while (@from_classes && !@results) {
907 0         0 my $from_class = shift @from_classes;
908 0         0 my @methods = @{$ALTERNATE_FROM_CLASS{$via_class}{$from_class}};
  0         0  
909 0         0 my $method;
910 0 0 0     0 if (@methods > 1 && !$via_method && !$ENV{UR_NO_REQUIRE_USER_VERIFY}) {
      0        
911 0         0 $self->status_message("Trying to find $via_class via $from_class...\n");
912 0         0 my $method_choices;
913 0         0 for (my $i = 0; $i < @methods; $i++) {
914 0         0 $method_choices .= ($i + 1) . ": " . $methods[$i];
915 0 0       0 $method_choices .= " [default]" if ($i == 0);
916 0         0 $method_choices .= "\n";
917             }
918 0         0 $method_choices .= (scalar(@methods) + 1) . ": none\n";
919 0         0 $method_choices .= "Which method would you like to use?";
920 0         0 my $response = $self->_ask_user_question($method_choices, 0, '\d+', 1, '#');
921 0 0       0 if ($response =~ /^\d+$/) {
    0          
922 0         0 $response--;
923 0 0 0     0 if ($response == @methods) {
    0          
924 0         0 $method = undef;
925             }
926             elsif ($response >= 0 && $response <= $#methods) {
927 0         0 $method = $methods[$response];
928             }
929             else {
930 0         0 $self->error_message("Response was out of bounds, exiting...");
931 0         0 exit;
932             }
933 0         0 $ALTERNATE_FROM_CLASS{$via_class}{$from_class} = [$method];
934             }
935             elsif (!$response) {
936 0         0 $self->status_message("Exiting...");
937             }
938             }
939             else {
940 0         0 $method = $methods[0];
941             }
942 0 0       0 unless($SEEN_FROM_CLASS{$from_class}) {
943             #$self->debug_message("Trying to find $via_class via $from_class->$method...");
944 0         0 @results = eval {$self->resolve_param_value_from_text($param_arg, $from_class, $method)};
  0         0  
945             }
946             } # END for my $from_class (@from_classes)
947             } # END if ($via_class)
948 0         0 return @results;
949             }
950              
951             sub _resolve_param_value_from_text_by_bool_expr {
952 2     2   4 my ($self, $param_class, $arg) = @_;
953              
954 2         2 my @results;
955 2         2 my $bx = eval {
956 2         7 UR::BoolExpr->resolve_for_string($param_class, $arg);
957             };
958 2 50       6 if ($bx) {
959 0         0 @results = $param_class->get($bx);
960             }
961             else {
962 2         14 die "Not a valid BoolExpr";
963             }
964             #$self->debug_message("B: $param_class '$arg' " . scalar(@results));
965              
966 0         0 return @results;
967             }
968              
969             sub _try_get_by_id {
970 2     2   1 my ($self, $param_class, $str) = @_;
971              
972 2         7 my $class_meta = $param_class->__meta__;
973 2         7 my @id_property_names = $class_meta->id_property_names;
974 2 50       10 if (@id_property_names == 0) {
    50          
975 0         0 die "Failed to determine ID property names for class ($param_class).";
976             } elsif (@id_property_names == 1) {
977 2   50     5 my $id_data_type = $class_meta->property_meta_for_name($id_property_names[0])->_data_type_as_class_name || '';
978             # Validate $str, if possible, to prevent warnings from database if $str does not fit column type.
979 2 50       14 if ($id_data_type->isa('UR::Value::Number')) { # Oracle's Number data type includes floats but we just use integers for numeric IDs
980 0         0 return ($str =~ /^[+-]?\d+$/);
981             }
982             }
983 2         7 return 1;
984             }
985              
986             sub _resolve_param_value_from_text_by_name_or_id {
987 2     2   3 my ($self, $param_class, $str) = @_;
988 2         3 my (@results);
989 2 50       7 if ($self->_try_get_by_id($param_class, $str)) {
990 2         3 @results = eval { $param_class->get($str) };
  2         7  
991             }
992 2 50 33     10 if (!@results && $param_class->can('name')) {
993 2         18 @results = $param_class->get(name => $str);
994 2 50       6 unless (@results) {
995 0         0 @results = $param_class->get("name like" => "$str");
996             }
997             }
998              
999 2         5 return @results;
1000             }
1001              
1002             sub _get_user_verification_for_param_value {
1003 0     0     my ($self, $param_name, @list) = @_;
1004              
1005 0           my $n_list = scalar(@list);
1006 0 0 0       if ($n_list > 200 && !$ENV{UR_NO_REQUIRE_USER_VERIFY}) {
1007 0           my $response = $self->_ask_user_question("Would you [v]iew all $n_list item(s) for '$param_name', (p)roceed, or e(x)it?", 0, '[v]|p|x', 'v');
1008 0 0 0       if(!$response || $response eq 'x') {
1009 0           $self->status_message("Exiting...");
1010 0           exit;
1011             }
1012 0 0         return @list if($response eq 'p');
1013             }
1014              
1015 0           my @new_list;
1016 0           while (!@new_list) {
1017 0           @new_list = $self->_get_user_verification_for_param_value_drilldown($param_name, @list);
1018             }
1019              
1020 0           my @ids = map { $_->id } @new_list;
  0            
1021 0           $self->status_message("The IDs for your selection are:\n" . join(',', @ids) . "\n\n");
1022 0           return @new_list;
1023             }
1024              
1025             sub _get_user_verification_for_param_value_drilldown {
1026 0     0     my ($self, $param_name, @results) = @_;
1027 0           my $n_results = scalar(@results);
1028 0           my $pad = length($n_results);
1029              
1030             # Allow an environment variable to be set to disable the require_user_verify attribute
1031 0 0         return @results if ($ENV{UR_NO_REQUIRE_USER_VERIFY});
1032 0 0         return if (@results == 0);
1033              
1034 0           my @dnames = map {$_->__display_name__} grep { $_->can('__display_name__') } @results;
  0            
  0            
1035 0 0         my $max_dname_length = @dnames ? length((sort { length($b) <=> length($a) } @dnames)[0]) : 0;
  0            
1036 0 0         my @statuses = map {$_->status || 'missing_status'} grep { $_->can('status') } @results;
  0            
  0            
1037 0 0         my $max_status_length = @statuses ? length((sort { length($b) <=> length($a) } @statuses)[0]) : 0;
  0            
1038              
1039 0           my @results_with_display_name_and_class = map { [ $_->__display_name__, $_->class, $_ ] } @results;
  0            
1040 0           @results = map { $_->[2] }
1041 0           sort { $a->[1] cmp $b->[1] }
1042 0           sort { $a->[0] cmp $b->[0] }
  0            
1043             @results_with_display_name_and_class;
1044              
1045 0           my @classes = List::MoreUtils::uniq(map {$_->class} @results);
  0            
1046              
1047 0           my $response;
1048 0           my @caller = caller(1);
1049 0           while (!$response) {
1050 0           $self->status_message("\n");
1051             # TODO: Replace this with lister?
1052 0           for (my $i = 1; $i <= $n_results; $i++) {
1053 0           my $param = $results[$i - 1];
1054 0           my $num = $self->_pad_string($i, $pad);
1055 0           my $msg = "$num:";
1056 0           $msg .= ' ' . $self->_pad_string($param->__display_name__, $max_dname_length, 'suffix');
1057 0           my $status = ' ';
1058 0 0         if ($param->can('status')) {
1059 0   0       $status = $param->status || 'missing_status';
1060             }
1061 0           $msg .= "\t" . $self->_pad_string($status, $max_status_length, 'suffix');
1062 0 0         $msg .= "\t" . $param->class if (@classes > 1);
1063 0           $self->status_message($msg);
1064             }
1065 0 0         if ($MESSAGE) {
1066 0           $MESSAGE = "\n" . '*'x80 . "\n" . $MESSAGE . "\n" . '*'x80 . "\n";
1067 0           $self->status_message($MESSAGE);
1068 0           $MESSAGE = '';
1069             }
1070 0           my $pretty_values = '(c)ontinue, (h)elp, e(x)it';
1071 0           my $valid_values = '\*|c|h|x|[-+]?[\d\-\., ]+';
1072 0 0         if ($caller[3] =~ /_trim_list_from_response/) {
1073 0           $pretty_values .= ', (b)ack';
1074 0           $valid_values .= '|b';
1075             }
1076 0           $response = $self->_ask_user_question("Please confirm the above items for '$param_name' or modify your selection.", 0, $valid_values, 'h', $pretty_values.', or specify item numbers to use');
1077 0 0 0       if (lc($response) eq 'h' || !$self->_validate_user_response_for_param_value_verification($response)) {
1078 0 0         $MESSAGE .= "\n" if ($MESSAGE);
1079 0           $MESSAGE .=
1080             "Help:\n".
1081             "* Specify which elements to keep by listing them, e.g. '1,3,12' would keep\n".
1082             " items 1, 3, and 12.\n".
1083             "* Begin list with a minus to remove elements, e.g. '-1,3,9' would remove\n".
1084             " items 1, 3, and 9.\n".
1085             "* Ranges can be used, e.g. '-11-17, 5' would remove items 11 through 17 and\n".
1086             " remove item 5.";
1087 0           $response = '';
1088             }
1089             }
1090 0 0         if (lc($response) eq 'x') {
    0          
    0          
    0          
1091 0           $self->status_message("Exiting...");
1092 0           exit;
1093             }
1094             elsif (lc($response) eq 'b') {
1095 0           return;
1096             }
1097             elsif (lc($response) eq 'c' | $response eq '*') {
1098 0           return @results;
1099             }
1100             elsif ($response =~ /^[-+]?[\d\-\., ]+$/) {
1101 0           @results = $self->_trim_list_from_response($response, $param_name, @results);
1102 0           return @results;
1103             }
1104             else {
1105 0           die $self->error_message("Conditional exception, should not have been reached!");
1106             }
1107             }
1108              
1109             sub terminal_input_filehandle {
1110 0     0 0   my $self = shift;
1111              
1112 0           my $fh = IO::File->new('/dev/tty', 'r');
1113 0 0         unless ($fh) {
1114 0           Carp::carp("Couldn't open /dev/tty for terminal input: $!\n Using STDIN...");
1115 0           $fh = *STDIN;
1116             }
1117 0           return $fh;
1118             }
1119              
1120             sub _ask_user_question {
1121 0     0     my $self = shift;
1122 0           my $question = shift;
1123 0           my $timeout = shift;
1124 0   0       my $valid_values = shift || "yes|no";
1125 0   0       my $default_value = shift || undef;
1126 0   0       my $pretty_valid_values = shift || $valid_values;
1127 0           $valid_values = lc($valid_values);
1128 0           my $input;
1129 0 0         $timeout = 60 unless(defined($timeout));
1130              
1131 0     0     local $SIG{ALRM} = sub { print STDERR "Exiting, failed to reply to question '$question' within '$timeout' seconds.\n"; exit; };
  0            
  0            
1132 0           print STDERR "\n$question\n";
1133 0           print STDERR "Reply with $pretty_valid_values: ";
1134              
1135 0 0         unless ($self->_can_interact_with_user) {
1136 0           print STDERR "\n";
1137 0           die $self->error_message("Attempting to ask user question but cannot interact with user!");
1138             }
1139              
1140 0           my $terminal = $self->terminal_input_filehandle();
1141              
1142 0 0         alarm($timeout) if ($timeout);
1143 0           chomp($input = $terminal->getline());
1144 0 0         alarm(0) if ($timeout);
1145              
1146 0           print STDERR "\n";
1147              
1148 0 0         if(lc($input) =~ /^$valid_values$/) {
    0          
1149 0           return lc($input);
1150             }
1151             elsif ($default_value) {
1152 0           return $default_value;
1153             }
1154             else {
1155 0           $self->error_message("'$input' is an invalid answer to question '$question'\n\n");
1156 0           return;
1157             }
1158             }
1159              
1160             sub _validate_user_response_for_param_value_verification {
1161 0     0     my ($self, $response_text) = @_;
1162 0 0         $response_text = substr($response_text, 1) if ($response_text =~ /^[+-]/);
1163 0           my @response = split(/[\s\,]/, $response_text);
1164 0           for my $response (@response) {
1165 0 0         if ($response =~ /^[xbc*]$/) {
1166 0           return 1;
1167             }
1168 0 0         if ($response !~ /^(\d+)([-\.]+(\d+))?$/) {
1169 0 0         $MESSAGE .= "\n" if ($MESSAGE);
1170 0           $MESSAGE .= "ERROR: Invalid list provided ($response)";
1171 0           return 0;
1172             }
1173 0 0 0       if ($3 && $1 && $3 < $1) {
      0        
1174 0 0         $MESSAGE .= "\n" if ($MESSAGE);
1175 0           $MESSAGE .= "ERROR: Inverted range provided ($1-$3)";
1176 0           return 0;
1177             }
1178             }
1179 0           return 1;
1180             }
1181              
1182             sub _trim_list_from_response {
1183 0     0     my ($self, $response_text, $param_name, @list) = @_;
1184              
1185 0           my $method;
1186 0 0         if ($response_text =~ /^[+-]/) {
1187 0           $method = substr($response_text, 0, 1);
1188 0           $response_text = substr($response_text, 1);
1189             }
1190             else {
1191 0           $method = '+';
1192             }
1193              
1194 0           my @response = split(/[\s\,]/, $response_text);
1195 0           my %indices;
1196 0 0         @indices{0..$#list} = 0..$#list if ($method eq '-');
1197              
1198 0           for my $response (@response) {
1199 0           $response =~ /^(\d+)([-\.]+(\d+))?$/;
1200 0           my $low = $1; $low--;
  0            
1201 0   0       my $high = $3 || $1; $high--;
  0            
1202 0 0         die if ($high < $low);
1203 0 0         if ($method eq '+') {
1204 0           @indices{$low..$high} = $low..$high;
1205             }
1206             else {
1207 0           delete @indices{$low..$high};
1208             }
1209             }
1210             #$self->debug_message("Indices: " . join(',', sort(keys %indices)));
1211 0           my @new_list = $self->_get_user_verification_for_param_value_drilldown($param_name, @list[sort keys %indices]);
1212 0 0         unless (@new_list) {
1213 0           @new_list = $self->_get_user_verification_for_param_value_drilldown($param_name, @list);
1214             }
1215 0           return @new_list;
1216             }
1217              
1218             sub _pad_string {
1219 0     0     my ($self, $str, $width, $pos) = @_;
1220 0 0         $str = '' if ! defined $str;
1221 0           my $padding = $width - length($str);
1222 0 0         $padding = 0 if ($padding < 0);
1223 0 0 0       if ($pos && $pos eq 'suffix') {
1224 0           return $str . ' 'x$padding;
1225             }
1226             else {
1227 0           return ' 'x$padding . $str;
1228             }
1229             }
1230              
1231             sub _can_interact_with_user {
1232 0     0     my $self = shift;
1233 0 0         if ( -t STDERR ) {
1234 0           return 1;
1235             }
1236             else {
1237 0           return 0;
1238             }
1239             }
1240              
1241              
1242             1;
1243              
1244