File Coverage

lib/Command/V1.pm
Criterion Covered Total %
statement 369 664 55.5
branch 122 318 38.3
condition 39 96 40.6
subroutine 43 63 68.2
pod 0 29 0.0
total 573 1170 48.9


line stmt bran cond sub pod time code
1             package Command::V1;
2              
3 15     15   378 use strict;
  15         21  
  15         403  
4 15     15   55 use warnings;
  15         18  
  15         385  
5              
6 15     15   53 use UR;
  15         17  
  15         98  
7 15     15   54 use Data::Dumper;
  15         20  
  15         693  
8 15     15   60 use File::Basename;
  15         22  
  15         715  
9 15     15   9352 use Getopt::Long;
  15         116390  
  15         65  
10 15     15   11110 use Term::ANSIColor qw();
  15         67311  
  15         2123  
11             require Text::Wrap;
12              
13             our $VERSION = "0.46"; # UR $VERSION;
14              
15             UR::Object::Type->define(
16             class_name => __PACKAGE__,
17             is => ['Command', 'Command::Common'],
18             is_abstract => 1,
19             attributes_have => [
20             is_input => { is => 'Boolean', is_optional => 1 },
21             is_output => { is => 'Boolean', is_optional => 1 },
22             is_param => { is => 'Boolean', is_optional => 1 },
23             shell_args_position => { is => 'Integer', is_optional => 1,
24             doc => 'when set, this property is a positional argument when run from a shell' },
25             ],
26             has_optional => [
27             debug => { is => 'Boolean', doc => 'enable debug messages' },
28             is_executed => { is => 'Boolean' },
29             result => { is => 'Scalar', is_output => 1 },
30             original_command_line => { is => 'String', doc => 'null-byte separated list of command and arguments when run via execute_with_shell_params_and_exit'},
31             ],
32             );
33              
34             # This is changed with "local" where used in some places
35             $Text::Wrap::columns = 100;
36              
37             # Required for color output
38             eval {
39             binmode STDOUT, ":utf8";
40             binmode STDERR, ":utf8";
41             };
42              
43             sub _init_subclass {
44             # Each Command subclass has an automatic wrapper around execute().
45             # This ensures it can be called as a class or instance method,
46             # and that proper handling occurs around it.
47 78     78   130 my $subclass_name = $_[0];
48 15     15   100 no strict;
  15         25  
  15         348  
49 15     15   49 no warnings;
  15         20  
  15         23861  
50 78 50       204 if ($subclass_name->can('execute')) {
51             # NOTE: manipulating %{ $subclass_name . '::' } directly causes ptkdb to segfault perl
52 78         731 my $new_symbol = "${subclass_name}::_execute_body";
53 78         141 my $old_symbol = "${subclass_name}::execute";
54 78         464 *$new_symbol = *$old_symbol;
55 78         197 undef *$old_symbol;
56             }
57             else {
58             #print "no execute in $subclass_name\n";
59             }
60              
61 78 50       183 if($subclass_name->can('shortcut')) {
62 78         731 my $new_symbol = "${subclass_name}::_shortcut_body";
63 78         132 my $old_symbol = "${subclass_name}::shortcut";
64 78         340 *$new_symbol = *$old_symbol;
65 78         142 undef *$old_symbol;
66             }
67              
68 78         237 return 1;
69             }
70              
71             #
72             # Standard external interface for shell dispatchers
73             #
74              
75             # TODO: abstract out all dispatchers for commands into a given API
76             sub execute_with_shell_params_and_exit {
77             # This automatically parses command-line options and "does the right thing":
78 0     0 0 0 my $class = shift;
79              
80 0 0       0 if (@_) {
81 0         0 die
82             qq|
83             No params expected for execute_with_shell_params_and_exit().
84             Usage:
85              
86             #!/usr/bin/env perl
87             use My::Command;
88             My::Command->execute_with_shell_params_and_exit;
89             |;
90             }
91              
92 0   0     0 $Command::entry_point_class ||= $class;
93 0   0     0 $Command::entry_point_bin ||= File::Basename::basename($0);
94              
95 0 0       0 if ($ENV{COMP_CWORD}) {
96 0         0 require Getopt::Complete;
97 0         0 my @spec = $class->resolve_option_completion_spec();
98 0         0 my $options = Getopt::Complete::Options->new(@spec);
99 0         0 $options->handle_shell_completion;
100 0         0 die "error: failed to exit after handling shell completion!";
101             }
102              
103 0         0 my @argv = @ARGV;
104 0         0 @ARGV = ();
105 0         0 my $exit_code;
106 0         0 eval {
107 0         0 $exit_code = $class->_execute_with_shell_params_and_return_exit_code(@argv);
108 0 0       0 UR::Context->commit or die "Failed to commit!: " . UR::Context->error_message();
109             };
110 0 0       0 if ($@) {
111 0         0 $class->error_message($@);
112 0 0       0 UR::Context->rollback or die "Failed to rollback changes after failed commit!!!\n";
113 0 0       0 $exit_code = 255 unless ($exit_code);
114             }
115 0         0 exit $exit_code;
116             }
117              
118             sub _execute_with_shell_params_and_return_exit_code {
119 2     2   4541 my $class = shift;
120 2         6 my @argv = @_;
121              
122 2         8 my $original_cmdline = join("\0",$0,@argv);
123              
124             # make --foo=bar equivalent to --foo bar
125 2 50       5 @argv = map { ($_ =~ /^(--\w+?)\=(.*)/) ? ($1,$2) : ($_) } @argv;
  2         17  
126 2         13 my ($delegate_class, $params,$error_tag_list) = $class->resolve_class_and_params_for_argv(@argv);
127 2         4 my $rv;
128 2 50 33     8 if ($error_tag_list and @$error_tag_list) {
129             $class->error_message("There were problems resolving some command-line parameters:\n\t"
130             . join("\n\t",
131 0         0 map { my($props,$type,$desc) = @$_{'properties','type','desc'};
  0         0  
132 0         0 "Property '" . join("','",@$props) . "' ($type): $desc" }
133             @$error_tag_list));
134             } else {
135 2         30 $rv = $class->_execute_delegate_class_with_params($delegate_class,$params,$original_cmdline);
136             }
137              
138 2         62 my $exit_code = $delegate_class->exit_code_for_return_value($rv);
139 2         12 return $exit_code;
140             }
141              
142             # this is called by both the shell dispatcher and http dispatcher for now
143             sub _execute_delegate_class_with_params {
144 4     4   1704 my ($class, $delegate_class, $params, $original_cmdline) = @_;
145              
146 4 50       12 unless ($delegate_class) {
147 0         0 $class->usage_message($class->help_usage_complete_text);
148 0         0 return;
149             }
150              
151 4         33 $delegate_class->dump_status_messages(1);
152 4         29 $delegate_class->dump_warning_messages(1);
153 4         22 $delegate_class->dump_error_messages(1);
154 4         18 $delegate_class->dump_usage_messages(1);
155 4         27 $delegate_class->dump_debug_messages(0);
156              
157 4 50 33     27 if ( $delegate_class->is_sub_command_delegator && !defined($params) ) {
158 0         0 my $command_name = $delegate_class->command_name;
159 0         0 $delegate_class->status_message($delegate_class->help_usage_complete_text);
160 0         0 $delegate_class->error_message("Please specify a valid sub-command for '$command_name'.");
161 0         0 return;
162             }
163 4 100       12 if ( $params->{help} ) {
164 2         14 $delegate_class->usage_message($delegate_class->help_usage_complete_text);
165 2         10 return 1;
166             }
167              
168 2 50       5 $params->{'original_command_line'} = $original_cmdline if (defined $original_cmdline);
169 2         9 my $command_object = $delegate_class->create(%$params);
170              
171 2 50       6 unless ($command_object) {
172             # The delegate class should have emitted an error message.
173             # This is just in case the developer is sloppy, and the user will think the task did not fail.
174 0         0 print STDERR "Exiting.\n";
175 0         0 return;
176             }
177              
178 2         5 $command_object->dump_status_messages(1);
179 2         7 $command_object->dump_warning_messages(1);
180 2         5 $command_object->dump_error_messages(1);
181 2         6 $command_object->dump_debug_messages($command_object->debug);
182 2 100       6 if ($command_object->debug) {
183 1         57 UR::ModuleBase->dump_debug_messages($command_object->debug);
184             }
185              
186 2         52 my $rv = $command_object->execute($params);
187              
188 2 50       9 if ($command_object->__errors__) {
189 0         0 $command_object->delete;
190             }
191              
192 2         6 return $rv;
193             }
194              
195             #
196             # Methods to override in concrete subclasses.
197             #
198              
199             # Override "execute" or "_execute_body" to implement the body of the command.
200             # See above for details of internal implementation.
201              
202             # By default, there are no bare arguments.
203             sub _bare_shell_argument_names {
204 25     25   47 my $self = shift;
205 25         103 my $meta = $self->__meta__;
206             my @ordered_names =
207 3         11 map { $_->property_name }
208 0         0 sort { $a->{shell_args_position} <=> $b->{shell_args_position} }
209 25         88 grep { $_->{shell_args_position} }
  149         162  
210             $self->_shell_args_property_meta();
211 25         132 return @ordered_names;
212             }
213              
214             sub help_brief {
215 0     0 0 0 my $self = shift;
216 0 0       0 if (my $doc = $self->__meta__->doc) {
217 0         0 return $doc;
218             }
219             else {
220 0         0 my @parents = $self->__meta__->ancestry_class_metas;
221 0         0 for my $parent (@parents) {
222 0 0       0 if (my $doc = $parent->doc) {
223 0         0 return $doc;
224             }
225             }
226 0 0       0 if ($self->is_sub_command_delegator) {
227 0         0 return "";
228             }
229             else {
230 0         0 return "no description!!!: define 'doc' in $self";
231             }
232             }
233             }
234              
235              
236             sub help_synopsis {
237 3     3 0 5 my $self = shift;
238 3         7 return '';
239             }
240              
241             sub help_detail {
242 3     3 0 666 my $self = shift;
243 3   33     21 return "!!! define help_detail() in module " . ref($self) || $self . "!";
244             }
245              
246             sub sub_command_category {
247 0     0 0 0 return;
248             }
249              
250             sub sub_command_sort_position {
251             # override to do something besides alpha sorting by name
252 0     0 0 0 return '9999999999 ' . $_[0]->command_name_brief;
253             }
254              
255              
256             #
257             # Self reflection
258             #
259              
260             sub is_abstract {
261             # Override when writing an subclass which is also abstract.
262 6     6 0 107 my $self = shift;
263 6         20 my $class_meta = $self->__meta__;
264 6         36 return $class_meta->is_abstract;
265             }
266              
267             sub is_executable {
268 6     6 0 10 my $self = shift;
269 6 50       29 if ($self->can("_execute_body") eq __PACKAGE__->can("_execute_body")) {
    50          
270 0         0 return;
271             }
272             elsif ($self->is_abstract) {
273 0         0 return;
274             }
275             else {
276 6         21 return 1;
277             }
278             }
279              
280             sub is_sub_command_delegator {
281 110     110 0 143 my $self = shift;
282 110 100       378 if (scalar($self->sub_command_dirs)) {
283 20         60 return 1;
284             }
285             else {
286 90         283 return;
287             }
288             }
289              
290             sub _time_now {
291             # return the current time in context
292             # this may not be the real time in selected cases
293 0     0   0 shift->__context__->now;
294             }
295              
296             sub color_command_name {
297 0     0 0 0 my $text = shift;
298              
299 0         0 my $colored_text = [];
300              
301 0         0 my @COLOR_TEMPLATES = ('red', 'bold red', 'magenta', 'bold magenta');
302 0         0 my @parts = split(/\s+/, $text);
303 0         0 for(my $i = 0 ; $i < @parts ; $i++ ){
304 0 0       0 push @$colored_text, ($i < @COLOR_TEMPLATES) ? Term::ANSIColor::colored($parts[$i], $COLOR_TEMPLATES[$i]) : $parts[$i];
305             }
306              
307 0         0 return join(' ', @$colored_text);
308             }
309              
310             sub _base_command_class_and_extension {
311 0     0   0 my $self = shift;
312 0   0     0 my $class = ref($self) || $self;
313 0         0 return ($class =~ /^(.*)::([^\:]+)$/);
314             }
315              
316             sub _command_name_for_class_word {
317 62     62   60 my $self = shift;
318 62         62 my $s = shift;
319 62         72 $s =~ s/_/-/g;
320 62         240 $s =~ s/^([A-Z])/\L$1/; # ignore first capital because that is assumed
321 62         163 $s =~ s/([A-Z])/-$1/g; # all other capitals prepend a dash
322 62         119 $s =~ s/([a-zA-Z])([0-9])/$1$2/g; # treat number as begining word
323 62         83 $s = lc($s);
324 62         146 return $s;
325             }
326              
327             sub command_name {
328 8     8 0 48 my $self = shift;
329 8   66     36 my $class = ref($self) || $self;
330 8         14 my $prepend = '';
331 8 50 33     37 if (defined($Command::entry_point_class) and $class =~ /^($Command::entry_point_class)(::.+|)$/) {
332 0         0 $prepend = $Command::entry_point_bin;
333 0         0 $class = $2;
334 0 0       0 if ($class =~ s/^:://) {
335 0         0 $prepend .= ' ';
336             }
337             }
338 8         32 my @words = grep { $_ ne 'Command' } split(/::/,$class);
  18         49  
339 8         17 my $n = join(' ', map { $self->_command_name_for_class_word($_) } @words);
  17         78  
340 8         32 return $prepend . $n;
341             }
342              
343             sub command_name_brief {
344 45     45 0 78 my $self = shift;
345 45   33     123 my $class = ref($self) || $self;
346 45         108 my @words = grep { $_ ne 'Command' } split(/::/,$class);
  201         220  
347 45         59 my $n = join(' ', map { $self->_command_name_for_class_word($_) } $words[-1]);
  45         165  
348 45         80 return $n;
349             }
350             #
351             # Methods to transform shell args into command properties
352             #
353              
354             my $_resolved_params_from_get_options = {};
355             sub _resolved_params_from_get_options {
356 0     0   0 return $_resolved_params_from_get_options;
357             }
358              
359             sub resolve_option_completion_spec {
360 40     40 0 48 my $class = shift;
361 40         48 my @completion_spec;
362              
363 40 100       99 if ($class->is_sub_command_delegator) {
364 9         11 my @sub = eval { $class->sub_command_names};
  9         79  
365 9 50       33 if ($@) {
366 0         0 $class->warning_message("Couldn't load class $class: $@\nSkipping $class...");
367 0         0 return;
368             }
369 9         25 for my $sub (@sub) {
370 43         147 my $sub_class = $class->class_for_sub_command($sub);
371 43 50       387 my $sub_tree = $sub_class->resolve_option_completion_spec() if defined($sub_class);
372              
373             # Hack to fix several broken commands, this should be removed once commands are fixed.
374             # If the commands were not broken then $sub_tree will always exist.
375             # Basically if $sub_tree is undef then we need to remove '>' to not break the OPTS_SPEC
376 43 50       55 if ($sub_tree) {
377 43         111 push @completion_spec, '>' . $sub => $sub_tree;
378             }
379             else {
380 0         0 print "WARNING: $sub has sub_class $sub_class of ($class) but could not resolve option completion spec for it.\n".
381             "Setting $sub to non-delegating command, investigate to correct tab completion.\n";
382 0         0 push @completion_spec, $sub => undef;
383             }
384             }
385 9         28 push @completion_spec, "help!" => undef;
386             }
387             else {
388 31         33 my $params_hash;
389 31         184 @completion_spec = $class->_shell_args_getopt_complete_specification;
390 15     15   82 no warnings;
  15         22  
  15         12711  
391 31 50       45 unless (grep { /^help\W/ } @completion_spec) {
  378         382  
392 31         51 push @completion_spec, "help!" => undef;
393             }
394             }
395              
396             return \@completion_spec
397 40         71 }
398              
399             sub resolve_class_and_params_for_argv {
400             # This is used by execute_with_shell_params_and_exit, but might be used within an application.
401 28     28 0 28902 my $self = shift;
402 28         69 my @argv = @_;
403              
404 28 100       122 if ($self->is_sub_command_delegator) {
405 3 50 33     49 if ( $argv[0] and $argv[0] !~ /^\-/
      33        
406             and my $class_for_sub_command = $self->class_for_sub_command($argv[0]) ) {
407             # delegate
408 3         6 shift @argv;
409 3         26 return $class_for_sub_command->resolve_class_and_params_for_argv(@argv);
410             }
411              
412 0 0       0 if (@argv) {
413             # this has sub-commands, and is also executable
414             # fall through to the execution_logic...
415             }
416             else {
417             #$self->error_message(
418             # 'Bad command "' . $sub_command . '"'
419             # , "\ncommands:"
420             # , $self->help_sub_commands
421             #);
422 0         0 return ($self,undef);
423             }
424             }
425              
426 25         123 my ($params_hash,@spec) = $self->_shell_args_getopt_specification;
427 25 50       49 unless (grep { /^help\W/ } @spec) {
  149         209  
428 25         51 push @spec, "help!";
429             }
430              
431             # Thes nasty GetOptions modules insist on working on
432             # the real @ARGV, while we like a little more flexibility.
433             # Not a problem in Perl. :) (which is probably why it was never fixed)
434 25         56 local @ARGV;
435 25         61 @ARGV = @argv;
436              
437 25         30 do {
438             # GetOptions also likes to emit warnings instead of return a list of errors :(
439 25         27 my @errors;
440 25     0   268 local $SIG{__WARN__} = sub { push @errors, @_ };
  0         0  
441              
442             ## Change the pattern to be '--', '-' followed by a non-digit, or '+'.
443             ## This s the effect of treating a negative number as a value of an option.
444             ## This means that we won't be allowed to have an option named, say, -1.
445             ## But since command modules' properties have to be allowable function names,
446             ## and "1" is not a valid function name, it's not really a problem
447             #Getopt::Long::Configure('prefix_pattern=--|-(?!\D)|\+');
448 25 50       143 unless (GetOptions($params_hash,@spec)) {
449 0         0 Carp::croak( join("\n", @errors) );
450             }
451             };
452              
453             # Q: Is there a standard getopt spec for capturing non-option paramters?
454             # Perhaps that's not getting "options" :)
455             # A: Yes. Use '<>'. But we need to process this anyway, so it won't help us.
456              
457 25 100       13602 if (my @names = $self->_bare_shell_argument_names) {
    50          
458 3         14 for (my $n=0; $n < @ARGV; $n++) {
459 0         0 my $name = $names[$n];
460 0 0       0 unless ($name) {
461 0         0 $self->error_message("Unexpected bare arguments: @ARGV[$n..$#ARGV]!");
462 0         0 return($self, undef);
463             }
464 0         0 my $value = $ARGV[$n];
465 0         0 my $meta = $self->__meta__->property_meta_for_name($name);
466 0 0       0 if ($meta->is_many) {
467 0 0       0 if ($n == $#names) {
468             # slurp the rest
469 0         0 $params_hash->{$name} = [@ARGV[$n..$#ARGV]];
470 0         0 last;
471             }
472             else {
473 0         0 die "has-many property $name is not last in bare_shell_argument_names for $self?!";
474             }
475             }
476             else {
477 0         0 $params_hash->{$name} = $value;
478             }
479             }
480             } elsif (@ARGV) {
481             ## argv but no names
482 0         0 $self->error_message("Unexpected bare arguments: @ARGV!");
483 0         0 return($self, undef);
484             }
485              
486 25         111 for my $key (keys %$params_hash) {
487             # handle any has-many comma-sep values
488 35         71 my $value = $params_hash->{$key};
489 35 50 100     216 if (ref($value)) {
    100          
490 0         0 my @new_value;
491 0         0 for my $v (@$value) {
492 0         0 my @parts = split(/,\s*/,$v);
493 0         0 push @new_value, @parts;
494             }
495 0         0 @$value = @new_value;
496              
497             } elsif ($value eq q('') or $value eq q("")) {
498             # Handle the special values '' and "" to mean undef/NULL
499 4         8 $params_hash->{$key} = '';
500             }
501              
502             # turn dashes into underscores
503 35         48 my $new_key = $key;
504              
505 35 100       111 next unless ($new_key =~ tr/-/_/);
506 28 0 33     74 if (exists $params_hash->{$new_key} && exists $params_hash->{$key}) {
507             # this corrects a problem where is_many properties badly interact
508             # with bare args leaving two entries in the hash like:
509             # a-bare-opt => [], a_bare_opt => ['with','vals']
510 0         0 delete $params_hash->{$key};
511 0         0 next;
512             }
513 28         68 $params_hash->{$new_key} = delete $params_hash->{$key};
514             }
515              
516 25         48 $_resolved_params_from_get_options = $params_hash;
517              
518 25         167 return $self, $params_hash;
519             }
520              
521             #
522             # Methods which let the command auto-document itself.
523             #
524              
525             sub help_usage_complete_text {
526 3     3 0 19 my $self = shift;
527              
528 3         23 my $command_name = $self->command_name;
529 3         5 my $text;
530              
531 3 50       15 if (not $self->is_executable) {
532             # no execute implemented
533 0 0       0 if ($self->is_sub_command_delegator) {
534             # show the list of sub-commands
535 0         0 $text = sprintf(
536             "Sub-commands for %s:\n%s",
537             Term::ANSIColor::colored($command_name, 'bold'),
538             $self->help_sub_commands,
539             );
540             }
541             else {
542             # developer error
543 0         0 my (@sub_command_dirs) = $self->sub_command_dirs;
544 0 0       0 if (grep { -d $_ } @sub_command_dirs) {
  0         0  
545 0         0 $text .= "No execute() implemented in $self, and no sub-commands found!"
546             }
547             else {
548 0         0 $text .= "No execute() implemented in $self, and no directory of sub-commands found!"
549             }
550             }
551             }
552             else {
553             # standard: update this to do the old --help format
554 3         18 my $synopsis = $self->help_synopsis;
555 3         20 my $required_args = $self->help_options(is_optional => 0);
556 3         13 my $optional_args = $self->help_options(is_optional => 1);
557 3 50       17 my $sub_commands = $self->help_sub_commands(brief => 1) if $self->is_sub_command_delegator;
558 3 50 50     9 $text = sprintf(
    50 50        
    50          
    50          
559             "\n%s\n%s\n\n%s%s%s%s%s\n",
560             Term::ANSIColor::colored('USAGE', 'underline'),
561             Text::Wrap::wrap(
562             ' ',
563             ' ',
564             Term::ANSIColor::colored($self->command_name, 'bold'),
565             $self->_shell_args_usage_string || '',
566             ),
567             ( $synopsis
568             ? sprintf("%s\n%s\n", Term::ANSIColor::colored("SYNOPSIS", 'underline'), $synopsis)
569             : ''
570             ),
571             ( $required_args
572             ? sprintf("%s\n%s\n", Term::ANSIColor::colored("REQUIRED ARGUMENTS", 'underline'), $required_args)
573             : ''
574             ),
575             ( $optional_args
576             ? sprintf("%s\n%s\n", Term::ANSIColor::colored("OPTIONAL ARGUMENTS", 'underline'), $optional_args)
577             : ''
578             ),
579             sprintf(
580             "%s\n%s\n",
581             Term::ANSIColor::colored("DESCRIPTION", 'underline'),
582             Text::Wrap::wrap(' ', ' ', $self->help_detail || '')
583             ),
584             ( $sub_commands
585             ? sprintf("%s\n%s\n", Term::ANSIColor::colored("SUB-COMMANDS", 'underline'), $sub_commands)
586             : ''
587             ),
588             );
589             }
590              
591 3         318 return $text;
592             }
593              
594             sub doc_sections {
595 0     0 0 0 my $self = shift;
596 0         0 my @sections;
597              
598 0         0 my $command_name = $self->command_name;
599 15     15   74 my $version = do { no strict; ${ $self->class . '::VERSION' } };
  15         25  
  15         6568  
  0         0  
  0         0  
  0         0  
600 0         0 my $help_brief = $self->help_brief;
601 0         0 my $datetime = $self->__context__->now;
602 0 0       0 my $sub_commands = $self->help_sub_commands(brief => 1) if $self->is_sub_command_delegator;
603 0         0 my ($date,$time) = split(' ',$datetime);
604              
605 0 0       0 push(@sections, UR::Doc::Section->create(
606             title => "NAME",
607             content => "$command_name" . ($help_brief ? " - $help_brief" : ""),
608             format => "pod",
609             ));
610              
611 0 0       0 push(@sections, UR::Doc::Section->create(
612             title => "VERSION",
613             content => "This document " # separated to trick the version updater
614             . "describes $command_name "
615             . ($version ? "version $version " : "")
616             . "($date at $time)",
617             format => "pod",
618             ));
619              
620 0 0       0 if ($sub_commands) {
621 0         0 push(@sections, UR::Doc::Section->create(
622             title => "SUB-COMMANDS",
623             content => $sub_commands,
624             format => 'pod',
625             ));
626             } else {
627 0         0 my $synopsis = $self->command_name . ' ' . $self->_shell_args_usage_string . "\n\n" . $self->help_synopsis;
628 0 0       0 if ($synopsis) {
629 0         0 push(@sections, UR::Doc::Section->create(
630             title => "SYNOPSIS",
631             content => $synopsis,
632             format => 'pod'
633             ));
634             }
635              
636 0         0 my $required_args = $self->help_options(is_optional => 0, format => "pod");
637 0 0       0 if ($required_args) {
638 0         0 push(@sections, UR::Doc::Section->create(
639             title => "REQUIRED ARGUMENTS",
640             content => "=over\n\n$required_args\n\n=back\n\n",
641             format => 'pod'
642             ));
643             }
644              
645 0         0 my $optional_args = $self->help_options(is_optional => 1, format => "pod");
646 0 0       0 if ($optional_args) {
647 0         0 push(@sections, UR::Doc::Section->create(
648             title => "OPTIONAL ARGUMENTS",
649             content => "=over\n\n$optional_args\n\n=back\n\n",
650             format => 'pod'
651             ));
652             }
653              
654             push(@sections, UR::Doc::Section->create(
655             title => "DESCRIPTION",
656 0         0 content => join('', map { " $_\n" } split ("\n",$self->help_detail)),
  0         0  
657             format => 'pod',
658             ));
659             }
660              
661 0         0 return @sections;
662             }
663              
664             sub help_usage_command_pod {
665 0     0 0 0 my $self = shift;
666              
667 0         0 my $command_name = $self->command_name;
668 0         0 my $pod;
669              
670 0         0 if (0) { # (not $self->is_executable)
671             # no execute implemented
672             if ($self->is_sub_command_delegator) {
673             # show the list of sub-commands
674             $pod = "Commands:\n" . $self->help_sub_commands;
675             }
676             else {
677             # developer error
678             my (@sub_command_dirs) = $self->sub_command_dirs;
679             if (grep { -d $_ } @sub_command_dirs) {
680             $pod .= "No execute() implemented in $self, and no sub-commands found!"
681             }
682             else {
683             $pod .= "No execute() implemented in $self, and no directory of sub-commands found!"
684             }
685             }
686             }
687             else {
688             # standard: update this to do the old --help format
689 0         0 my $synopsis = $self->command_name . ' ' . $self->_shell_args_usage_string . "\n\n" . $self->help_synopsis;
690 0         0 my $required_args = $self->help_options(is_optional => 0, format => "pod");
691 0         0 my $optional_args = $self->help_options(is_optional => 1, format => "pod");
692 0 0       0 my $sub_commands = $self->help_sub_commands(brief => 1) if $self->is_sub_command_delegator;
693 0         0 my $help_brief = $self->help_brief;
694 15     15   75 my $version = do { no strict; ${ $self->class . '::VERSION' } };
  15         30  
  15         11929  
  0         0  
  0         0  
  0         0  
695              
696 0 0       0 $pod =
697             "\n=pod"
698             . "\n\n=head1 NAME"
699             . "\n\n"
700             . $self->command_name
701             . ($help_brief ? " - " . $self->help_brief : '')
702             . "\n\n";
703              
704 0 0       0 if ($version) {
705 0         0 $pod .=
706             "\n\n=head1 VERSION"
707             . "\n\n"
708             . "This document " # separated to trick the version updater
709             . "describes " . $self->command_name . " version " . $version . '.'
710             . "\n\n";
711             }
712              
713 0 0       0 if ($sub_commands) {
714 0 0       0 $pod .=
715             (
716             $sub_commands
717             ? "=head1 SUB-COMMANDS\n\n" . $sub_commands . "\n\n"
718             : ''
719             )
720             }
721             else {
722             $pod .=
723             (
724             $synopsis
725             ? "=head1 SYNOPSIS\n\n" . $synopsis . "\n\n"
726             : ''
727             )
728             . (
729             $required_args
730             ? "=head1 REQUIRED ARGUMENTS\n\n=over\n\n" . $required_args . "\n\n=back\n\n"
731             : ''
732             )
733             . (
734             $optional_args
735             ? "=head1 OPTIONAL ARGUMENTS\n\n=over\n\n" . $optional_args . "\n\n=back\n\n"
736             : ''
737             )
738             . "=head1 DESCRIPTION:\n\n"
739 0 0       0 . join('', map { " $_\n" } split ("\n",$self->help_detail))
  0 0       0  
    0          
740             . "\n";
741             }
742              
743 0         0 $pod .= "\n\n=cut\n\n";
744              
745             }
746 0         0 return "\n$pod";
747             }
748              
749             sub help_header {
750 0     0 0 0 my $class = shift;
751 0         0 return sprintf("%s - %-80s\n",
752             $class->command_name
753             ,$class->help_brief
754             )
755             }
756              
757             sub help_options {
758 6     6 0 7 my $self = shift;
759 6         14 my %params = @_;
760              
761 6         12 my $format = delete $params{format};
762 6         27 my @property_meta = $self->_shell_args_property_meta(%params);
763              
764 6         9 my @data;
765 6         7 my $max_name_length = 0;
766 6         11 for my $property_meta (@property_meta) {
767 11         36 my $param_name = $self->_shell_arg_name_from_property_meta($property_meta);
768 11 50       26 if ($property_meta->{shell_args_position}) {
769 0         0 $param_name = uc($param_name);
770             }
771              
772             #$param_name = "--$param_name";
773 11         26 my $doc = $property_meta->doc;
774 11         31 my $valid_values = $property_meta->valid_values;
775 11         26 my $example_values = $property_meta->example_values;
776 11 100       21 unless ($doc) {
777             # Maybe a parent class has documentation for this property
778 4         6 eval {
779 4         19 foreach my $ancestor_class_meta ( $property_meta->class_meta->ancestry_class_metas ) {
780 16         31 my $ancestor_property_meta = $ancestor_class_meta->property_meta_for_name($property_meta->property_name);
781 16 100 66     43 if ($ancestor_property_meta and $doc = $ancestor_property_meta->doc) {
782 1         2 last;
783             }
784             }
785             };
786             }
787              
788 11 100       26 if (!$doc) {
789 3 50       9 if (!$valid_values) {
790 3         7 $doc = "(undocumented)";
791             }
792             else {
793 0         0 $doc = '';
794             }
795             }
796 11 50       18 if ($valid_values) {
797 0         0 $doc .= "\nvalid values:\n";
798 0         0 for my $v (@$valid_values) {
799 0         0 $doc .= " " . $v . "\n";
800 0 0       0 $max_name_length = length($v)+2 if $max_name_length < length($v)+2;
801             }
802 0         0 chomp $doc;
803             }
804 11 100 66     50 if ($example_values && @$example_values) {
805 3   100     16 $doc .= "\nexample" . (@$example_values > 1 and 's') . ":\n";
806             $doc .= join(', ',
807 3 50       10 map { ref($_) ? Data::Dumper->new([$_])->Terse(1)->Dump() : $_ } @$example_values
  7         20  
808             );
809 3         9 chomp($doc);
810             }
811 11 100       25 $max_name_length = length($param_name) if $max_name_length < length($param_name);
812              
813 11   50     24 my $param_type = $property_meta->data_type || '';
814 11 50 33     54 if (defined($param_type) and $param_type !~ m/::/) {
815 11         26 $param_type = ucfirst(lc($param_type));
816             }
817              
818 11         14 my $default_value;
819 11 50 33     23 if (defined($default_value = $property_meta->default_value)
820             || defined(my $calculated_default = $property_meta->calculated_default)
821             ) {
822 0 0       0 unless (defined $default_value) {
823 0         0 $default_value = $calculated_default->()
824             }
825              
826 0 0 0     0 if ($param_type eq 'Boolean') {
    0          
827 0 0       0 $default_value = $default_value ? "'true'" : "'false' (--no$param_name)";
828             } elsif ($property_meta->is_many && ref($default_value) eq 'ARRAY') {
829 0 0       0 if (@$default_value) {
830 0         0 $default_value = "('" . join("','",@$default_value) . "')";
831             } else {
832 0         0 $default_value = "()";
833             }
834             } else {
835 0         0 $default_value = "'$default_value'";
836             }
837 0         0 $default_value = "\nDefault value $default_value if not specified";
838             }
839              
840 11         24 push @data, [$param_name, $param_type, $doc, $default_value];
841 11 100       32 if ($param_type eq 'Boolean') {
842 3         12 push @data, ['no'.$param_name, $param_type, "Make $param_name 'false'" ];
843             }
844             }
845 6         9 my $text = '';
846 6         11 for my $row (@data) {
847 14 50 33     1690 if (defined($format) and $format eq 'pod') {
    50 33        
848 0 0       0 $text .= "\n=item " . $row->[0] . ($row->[1]? ' I<' . $row->[1] . '>' : '') . "\n\n" . $row->[2] . "\n". ($row->[3]? $row->[3] . "\n" : '');
    0          
849             }
850             elsif (defined($format) and $format eq 'html') {
851 0 0       0 $text .= "\n\t
" . $row->[0] . ($row->[1]? ' ' . $row->[1] . '' : '') . "
" . $row->[2] . ($row->[3]? "
" . $row->[3] : '') . "
\n";
    0          
852             }
853             else {
854 14   50     58 $text .= sprintf(
855             " %s\n%s\n",
856             Term::ANSIColor::colored($row->[0], 'bold') . " " . $row->[1],
857             Text::Wrap::wrap(
858             " ", # 1st line indent,
859             " ", # all other lines indent,
860             $row->[2],
861             $row->[3] || '',
862             ),
863             );
864             }
865             }
866              
867 6         678 return $text;
868             }
869              
870             sub sorted_sub_command_classes {
871 15     15   81 no warnings;
  15         19  
  15         5792  
872 0     0 0 0 my @c = shift->sub_command_classes;
873              
874 0         0 my @commands_with_position = map { [ $_->sub_command_sort_position, $_ ] } @c;
  0         0  
875 0 0       0 my @sorted = sort { $a->[0] <=> $b->[0]
  0         0  
876             ||
877             $a->[0] cmp $b->[0]
878             }
879             @commands_with_position;
880 0         0 return map { $_->[1] } @sorted;
  0         0  
881             }
882              
883             sub sorted_sub_command_names {
884 0     0 0 0 my $class = shift;
885 0         0 my @sub_command_classes = $class->sorted_sub_command_classes;
886 0         0 my @sub_command_names = map { $_->command_name_brief } @sub_command_classes;
  0         0  
887 0         0 return @sub_command_names;
888             }
889              
890             sub sub_commands_table {
891 0     0 0 0 my $class = shift;
892 0         0 my @sub_command_names = $class->sorted_sub_command_names;
893              
894 0         0 my $max_length = 0;
895 0         0 for (@sub_command_names) {
896 0 0       0 $max_length = length($_) if ($max_length < length($_));
897             }
898 0   0     0 $max_length ||= 79;
899 0         0 my $col_spacer = '_'x$max_length;
900              
901 0         0 my $n_cols = floor(80/$max_length);
902 0         0 my $n_rows = ceil(@sub_command_names/$n_cols);
903 0         0 my @tb_rows;
904 0         0 for (my $i = 0; $i < @sub_command_names; $i += $n_cols) {
905 0         0 my $end = $i + $n_cols - 1;
906 0 0       0 $end = $#sub_command_names if ($end > $#sub_command_names);
907 0         0 push @tb_rows, [@sub_command_names[$i..$end]];
908             }
909 0         0 my @col_alignment;
910 0         0 for (my $i = 0; $i < $n_cols; $i++) {
911 0         0 push @col_alignment, { sample => "&$col_spacer" };
912             }
913 0         0 my $tb = Text::Table->new(@col_alignment);
914 0         0 $tb->load(@tb_rows);
915 0         0 return $tb;
916             }
917              
918             sub help_sub_commands {
919 0     0 0 0 my $class = shift;
920 0         0 my %params = @_;
921 0         0 my $command_name_method = 'command_name_brief';
922             #my $command_name_method = ($params{brief} ? 'command_name_brief' : 'command_name');
923              
924 0         0 my @sub_command_classes = $class->sorted_sub_command_classes;
925              
926 0         0 my %categories;
927             my @categories;
928 0         0 for my $sub_command_class (@sub_command_classes) {
929 0         0 my $category = $sub_command_class->sub_command_category;
930 0 0       0 $category = '' if not defined $category;
931 0 0       0 next if $sub_command_class->_is_hidden_in_docs();
932 0         0 my $sub_commands_within_category = $categories{$category};
933 0 0       0 unless ($sub_commands_within_category) {
934 0 0 0     0 if (defined $category and length $category) {
935 0         0 push @categories, $category;
936             }
937             else {
938 0         0 unshift @categories,'';
939             }
940 0         0 $sub_commands_within_category = $categories{$category} = [];
941             }
942 0         0 push @$sub_commands_within_category,$sub_command_class;
943             }
944              
945 15     15   71 no warnings;
  15         2187  
  15         29239  
946 0         0 local $Text::Wrap::columns = 60;
947              
948 0         0 my $full_text = '';
949 0         0 my @full_data;
950 0         0 for my $category (@categories) {
951 0         0 my $sub_commands_within_this_category = $categories{$category};
952             my @data = map {
953 0         0 my @rows = split("\n",Text::Wrap::wrap('', ' ', $_->help_brief));
  0         0  
954 0         0 chomp @rows;
955             (
956             [
957             $_->$command_name_method,
958             $_->_shell_args_usage_string_abbreviated,
959             $rows[0],
960             ],
961             map {
962 0         0 [
963 0         0 '',
964             ' ',
965             $rows[$_],
966             ]
967             } (1..$#rows)
968             );
969             }
970             @$sub_commands_within_this_category;
971              
972 0 0       0 if ($category) {
973             # add a space between categories
974 0 0       0 push @full_data, ['','',''] if @full_data;
975              
976 0 0       0 if ($category =~ /\D/) {
977             # non-numeric categories show their category as a header
978 0 0       0 $category .= ':' if $category =~ /\S/;
979 0         0 push @full_data,
980             [
981             Term::ANSIColor::colored(uc($category), 'blue'),
982             '',
983             ''
984             ];
985              
986             }
987             else {
988             # numeric categories just sort
989             }
990             }
991              
992 0         0 push @full_data, @data;
993             }
994              
995 0         0 my @max_width_found = (0,0,0);
996 0         0 for (@full_data) {
997 0         0 for my $c (0..2) {
998 0 0       0 $max_width_found[$c] = length($_->[$c]) if $max_width_found[$c] < length($_->[$c]);
999             }
1000             }
1001              
1002 0         0 my @colors = (qw/ red bold /);
1003 0         0 my $text = '';
1004 0         0 for my $row (@full_data) {
1005 0         0 for my $c (0..2) {
1006 0         0 $text .= ' ';
1007 0         0 $text .= Term::ANSIColor::colored($row->[$c], $colors[$c]),
1008             $text .= ' ';
1009 0         0 $text .= ' ' x ($max_width_found[$c]-length($row->[$c]));
1010             }
1011 0         0 $text .= "\n";
1012             }
1013 0         0 return $text;
1014             }
1015              
1016 0     0   0 sub _is_hidden_in_docs { return; }
1017              
1018             #
1019             # Methods which transform command properties into shell args (getopt)
1020             #
1021              
1022             sub _shell_args_property_meta {
1023 101     101   132 my $self = shift;
1024 101         325 my $class_meta = $self->__meta__;
1025              
1026             # Find which property metas match the rules. We have to do it this way
1027             # because just calling 'get_all_property_metas()' will product multiple matches
1028             # if a property is overridden in a child class
1029 101         455 my $rule = UR::Object::Property->define_boolexpr(@_);
1030 101         127 my %seen;
1031 101         134 my (@positional,@required,@optional);
1032 101         693 foreach my $property_meta ( $class_meta->get_all_property_metas() ) {
1033 1264         1951 my $property_name = $property_meta->property_name;
1034              
1035 1264 100       2424 next if $seen{$property_name}++;
1036 1150 100       1874 next unless $rule->evaluate($property_meta);
1037              
1038 1127 100       1669 next if $property_name eq 'id';
1039 1029 100       1295 next if $property_name eq 'result';
1040 931 100       1199 next if $property_name eq 'is_executed';
1041 833 100       1006 next if $property_name eq 'original_command_line';
1042 735 100       1105 next if $property_name =~ /^_/;
1043 733 50 66     1292 next if defined($property_meta->data_type) and $property_meta->data_type =~ /::/;
1044 733 50       1198 next if not $property_meta->is_mutable;
1045 733 50       1045 next if $property_meta->is_delegated;
1046 733 100       1078 next if $property_meta->is_calculated;
1047             # 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
1048 561 50       926 next if $property_meta->is_transient;
1049 561 50       819 next if $property_meta->is_constant;
1050 561 100       1180 if ($property_meta->{shell_args_position}) {
    100          
1051 42         64 push @positional, $property_meta;
1052             }
1053             elsif ($property_meta->is_optional) {
1054 382         488 push @optional, $property_meta;
1055             }
1056             else {
1057 137         213 push @required, $property_meta;
1058             }
1059             }
1060              
1061 101         140 my @result;
1062 101         176 @required = map { [ $_->property_name, $_ ] } @required;
  137         241  
1063 101         176 @optional = map { [ $_->property_name, $_ ] } @optional;
  382         537  
1064 101         187 @positional = map { [ $_->{shell_args_position}, $_ ] } @positional;
  42         104  
1065              
1066             @result = (
1067 79         201 (sort { $a->[0] cmp $b->[0] } @required),
1068 471         519 (sort { $a->[0] cmp $b->[0] } @optional),
1069 101         451 (sort { $a->[0] <=> $b->[0] } @positional),
  3         6  
1070             );
1071              
1072 101         129 return map { $_->[1] } @result;
  561         851  
1073             }
1074              
1075             sub _shell_arg_name_from_property_meta {
1076 360     360   290 my ($self, $property_meta,$singularize) = @_;
1077 360 50       709 my $property_name = ($singularize ? $property_meta->singular_name : $property_meta->property_name);
1078 360         291 my $param_name = $property_name;
1079 360         595 $param_name =~ s/_/-/g;
1080 360         437 return $param_name;
1081             }
1082              
1083             sub _shell_arg_getopt_qualifier_from_property_meta {
1084 338     338   266 my ($self, $property_meta) = @_;
1085              
1086 338 100       490 my $many = ($property_meta->is_many ? '@' : '');
1087 338 100 100     478 if (defined($property_meta->data_type) and $property_meta->data_type =~ /Boolean/) {
1088 119         326 return '!' . $many;
1089             }
1090             #elsif ($property_meta->is_optional) {
1091             # return ':s' . $many;
1092             #}
1093             else {
1094 219         555 return '=s' . $many;
1095             }
1096             }
1097              
1098             sub _shell_arg_usage_string_from_property_meta {
1099 11     11   14 my ($self, $property_meta) = @_;
1100 11         19 my $string = $self->_shell_arg_name_from_property_meta($property_meta);
1101 11 50       20 if ($property_meta->{shell_args_position}) {
1102 0         0 $string = uc($string);
1103             }
1104              
1105 11 50       16 if ($property_meta->{shell_args_position}) {
1106 0 0       0 if ($property_meta->is_optional) {
1107 0         0 $string = "[$string]";
1108             }
1109             }
1110             else {
1111 11         14 $string = "--$string";
1112 11 100 66     20 if (defined($property_meta->data_type) and $property_meta->data_type =~ /Boolean/) {
1113 3         5 $string = "[$string]";
1114             }
1115             else {
1116 8 50       17 if ($property_meta->is_many) {
1117 0         0 $string .= "=?[,?]";
1118             }
1119             else {
1120 8         11 $string .= '=?';
1121             }
1122 8 100       13 if ($property_meta->is_optional) {
1123 1         4 $string = "[$string]";
1124             }
1125             }
1126             }
1127 11         46 return $string;
1128             }
1129              
1130             sub _shell_arg_getopt_specification_from_property_meta {
1131 149     149   143 my ($self,$property_meta) = @_;
1132 149         218 my $arg_name = $self->_shell_arg_name_from_property_meta($property_meta);
1133             return (
1134 149 50       235 $arg_name . $self->_shell_arg_getopt_qualifier_from_property_meta($property_meta),
1135             ($property_meta->is_many ? ($arg_name => []) : ())
1136             );
1137             }
1138              
1139              
1140             sub _shell_arg_getopt_complete_specification_from_property_meta {
1141 189     189   178 my ($self,$property_meta) = @_;
1142 189         336 my $arg_name = $self->_shell_arg_name_from_property_meta($property_meta);
1143 189         287 my $completions = $property_meta->valid_values;
1144 189 50       198 if ($completions) {
1145 0 0       0 if (ref($completions) eq 'ARRAY') {
1146 0         0 $completions = [ @$completions ];
1147             }
1148             }
1149             else {
1150 189         250 my $type = $property_meta->data_type;
1151 189         301 my @complete_as_files = (
1152             'File','FilePath','Filesystem','FileSystem','FilesystemPath','FileSystemPath',
1153             'Text','String',
1154             );
1155 189         226 my @complete_as_directories = (
1156             'Directory','DirectoryPath','Dir','DirPath',
1157             );
1158 189 100       218 if (!defined($type)) {
1159 21         25 $completions = 'files';
1160             }
1161             else {
1162 168         133 for my $pattern (@complete_as_files) {
1163 1326 100 66     3113 if (!$type || $type eq $pattern) {
1164 71         62 $completions = 'files';
1165 71         51 last;
1166             }
1167             }
1168 168         123 for my $pattern (@complete_as_directories) {
1169 672 50 33     1682 if ( $type && $type eq $pattern) {
1170 0         0 $completions = 'directories';
1171 0         0 last;
1172             }
1173             }
1174             }
1175             }
1176             return (
1177 189         338 $arg_name . $self->_shell_arg_getopt_qualifier_from_property_meta($property_meta),
1178             $completions,
1179             # ($property_meta->is_many ? ($arg_name => []) : ())
1180             );
1181             }
1182              
1183             sub _shell_args_getopt_specification {
1184 25     25   49 my $self = shift;
1185 25         30 my @getopt;
1186             my @params;
1187 25         108 for my $meta ($self->_shell_args_property_meta) {
1188 149         307 my ($spec, @params_addition) = $self->_shell_arg_getopt_specification_from_property_meta($meta);
1189 149         146 push @getopt,$spec;
1190 149         163 push @params, @params_addition;
1191             }
1192 25         83 @getopt = sort @getopt;
1193 25         114 return { @params}, @getopt;
1194             }
1195              
1196             sub _shell_args_getopt_complete_specification {
1197 31     31   40 my $self = shift;
1198 31         32 my @getopt;
1199 31         153 for my $meta ($self->_shell_args_property_meta) {
1200 189         448 my ($spec, $completions) = $self->_shell_arg_getopt_complete_specification_from_property_meta($meta);
1201 189         228 push @getopt, $spec, $completions;
1202             }
1203 31         131 return @getopt;
1204             }
1205              
1206             sub _shell_args_usage_string {
1207 3     3   51 my $self = shift;
1208 3 50       11 if ($self->is_executable) {
    0          
1209             return join(
1210             " ",
1211             map {
1212 3         11 $self->_shell_arg_usage_string_from_property_meta($_)
  11         36  
1213             } $self->_shell_args_property_meta()
1214              
1215             );
1216             }
1217             elsif ($self->is_sub_command_delegator) {
1218 0         0 my @names = $self->sub_command_names;
1219 0         0 return "[" . join("|",@names) . "] ..."
1220             }
1221             else {
1222 0         0 return "(no execute or sub commands implemented)"
1223             }
1224 0         0 return "";
1225             }
1226              
1227             sub _shell_args_usage_string_abbreviated {
1228 0     0   0 my $self = shift;
1229 0 0       0 if ($self->is_sub_command_delegator) {
1230 0         0 return "...";
1231             }
1232             else {
1233 0         0 my $detailed = $self->_shell_args_usage_string;
1234 0 0       0 if (length($detailed) <= 20) {
1235 0         0 return $detailed;
1236             }
1237             else {
1238 0         0 return substr($detailed,0,17) . '...';
1239             }
1240             }
1241             }
1242              
1243             #
1244             # The following methods build allow a command to determine its
1245             # sub-commands, if there are any.
1246             #
1247              
1248             # This is for cases in which the Foo::Bar command delegates to
1249             # Foo::Bar::Baz, Foo::Bar::Buz or Foo::Bar::Doh, depending on its paramters.
1250              
1251             sub sub_command_dirs {
1252 119     119 0 133 my $class = shift;
1253 119   33     429 my $module = ref($class) || $class;
1254 119         468 $module =~ s/::/\//g;
1255              
1256             # multiple dirs is not working quite yet
1257             #my @paths = grep { -d $_ } map { "$_/$module" } @INC;
1258             #return @paths;
1259              
1260 119         160 $module .= '.pm';
1261 119         214 my $path = $INC{$module};
1262 119 100       217 unless ($path) {
1263 29         85 return;
1264             }
1265 90         238 $path =~ s/.pm$//;
1266 90 100       1737 unless (-d $path) {
1267 61         130 return;
1268             }
1269 29         118 return $path;
1270             }
1271              
1272             sub sub_command_classes {
1273 9     9 0 13 my $class = shift;
1274 9         17 my @paths = $class->sub_command_dirs;
1275 9 50       20 return unless @paths;
1276             @paths =
1277 56         118 grep { s/\.pm$// }
1278 9         1502 map { glob("$_/*") }
1279 9         95 grep { -d $_ }
1280 9 50       18 grep { defined($_) and length($_) }
  9         41  
1281             @paths;
1282 9 50       28 return unless @paths;
1283             my @classes =
1284             grep {
1285 46 100       299 ($_->is_sub_command_delegator or !$_->__meta__->is_abstract)
1286             }
1287 46 50       285 grep { $_ and $_->isa('Command') }
1288 46         217 map { $class->class_for_sub_command($_) }
1289 46         40 map { s/_/-/g; $_ }
  46         46  
1290 9         15 map { basename($_) }
  46         1030  
1291             @paths;
1292 9         59 return @classes;
1293             }
1294              
1295             sub sub_command_names {
1296 9     9 0 19 my $class = shift;
1297 9         44 my @sub_command_classes = $class->sub_command_classes;
1298 9         20 my @sub_command_names = map { $_->command_name_brief } @sub_command_classes;
  43         207  
1299 9         52 return @sub_command_names;
1300             }
1301              
1302             sub class_for_sub_command {
1303 92     92 0 108 my $self = shift;
1304 92   33     277 my $class = ref($self) || $self;
1305 92         88 my $sub_command = shift;
1306              
1307 92 50       180 return if $sub_command =~ /^\-/;
1308              
1309 92         187 my $sub_class = join("", map { ucfirst($_) } split(/-/, $sub_command));
  112         276  
1310 92         189 $sub_class = $class . "::" . $sub_class;
1311              
1312 92         263 my $meta = UR::Object::Type->get($sub_class); # allow in memory classes
1313 92 50       587 unless ( $meta ) {
    50          
1314 0           eval "use $sub_class;";
1315 0 0         if ($@) {
1316 0 0         if ($@ =~ /^Can't locate .*\.pm in \@INC/) {
1317             #die "Failed to find $sub_class! $class_for_sub_command.pm!\n$@";
1318 0           return;
1319             }
1320             else {
1321 0           my @msg = split("\n",$@);
1322 0           pop @msg;
1323 0           pop @msg;
1324 0           $self->error_message("$sub_class failed to compile!:\n@msg\n\n");
1325 0           return;
1326             }
1327             }
1328             }
1329 0         0 elsif (my $isa = $sub_class->isa("Command")) {
1330 92 50       172 if (ref($isa)) {
1331             # dumb modules (Test::Class) mess with the standard isa() API
1332 0 0       0 if ($sub_class->SUPER::isa("Command")) {
1333 0         0 return $sub_class;
1334             }
1335             else {
1336 0         0 return;
1337             }
1338             }
1339 92         272 return $sub_class;
1340             }
1341             else {
1342 0           return;
1343             }
1344             }
1345              
1346             # Run the given command-line with stdout and stderr redirected to /dev/null
1347             sub system_inhibit_std_out_err {
1348 0     0 0   my($self,$cmdline) = @_;
1349              
1350 0 0         open my $oldout, ">&STDOUT" or die "Can't dup STDOUT: $!";
1351 0 0         open my $olderr, ">&", \*STDERR or die "Can't dup STDERR: $!";
1352              
1353 0           open(STDOUT,'>/dev/null');
1354 0           open(STDERR,'>/dev/null');
1355              
1356 0           my $ec = system ( $cmdline );
1357              
1358 0 0         open STDOUT, ">&", $oldout or die "Can't dup \$oldout: $!";
1359 0 0         open STDERR, ">&", $olderr or die "Can't dup \$olderr: $!";
1360              
1361 0           return $ec;
1362             }
1363              
1364             sub parent_command_class {
1365 0     0 0   my $class = shift;
1366 0 0         $class = ref($class) if ref($class);
1367 0           my @components = split("::", $class);
1368 0 0         return if @components == 1;
1369 0           my $parent = join("::", @components[0..$#components-1]);
1370 0 0         return $parent if $parent->can("command_name");
1371 0           return;
1372             }
1373              
1374              
1375             1;
1376              
1377             __END__