lib/Command/View/DocMethods.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 132 | 308 | 42.8 |
branch | 25 | 130 | 19.2 |
condition | 15 | 78 | 19.2 |
subroutine | 22 | 39 | 56.4 |
pod | 0 | 18 | 0.0 |
total | 194 | 573 | 33.8 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Command::V2; # additional methods to produce documentation, TODO: turn into a real view | ||||||
2 | 9 | 9 | 189 | use strict; | |||
9 | 13 | ||||||
9 | 235 | ||||||
3 | 9 | 9 | 32 | use warnings; | |||
9 | 12 | ||||||
9 | 219 | ||||||
4 | |||||||
5 | 9 | 9 | 2584 | use Term::ANSIColor qw(); | |||
9 | 21649 | ||||||
9 | 182 | ||||||
6 | 9 | 9 | 3993 | use Pod::Simple::Text; | |||
9 | 221670 | ||||||
9 | 95 | ||||||
7 | require Text::Wrap; | ||||||
8 | |||||||
9 | # This is changed with "local" where used in some places | ||||||
10 | $Text::Wrap::columns = 100; | ||||||
11 | |||||||
12 | # Required for color output | ||||||
13 | eval { | ||||||
14 | binmode STDOUT, ":utf8"; | ||||||
15 | binmode STDERR, ":utf8"; | ||||||
16 | }; | ||||||
17 | |||||||
18 | sub help_brief { | ||||||
19 | 2 | 2 | 0 | 4 | my $self = shift; | ||
20 | 2 | 50 | 5 | if (my $doc = $self->__meta__->doc) { | |||
21 | 2 | 8 | return $doc; | ||||
22 | } | ||||||
23 | else { | ||||||
24 | 0 | 0 | my @parents = $self->__meta__->ancestry_class_metas; | ||||
25 | 0 | 0 | for my $parent (@parents) { | ||||
26 | 0 | 0 | 0 | if (my $doc = $parent->doc) { | |||
27 | 0 | 0 | return $doc; | ||||
28 | } | ||||||
29 | } | ||||||
30 | 0 | 0 | return "no description!!!: define 'doc' in the class definition for " | ||||
31 | . $self->class; | ||||||
32 | } | ||||||
33 | } | ||||||
34 | |||||||
35 | sub help_synopsis { | ||||||
36 | 1 | 1 | 0 | 1 | my $self = shift; | ||
37 | 1 | 3 | return ''; | ||||
38 | } | ||||||
39 | |||||||
40 | sub help_detail { | ||||||
41 | 1 | 1 | 0 | 19 | my $self = shift; | ||
42 | 1 | 33 | 7 | return "!!! define help_detail() in module " . ref($self) || $self . "!"; | |||
43 | } | ||||||
44 | |||||||
45 | sub sub_command_category { | ||||||
46 | 2 | 2 | 0 | 12 | return; | ||
47 | } | ||||||
48 | |||||||
49 | sub sub_command_sort_position { | ||||||
50 | # override to do something besides alpha sorting by name | ||||||
51 | 2 | 2 | 0 | 10 | return '9999999999 ' . $_[0]->command_name_brief; | ||
52 | } | ||||||
53 | |||||||
54 | # LEGACY: poorly named | ||||||
55 | sub help_usage_command_pod { | ||||||
56 | 0 | 0 | 0 | 0 | return shift->doc_manual(@_); | ||
57 | } | ||||||
58 | |||||||
59 | # LEGACY: poorly named | ||||||
60 | sub help_usage_complete_text { | ||||||
61 | 2 | 2 | 0 | 24 | shift->doc_help(@_) | ||
62 | } | ||||||
63 | |||||||
64 | sub doc_help { | ||||||
65 | 1 | 1 | 0 | 2 | my $self = shift; | ||
66 | |||||||
67 | 1 | 7 | my $command_name = $self->command_name; | ||||
68 | 1 | 1 | my $text; | ||||
69 | |||||||
70 | 1 | 3 | my $extra_help = ''; | ||||
71 | 1 | 5 | my @extra_help = $self->_additional_help_sections; | ||||
72 | 1 | 3 | while (@extra_help) { | ||||
73 | 0 | 0 | 0 | my $title = shift @extra_help || ''; | |||
74 | 0 | 0 | 0 | my $content = shift @extra_help || ''; | |||
75 | 0 | 0 | $extra_help .= sprintf( | ||||
76 | "%s\n\n%s\n", | ||||||
77 | Term::ANSIColor::colored($title, 'underline'), | ||||||
78 | _pod2txt($content) | ||||||
79 | ), | ||||||
80 | } | ||||||
81 | |||||||
82 | # standard: update this to do the old --help format | ||||||
83 | 1 | 7 | my $synopsis = $self->help_synopsis; | ||||
84 | 1 | 5 | my $required_inputs = $self->help_options(is_optional => 0, is_input => 1); | ||||
85 | 1 | 3 | my $required_outputs = $self->help_options(is_optional => 0, is_output => 1); | ||||
86 | 1 | 4 | my $required_params = $self->help_options(is_optional => 0, is_param => 1); | ||||
87 | 1 | 4 | my $optional_inputs = $self->help_options(is_optional => 1, is_input => 1); | ||||
88 | 1 | 3 | my $optional_outputs = $self->help_options(is_optional => 1, is_output => 1); | ||||
89 | 1 | 4 | my $optional_params = $self->help_options(is_optional => 1, is_param => 1); | ||||
90 | 1 | 2 | my @parts; | ||||
91 | |||||||
92 | 1 | 4 | push @parts, Term::ANSIColor::colored('USAGE', 'underline'); | ||||
93 | 1 | 50 | 23 | push @parts, | |||
94 | Text::Wrap::wrap( | ||||||
95 | ' ', | ||||||
96 | ' ', | ||||||
97 | Term::ANSIColor::colored($self->command_name, 'bold'), | ||||||
98 | $self->_shell_args_usage_string || '', | ||||||
99 | ); | ||||||
100 | |||||||
101 | 1 | 50 | 141 | push @parts, | |||
102 | ( $synopsis | ||||||
103 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("SYNOPSIS", 'underline'), $synopsis) | ||||||
104 | : '' | ||||||
105 | ); | ||||||
106 | 1 | 50 | 5 | push @parts, | |||
107 | ( $required_inputs | ||||||
108 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("REQUIRED INPUTS", 'underline'), $required_inputs) | ||||||
109 | : '' | ||||||
110 | ); | ||||||
111 | 1 | 50 | 6 | push @parts, | |||
112 | ( $required_params | ||||||
113 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("REQUIRED PARAMS", 'underline'), $required_params) | ||||||
114 | : '' | ||||||
115 | ); | ||||||
116 | 1 | 50 | 23 | push @parts, | |||
117 | ( $optional_inputs | ||||||
118 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("OPTIONAL INPUTS", 'underline'), $optional_inputs) | ||||||
119 | : '' | ||||||
120 | ); | ||||||
121 | 1 | 50 | 3 | push @parts, | |||
122 | ( $optional_params | ||||||
123 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("OPTIONAL PARAMS", 'underline'), $optional_params) | ||||||
124 | : '' | ||||||
125 | ); | ||||||
126 | 1 | 50 | 3 | push @parts, | |||
127 | ( $required_outputs | ||||||
128 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("REQUIRED OUTPUTS", 'underline'), $required_outputs) | ||||||
129 | : '' | ||||||
130 | ); | ||||||
131 | 1 | 50 | 3 | push @parts, | |||
132 | ( $optional_outputs | ||||||
133 | ? sprintf("%s\n%s\n", Term::ANSIColor::colored("OPTIONAL OUTPUTS", 'underline'), $optional_outputs) | ||||||
134 | : '' | ||||||
135 | ); | ||||||
136 | 1 | 50 | 3 | push @parts, | |||
137 | sprintf( | ||||||
138 | "%s\n%s\n", | ||||||
139 | Term::ANSIColor::colored("DESCRIPTION", 'underline'), | ||||||
140 | _pod2txt($self->help_detail || '') | ||||||
141 | ); | ||||||
142 | 1 | 50 | 41 | push @parts, | |||
143 | ( $extra_help ? $extra_help : '' ); | ||||||
144 | |||||||
145 | 1 | 4 | $text = sprintf( | ||||
146 | "\n%s\n%s\n\n%s%s%s%s%s%s%s%s%s\n", | ||||||
147 | @parts | ||||||
148 | ); | ||||||
149 | |||||||
150 | 1 | 5 | return $text; | ||||
151 | } | ||||||
152 | |||||||
153 | sub parent_command_class { | ||||||
154 | 0 | 0 | 0 | 0 | my $class = shift; | ||
155 | 0 | 0 | 0 | $class = ref($class) if ref($class); | |||
156 | 0 | 0 | my @components = split("::", $class); | ||||
157 | 0 | 0 | 0 | return if @components == 1; | |||
158 | 0 | 0 | my $parent = join("::", @components[0..$#components-1]); | ||||
159 | 0 | 0 | 0 | return $parent if $parent->can("command_name"); | |||
160 | 0 | 0 | return; | ||||
161 | } | ||||||
162 | |||||||
163 | sub doc_sections { | ||||||
164 | 0 | 0 | 0 | 0 | my $self = shift; | ||
165 | 0 | 0 | my @sections; | ||||
166 | |||||||
167 | 0 | 0 | my $command_name = $self->command_name; | ||||
168 | |||||||
169 | 9 | 9 | 6048 | my $version = do { no strict; ${ $self->class . '::VERSION' } }; | |||
9 | 14 | ||||||
9 | 6115 | ||||||
0 | 0 | ||||||
0 | 0 | ||||||
0 | 0 | ||||||
170 | 0 | 0 | my $help_brief = $self->help_brief; | ||||
171 | 0 | 0 | my $datetime = $self->__context__->now; | ||||
172 | 0 | 0 | my ($date,$time) = split(' ',$datetime); | ||||
173 | |||||||
174 | 0 | 0 | 0 | push(@sections, UR::Doc::Section->create( | |||
175 | title => "NAME", | ||||||
176 | content => "$command_name" . ($help_brief ? " - $help_brief" : ""), | ||||||
177 | format => "pod", | ||||||
178 | )); | ||||||
179 | |||||||
180 | 0 | 0 | 0 | push(@sections, UR::Doc::Section->create( | |||
181 | title => "VERSION", | ||||||
182 | content => "This document " # separated to trick the version updater | ||||||
183 | . "describes $command_name " | ||||||
184 | . ($version ? "version $version " : "") | ||||||
185 | . "($date at $time)", | ||||||
186 | format => "pod", | ||||||
187 | )); | ||||||
188 | |||||||
189 | 0 | 0 | my $synopsis = $self->command_name . ' ' . $self->_shell_args_usage_string . "\n\n" . $self->help_synopsis; | ||||
190 | 0 | 0 | 0 | if ($synopsis) { | |||
191 | 0 | 0 | push(@sections, UR::Doc::Section->create( | ||||
192 | title => "SYNOPSIS", | ||||||
193 | content => $synopsis, | ||||||
194 | format => 'pod' | ||||||
195 | )); | ||||||
196 | } | ||||||
197 | |||||||
198 | 0 | 0 | my $required_args = $self->help_options(is_optional => 0, format => "pod"); | ||||
199 | 0 | 0 | 0 | if ($required_args) { | |||
200 | 0 | 0 | push(@sections, UR::Doc::Section->create( | ||||
201 | title => "REQUIRED ARGUMENTS", | ||||||
202 | content => "=over\n\n$required_args\n\n=back\n\n", | ||||||
203 | format => 'pod' | ||||||
204 | )); | ||||||
205 | } | ||||||
206 | |||||||
207 | 0 | 0 | my $optional_args = $self->help_options(is_optional => 1, format => "pod"); | ||||
208 | 0 | 0 | 0 | if ($optional_args) { | |||
209 | 0 | 0 | push(@sections, UR::Doc::Section->create( | ||||
210 | title => "OPTIONAL ARGUMENTS", | ||||||
211 | content => "=over\n\n$optional_args\n\n=back\n\n", | ||||||
212 | format => 'pod' | ||||||
213 | )); | ||||||
214 | } | ||||||
215 | |||||||
216 | 0 | 0 | 0 | my $manual = $self->_doc_manual_body || $self->help_detail; | |||
217 | 0 | 0 | push(@sections, UR::Doc::Section->create( | ||||
218 | title => "DESCRIPTION", | ||||||
219 | content => $manual, | ||||||
220 | format => 'pod', | ||||||
221 | )); | ||||||
222 | |||||||
223 | 0 | 0 | my @extra_help = $self->_additional_help_sections; | ||||
224 | 0 | 0 | while (@extra_help) { | ||||
225 | 0 | 0 | 0 | my $title = shift @extra_help || ''; | |||
226 | 0 | 0 | 0 | my $content = shift @extra_help || ''; | |||
227 | 0 | 0 | push (@sections, UR::Doc::Section->create( | ||||
228 | title => $title, | ||||||
229 | content => $content, | ||||||
230 | format => 'pod' | ||||||
231 | )); | ||||||
232 | } | ||||||
233 | |||||||
234 | 0 | 0 | 0 | if ($self->can("doc_sub_commands")) { | |||
235 | 0 | 0 | my $sub_commands = $self->doc_sub_commands(brief => 1); | ||||
236 | 0 | 0 | 0 | if ($sub_commands) { | |||
237 | 0 | 0 | push(@sections, UR::Doc::Section->create( | ||||
238 | title => "SUB-COMMANDS", | ||||||
239 | content => $sub_commands, | ||||||
240 | format => "pod", | ||||||
241 | )); | ||||||
242 | } | ||||||
243 | } | ||||||
244 | |||||||
245 | 0 | 0 | my @footer_section_methods = ( | ||||
246 | 'LICENSE' => '_doc_license', | ||||||
247 | 'AUTHORS' => '_doc_authors', | ||||||
248 | 'CREDITS' => '_doc_credits', | ||||||
249 | 'BUGS' => '_doc_bugs', | ||||||
250 | 'SEE ALSO' => '_doc_see_also' | ||||||
251 | ); | ||||||
252 | |||||||
253 | 0 | 0 | while (@footer_section_methods) { | ||||
254 | 0 | 0 | my $header = shift @footer_section_methods; | ||||
255 | 0 | 0 | my $method = shift @footer_section_methods; | ||||
256 | 0 | 0 | my @txt = $self->$method; | ||||
257 | 0 | 0 | 0 | 0 | next if (@txt == 0 or (@txt == 1 and not $txt[0])); | ||
0 | |||||||
258 | 0 | 0 | my $content; | ||||
259 | 0 | 0 | 0 | if (@txt == 1) { | |||
260 | 0 | 0 | $content = $txt[0]; | ||||
261 | } else { | ||||||
262 | 0 | 0 | $content = join("\n", @txt); | ||||
263 | } | ||||||
264 | |||||||
265 | 0 | 0 | push(@sections, UR::Doc::Section->create( | ||||
266 | title => $header, | ||||||
267 | content => $content, | ||||||
268 | format => "pod", | ||||||
269 | )); | ||||||
270 | } | ||||||
271 | |||||||
272 | 0 | 0 | return @sections; | ||||
273 | } | ||||||
274 | |||||||
275 | sub doc_sub_commands { | ||||||
276 | 0 | 0 | 0 | 0 | my $self = shift; | ||
277 | 0 | 0 | return; | ||||
278 | } | ||||||
279 | |||||||
280 | sub doc_manual { | ||||||
281 | 0 | 0 | 0 | 0 | my $self = shift; | ||
282 | 0 | 0 | my $pod = $self->_doc_name_version; | ||||
283 | |||||||
284 | 0 | 0 | my $synopsis = $self->command_name . ' ' . $self->_shell_args_usage_string . "\n\n" . $self->help_synopsis; | ||||
285 | 0 | 0 | my $required_args = $self->help_options(is_optional => 0, format => "pod"); | ||||
286 | 0 | 0 | my $optional_args = $self->help_options(is_optional => 1, format => "pod"); | ||||
287 | 0 | 0 | 0 | $pod .= | |||
0 | |||||||
0 | |||||||
288 | ( | ||||||
289 | $synopsis | ||||||
290 | ? "=head1 SYNOPSIS\n\n" . $synopsis . "\n\n" | ||||||
291 | : '' | ||||||
292 | ) | ||||||
293 | . ( | ||||||
294 | $required_args | ||||||
295 | ? "=head1 REQUIRED ARGUMENTS\n\n=over\n\n" . $required_args . "\n\n=back\n\n" | ||||||
296 | : '' | ||||||
297 | ) | ||||||
298 | . ( | ||||||
299 | $optional_args | ||||||
300 | ? "=head1 OPTIONAL ARGUMENTS\n\n=over\n\n" . $optional_args . "\n\n=back\n\n" | ||||||
301 | : '' | ||||||
302 | ); | ||||||
303 | |||||||
304 | 0 | 0 | my $manual = $self->_doc_manual_body; | ||||
305 | 0 | 0 | my $help = $self->help_detail; | ||||
306 | 0 | 0 | 0 | 0 | if ($manual or $help) { | ||
307 | 0 | 0 | $pod .= "=head1 DESCRIPTION:\n\n"; | ||||
308 | |||||||
309 | 0 | 0 | 0 | my $txt = $manual || $help; | |||
310 | 0 | 0 | 0 | if ($txt =~ /^\=/) { | |||
311 | # pure POD | ||||||
312 | 0 | 0 | $pod .= $manual; | ||||
313 | } | ||||||
314 | else { | ||||||
315 | 0 | 0 | $txt =~ s/\n/\n\n/g; | ||||
316 | 0 | 0 | $pod .= $txt; | ||||
317 | #$pod .= join('', map { " $_\n" } split ("\n",$txt)) . "\n"; | ||||||
318 | } | ||||||
319 | } | ||||||
320 | |||||||
321 | 0 | 0 | $pod .= $self->_doc_footer(); | ||||
322 | 0 | 0 | $pod .= "\n\n=cut\n\n"; | ||||
323 | 0 | 0 | return "\n$pod"; | ||||
324 | } | ||||||
325 | |||||||
326 | |||||||
327 | sub _doc_name_version { | ||||||
328 | 0 | 0 | 0 | my $self = shift; | |||
329 | |||||||
330 | 0 | 0 | my $command_name = $self->command_name; | ||||
331 | 0 | 0 | my $pod; | ||||
332 | |||||||
333 | # standard: update this to do the old --help format | ||||||
334 | 0 | 0 | my $synopsis = $self->command_name . ' ' . $self->_shell_args_usage_string . "\n\n" . $self->help_synopsis; | ||||
335 | 0 | 0 | my $help_brief = $self->help_brief; | ||||
336 | 9 | 9 | 47 | my $version = do { no strict; ${ $self->class . '::VERSION' } }; | |||
9 | 14 | ||||||
9 | 9304 | ||||||
0 | 0 | ||||||
0 | 0 | ||||||
0 | 0 | ||||||
337 | 0 | 0 | my $datetime = $self->__context__->now; | ||||
338 | 0 | 0 | my ($date,$time) = split(' ',$datetime); | ||||
339 | |||||||
340 | 0 | 0 | 0 | $pod = | |||
341 | "\n=pod" | ||||||
342 | . "\n\n=head1 NAME" | ||||||
343 | . "\n\n" | ||||||
344 | . $self->command_name | ||||||
345 | . ($help_brief ? " - " . $self->help_brief : '') | ||||||
346 | . "\n\n"; | ||||||
347 | |||||||
348 | 0 | 0 | $pod .= | ||||
349 | "\n\n=head1 VERSION" | ||||||
350 | . "\n\n" | ||||||
351 | . "This document " # separated to trick the version updater | ||||||
352 | . "describes " . $self->command_name; | ||||||
353 | |||||||
354 | 0 | 0 | 0 | if ($version) { | |||
355 | 0 | 0 | $pod .= " version " . $version . " ($date at $time).\n\n"; | ||||
356 | } | ||||||
357 | else { | ||||||
358 | 0 | 0 | $pod .= " ($date at $time)\n\n"; | ||||
359 | } | ||||||
360 | |||||||
361 | 0 | 0 | return $pod; | ||||
362 | } | ||||||
363 | |||||||
364 | sub _doc_manual_body { | ||||||
365 | 0 | 0 | 0 | return ''; | |||
366 | } | ||||||
367 | |||||||
368 | sub help_header { | ||||||
369 | 0 | 0 | 0 | 0 | my $class = shift; | ||
370 | 0 | 0 | return sprintf("%s - %-80s\n", | ||||
371 | $class->command_name | ||||||
372 | ,$class->help_brief | ||||||
373 | ) | ||||||
374 | } | ||||||
375 | |||||||
376 | sub help_options { | ||||||
377 | 6 | 6 | 0 | 5 | my $self = shift; | ||
378 | 6 | 11 | my %params = @_; | ||||
379 | |||||||
380 | 6 | 7 | my $format = delete $params{format}; | ||||
381 | 6 | 24 | my @property_meta = $self->_shell_args_property_meta(%params); | ||||
382 | |||||||
383 | 6 | 7 | my @data; | ||||
384 | 6 | 6 | my $max_name_length = 0; | ||||
385 | 6 | 8 | for my $property_meta (@property_meta) { | ||||
386 | 1 | 11 | my $param_name = $self->_shell_arg_name_from_property_meta($property_meta); | ||||
387 | 1 | 50 | 3 | if ($property_meta->{shell_args_position}) { | |||
388 | 0 | 0 | $param_name = uc($param_name); | ||||
389 | } | ||||||
390 | |||||||
391 | #$param_name = "--$param_name"; | ||||||
392 | 1 | 4 | my $doc = $property_meta->doc; | ||||
393 | 1 | 3 | my $valid_values = $property_meta->valid_values; | ||||
394 | 1 | 3 | my $example_values = $property_meta->example_values; | ||||
395 | 1 | 50 | 3 | unless ($doc) { | |||
396 | # Maybe a parent class has documentation for this property | ||||||
397 | 0 | 0 | eval { | ||||
398 | 0 | 0 | foreach my $ancestor_class_meta ( $property_meta->class_meta->ancestry_class_metas ) { | ||||
399 | 0 | 0 | my $ancestor_property_meta = $ancestor_class_meta->property_meta_for_name($property_meta->property_name); | ||||
400 | 0 | 0 | 0 | 0 | if ($ancestor_property_meta and $doc = $ancestor_property_meta->doc) { | ||
401 | 0 | 0 | last; | ||||
402 | } | ||||||
403 | } | ||||||
404 | }; | ||||||
405 | } | ||||||
406 | |||||||
407 | 1 | 50 | 3 | if (!$doc) { | |||
408 | 0 | 0 | 0 | if (!$valid_values) { | |||
409 | 0 | 0 | $doc = "(undocumented)"; | ||||
410 | } | ||||||
411 | else { | ||||||
412 | 0 | 0 | $doc = ''; | ||||
413 | } | ||||||
414 | } | ||||||
415 | 1 | 50 | 4 | if ($valid_values) { | |||
416 | 0 | 0 | $doc .= "\nvalid values:\n"; | ||||
417 | 0 | 0 | for my $v (@$valid_values) { | ||||
418 | 0 | 0 | $doc .= " " . $v . "\n"; | ||||
419 | 0 | 0 | 0 | $max_name_length = length($v)+2 if $max_name_length < length($v)+2; | |||
420 | } | ||||||
421 | 0 | 0 | chomp $doc; | ||||
422 | } | ||||||
423 | 1 | 50 | 33 | 10 | if ($example_values && @$example_values) { | ||
424 | 1 | 50 | 6 | $doc .= "\nexample" . (@$example_values > 1 and 's') . ":\n"; | |||
425 | $doc .= join(', ', | ||||||
426 | 1 | 50 | 2 | map { ref($_) ? Data::Dumper->new([$_])->Terse(1)->Dump() : $_ } @$example_values | |||
1 | 4 | ||||||
427 | ); | ||||||
428 | 1 | 3 | chomp($doc); | ||||
429 | } | ||||||
430 | 1 | 50 | 3 | $max_name_length = length($param_name) if $max_name_length < length($param_name); | |||
431 | |||||||
432 | 1 | 50 | 4 | my $param_type = $property_meta->data_type || ''; | |||
433 | 1 | 50 | 33 | 8 | if (defined($param_type) and $param_type !~ m/::/) { | ||
434 | 1 | 3 | $param_type = ucfirst(lc($param_type)); | ||||
435 | } | ||||||
436 | |||||||
437 | 1 | 1 | my $default_value; | ||||
438 | 1 | 50 | 33 | 5 | if (defined($default_value = $property_meta->default_value) | ||
439 | || defined(my $calculated_default = $property_meta->calculated_default) | ||||||
440 | ) { | ||||||
441 | 0 | 0 | 0 | unless (defined $default_value) { | |||
442 | 0 | 0 | $default_value = $calculated_default->() | ||||
443 | } | ||||||
444 | |||||||
445 | 0 | 0 | 0 | 0 | if ($param_type eq 'Boolean') { | ||
0 | |||||||
446 | 0 | 0 | 0 | $default_value = $default_value ? "'true'" : "'false' (--no$param_name)"; | |||
447 | } elsif ($property_meta->is_many && ref($default_value) eq 'ARRAY') { | ||||||
448 | 0 | 0 | 0 | if (@$default_value) { | |||
449 | 0 | 0 | $default_value = "('" . join("','",@$default_value) . "')"; | ||||
450 | } else { | ||||||
451 | 0 | 0 | $default_value = "()"; | ||||
452 | } | ||||||
453 | } else { | ||||||
454 | 0 | 0 | $default_value = "'$default_value'"; | ||||
455 | } | ||||||
456 | 0 | 0 | $default_value = "\nDefault value $default_value if not specified"; | ||||
457 | } | ||||||
458 | |||||||
459 | 1 | 4 | push @data, [$param_name, $param_type, $doc, $default_value]; | ||||
460 | 1 | 50 | 3 | if ($param_type eq 'Boolean') { | |||
461 | 0 | 0 | push @data, ['no'.$param_name, $param_type, "Make $param_name 'false'" ]; | ||||
462 | } | ||||||
463 | } | ||||||
464 | 6 | 6 | my $text = ''; | ||||
465 | 6 | 6 | for my $row (@data) { | ||||
466 | 1 | 50 | 33 | 7 | if (defined($format) and $format eq 'pod') { | ||
50 | 33 | ||||||
467 | 0 | 0 | 0 | $text .= "\n=item " . $row->[0] . ($row->[1]? ' I<' . $row->[1] . '>' : '') . "\n\n" . $row->[2] . "\n". ($row->[3]? $row->[3] . "\n" : ''); | |||
0 | |||||||
468 | } | ||||||
469 | elsif (defined($format) and $format eq 'html') { | ||||||
470 | 0 | 0 | 0 | $text .= "\n\t " . $row->[0] . ($row->[1]? ' ' . $row->[1] . '' : '') . " " . $row->[2] . ($row->[3]? " " . $row->[3] : '') . " \n"; |
|||
0 | |||||||
471 | } | ||||||
472 | else { | ||||||
473 | 1 | 50 | 6 | $text .= sprintf( | |||
474 | " %s\n%s\n", | ||||||
475 | Term::ANSIColor::colored($row->[0], 'bold'), # . " " . $row->[1], | ||||||
476 | Text::Wrap::wrap( | ||||||
477 | " ", # 1st line indent, | ||||||
478 | " ", # all other lines indent, | ||||||
479 | $row->[2], | ||||||
480 | $row->[3] || '', | ||||||
481 | ), | ||||||
482 | ); | ||||||
483 | } | ||||||
484 | } | ||||||
485 | |||||||
486 | 6 | 312 | return $text; | ||||
487 | } | ||||||
488 | |||||||
489 | |||||||
490 | sub _doc_footer { | ||||||
491 | 0 | 0 | 0 | my $self = shift; | |||
492 | 0 | 0 | my $pod = ''; | ||||
493 | |||||||
494 | 0 | 0 | my @method_header_map = ( | ||||
495 | 'LICENSE' => '_doc_license', | ||||||
496 | 'AUTHORS' => '_doc_authors', | ||||||
497 | 'CREDITS' => '_doc_credits', | ||||||
498 | 'BUGS' => '_doc_bugs', | ||||||
499 | 'SEE ALSO' => '_doc_see_also' | ||||||
500 | ); | ||||||
501 | |||||||
502 | 0 | 0 | while (@method_header_map) { | ||||
503 | 0 | 0 | my $header = shift @method_header_map; | ||||
504 | 0 | 0 | my $method = shift @method_header_map; | ||||
505 | 0 | 0 | my @txt = $self->$method; | ||||
506 | 0 | 0 | 0 | 0 | next if (@txt == 0 or (@txt == 1 and not $txt[0])); | ||
0 | |||||||
507 | 0 | 0 | 0 | if (@txt == 1) { | |||
508 | 0 | 0 | my @lines = split("\n",$txt[0]); | ||||
509 | 0 | 0 | $pod .= "=head1 $header\n\n" | ||||
510 | . join(" \n", @lines) | ||||||
511 | . "\n\n"; | ||||||
512 | } | ||||||
513 | else { | ||||||
514 | 0 | 0 | $pod .= "=head1 $header\n\n" | ||||
515 | . join("\n ",@txt); | ||||||
516 | 0 | 0 | $pod .= "\n\n"; | ||||
517 | } | ||||||
518 | } | ||||||
519 | |||||||
520 | 0 | 0 | return $pod; | ||||
521 | } | ||||||
522 | |||||||
523 | sub _doc_license { | ||||||
524 | 0 | 0 | 0 | return ''; | |||
525 | } | ||||||
526 | |||||||
527 | sub _doc_authors { | ||||||
528 | 0 | 0 | 0 | return (); | |||
529 | } | ||||||
530 | |||||||
531 | sub _doc_credits { | ||||||
532 | 0 | 0 | 0 | return ''; | |||
533 | } | ||||||
534 | |||||||
535 | sub _doc_bugs { | ||||||
536 | 0 | 0 | 0 | return ''; | |||
537 | } | ||||||
538 | |||||||
539 | sub _doc_see_also { | ||||||
540 | 0 | 0 | 0 | return (); | |||
541 | } | ||||||
542 | |||||||
543 | |||||||
544 | sub _shell_args_usage_string { | ||||||
545 | 1 | 1 | 26 | my $self = shift; | |||
546 | |||||||
547 | 1 | 1 | return eval { | ||||
548 | 1 | 50 | 9 | if ( $self->isa('Command::Tree') ) { | |||
50 | |||||||
0 | |||||||
549 | 0 | 0 | return '...'; | ||||
550 | } | ||||||
551 | elsif ($self->can("_execute_body") eq __PACKAGE__->can("_execute_body")) { | ||||||
552 | 1 | 20 | return '(no execute!)'; | ||||
553 | } | ||||||
554 | elsif ($self->__meta__->is_abstract) { | ||||||
555 | 0 | 0 | return '(no sub commands!)'; | ||||
556 | } | ||||||
557 | else { | ||||||
558 | return join( | ||||||
559 | " ", | ||||||
560 | map { | ||||||
561 | 0 | 0 | $self->_shell_arg_usage_string_from_property_meta($_) | ||||
0 | 0 | ||||||
562 | } $self->_shell_args_property_meta() | ||||||
563 | |||||||
564 | ); | ||||||
565 | } | ||||||
566 | }; | ||||||
567 | } | ||||||
568 | |||||||
569 | sub _shell_args_usage_string_abbreviated { | ||||||
570 | 0 | 0 | 0 | my $self = shift; | |||
571 | 0 | 0 | my $detailed = $self->_shell_args_usage_string; | ||||
572 | 0 | 0 | 0 | if (length($detailed) <= 20) { | |||
573 | 0 | 0 | return $detailed; | ||||
574 | } | ||||||
575 | else { | ||||||
576 | 0 | 0 | return substr($detailed,0,17) . '...'; | ||||
577 | } | ||||||
578 | } | ||||||
579 | |||||||
580 | sub sub_command_mapping { | ||||||
581 | 5 | 5 | 0 | 8 | my ($self, $class) = @_; | ||
582 | 5 | 50 | 22 | return if !$class; | |||
583 | 9 | 9 | 43 | no strict 'refs'; | |||
9 | 16 | ||||||
9 | 5407 | ||||||
584 | 0 | 0 | my $mapping = ${ $class . '::SUB_COMMAND_MAPPING'}; | ||||
0 | 0 | ||||||
585 | 0 | 0 | 0 | if (ref($mapping) eq 'HASH') { | |||
586 | 0 | 0 | return $mapping; | ||||
587 | } else { | ||||||
588 | 0 | 0 | return; | ||||
589 | } | ||||||
590 | }; | ||||||
591 | |||||||
592 | sub command_name { | ||||||
593 | 5 | 5 | 0 | 7 | my $self = shift; | ||
594 | 5 | 66 | 24 | my $class = ref($self) || $self; | |||
595 | 5 | 9 | my $prepend = ''; | ||||
596 | |||||||
597 | |||||||
598 | # There can be a hash in the command entry point class that maps | ||||||
599 | # root level tools to classes so they can be in a different location | ||||||
600 | # ...this bit of code considers that misdirection: | ||||||
601 | 5 | 8 | my $entry_point_class = $Command::entry_point_class; | ||||
602 | 5 | 20 | my $mapping = $self->sub_command_mapping($entry_point_class); | ||||
603 | 5 | 15 | for my $k (%$mapping) { | ||||
604 | 0 | 0 | my $v = $mapping->{$k}; | ||||
605 | 0 | 0 | 0 | 0 | if ($v && $v eq $class) { | ||
606 | 0 | 0 | my @words = grep { $_ ne 'Command' } split(/::/,$class); | ||||
0 | 0 | ||||||
607 | 0 | 0 | return join(' ', $self->_command_name_for_class_word($words[0]), $k); | ||||
608 | } | ||||||
609 | } | ||||||
610 | |||||||
611 | |||||||
612 | 5 | 50 | 33 | 26 | if (defined($entry_point_class) and $class =~ /^($entry_point_class)(::.+|)$/) { | ||
613 | 0 | 0 | $prepend = $Command::entry_point_bin; | ||||
614 | 0 | 0 | $class = $2; | ||||
615 | 0 | 0 | 0 | if ($class =~ s/^:://) { | |||
616 | 0 | 0 | $prepend .= ' '; | ||||
617 | } | ||||||
618 | } | ||||||
619 | 5 | 17 | my @words = grep { $_ ne 'Command' } split(/::/,$class); | ||||
11 | 23 | ||||||
620 | 5 | 8 | my $n = join(' ', map { $self->_command_name_for_class_word($_) } @words); | ||||
11 | 27 | ||||||
621 | 5 | 19 | return $prepend . $n; | ||||
622 | } | ||||||
623 | |||||||
624 | sub command_name_brief { | ||||||
625 | 9 | 9 | 0 | 10 | my $self = shift; | ||
626 | 9 | 33 | 75 | my $class = ref($self) || $self; | |||
627 | 9 | 23 | my @words = grep { $_ ne 'Command' } split(/::/,$class); | ||||
33 | 42 | ||||||
628 | 9 | 16 | my $n = join(' ', map { $self->_command_name_for_class_word($_) } $words[-1]); | ||||
9 | 33 | ||||||
629 | 9 | 39 | return $n; | ||||
630 | } | ||||||
631 | |||||||
632 | sub color_command_name { | ||||||
633 | 0 | 0 | 0 | 0 | my $text = shift; | ||
634 | |||||||
635 | 0 | 0 | my $colored_text = []; | ||||
636 | |||||||
637 | 0 | 0 | my @COLOR_TEMPLATES = ('red', 'bold red', 'magenta', 'bold magenta'); | ||||
638 | 0 | 0 | my @parts = split(/\s+/, $text); | ||||
639 | 0 | 0 | for(my $i = 0 ; $i < @parts ; $i++ ){ | ||||
640 | 0 | 0 | 0 | push @$colored_text, ($i < @COLOR_TEMPLATES) ? Term::ANSIColor::colored($parts[$i], $COLOR_TEMPLATES[$i]) : $parts[$i]; | |||
641 | } | ||||||
642 | |||||||
643 | 0 | 0 | return join(' ', @$colored_text); | ||||
644 | } | ||||||
645 | |||||||
646 | sub _base_command_class_and_extension { | ||||||
647 | 0 | 0 | 0 | my $self = shift; | |||
648 | 0 | 0 | 0 | my $class = ref($self) || $self; | |||
649 | 0 | 0 | return ($class =~ /^(.*)::([^\:]+)$/); | ||||
650 | } | ||||||
651 | |||||||
652 | sub _command_name_for_class_word { | ||||||
653 | 45 | 45 | 55 | my $self = shift; | |||
654 | 45 | 43 | my $s = shift; | ||||
655 | 45 | 67 | $s =~ s/_/-/g; | ||||
656 | 45 | 228 | $s =~ s/^([A-Z])/\L$1/; # ignore first capital because that is assumed | ||||
657 | 45 | 91 | $s =~ s/([A-Z])/-$1/g; # all other capitals prepend a dash | ||||
658 | 45 | 92 | $s =~ s/([a-zA-Z])([0-9])/$1$2/g; # treat number as begining word | ||||
659 | 45 | 56 | $s = lc($s); | ||||
660 | 45 | 93 | return $s; | ||||
661 | } | ||||||
662 | |||||||
663 | sub _pod2txt { | ||||||
664 | 1 | 1 | 1 | my $txt = shift; | |||
665 | 1 | 1 | my $output = ''; | ||||
666 | 1 | 11 | my $parser = Pod::Simple::Text->new; | ||||
667 | 1 | 113 | $parser->no_errata_section(1); | ||||
668 | 1 | 10 | $parser->output_string($output); | ||||
669 | 1 | 976 | $parser->parse_string_document("=pod\n\n$txt"); | ||||
670 | 1 | 1003 | return $output; | ||||
671 | } | ||||||
672 | |||||||
673 | sub _additional_help_sections { | ||||||
674 | 1 | 1 | 2 | return; | |||
675 | } | ||||||
676 | |||||||
677 | 1; |