| 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__ |