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