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