| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Applify; | 
| 2 | 18 |  |  | 18 |  | 54178 | use strict; | 
|  | 18 |  |  |  |  | 57 |  | 
|  | 18 |  |  |  |  | 482 |  | 
| 3 | 17 |  |  | 17 |  | 109 | use warnings; | 
|  | 17 |  |  |  |  | 37 |  | 
|  | 17 |  |  |  |  | 911 |  | 
| 4 | 16 |  |  | 16 |  | 96 | use Carp           (); | 
|  | 16 |  |  |  |  | 39 |  | 
|  | 16 |  |  |  |  | 614 |  | 
| 5 | 14 |  |  | 14 |  | 78 | use File::Basename (); | 
|  | 14 |  |  |  |  | 29 |  | 
|  | 14 |  |  |  |  | 599 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 14 | 50 |  | 14 |  | 912 | use constant SUB_NAME_IS_AVAILABLE => $INC{'App/FatPacker/Trace.pm'} | 
|  | 14 | 50 |  |  |  | 10262 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 8 |  |  |  |  |  |  | ? 0    # this will be true when running under "fatpack" | 
| 9 | 14 |  |  | 14 |  | 93 | : eval 'use Sub::Name; 1' ? 1 : 0; | 
|  | 14 |  |  |  |  | 37 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | our $VERSION = '0.14'; | 
| 12 |  |  |  |  |  |  | our $PERLDOC       = 'perldoc'; | 
| 13 |  |  |  |  |  |  | our $SUBCMD_PREFIX = "command"; | 
| 14 |  |  |  |  |  |  | my $ANON = 1; | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | sub app { | 
| 17 | 43 |  |  | 43 | 1 | 6520 | my $self   = shift; | 
| 18 | 43 |  | 66 |  |  | 227 | my $code   = $self->{app} ||= shift; | 
| 19 | 43 |  |  |  |  | 138 | my $parser = $self->_option_parser; | 
| 20 | 43 |  |  |  |  | 2840 | my (%options, @options_spec, $application_class, $app); | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | # has to be run before calculating option spec. | 
| 23 |  |  |  |  |  |  | # cannot do ->can() as application_class isn't created yet. | 
| 24 | 43 | 100 |  |  |  | 185 | if ($self->_subcommand_activate($ARGV[0])) { shift @ARGV; } | 
|  | 3 |  |  |  |  | 11 |  | 
| 25 | 42 |  |  |  |  | 108 | for my $option (@{$self->{options}}) { | 
|  | 42 |  |  |  |  | 159 |  | 
| 26 | 115 |  |  |  |  | 614 | my $switch = $self->_attr_to_option($option->{name}); | 
| 27 | 115 |  |  |  |  | 305 | push @options_spec, $self->_calculate_option_spec($option); | 
| 28 | 115 | 50 |  |  |  | 462 | $options{$switch} = $option->{default}     if exists $option->{default}; | 
| 29 | 115 | 100 |  |  |  | 373 | $options{$switch} = [@{$options{$switch}}] if ref($options{$switch}) eq 'ARRAY'; | 
|  | 24 |  |  |  |  | 72 |  | 
| 30 |  |  |  |  |  |  | } | 
| 31 |  |  |  |  |  |  |  | 
| 32 | 42 | 50 |  |  |  | 190 | unless ($parser->getoptions(\%options, @options_spec, $self->_default_options)) { | 
| 33 | 0 |  |  |  |  | 0 | $self->_exit(1); | 
| 34 |  |  |  |  |  |  | } | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 42 | 100 |  |  |  | 26680 | if ($options{help}) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 37 | 1 |  |  |  |  | 9 | $self->print_help; | 
| 38 | 1 |  |  |  |  | 6 | $self->_exit('help'); | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  | elsif ($options{man}) { | 
| 41 | 1 |  |  |  |  | 5 | system $PERLDOC => $self->documentation; | 
| 42 | 1 |  |  |  |  | 66 | $self->_exit($? >> 8); | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  | elsif ($options{version}) { | 
| 45 | 1 |  |  |  |  | 10 | $self->print_version; | 
| 46 | 1 |  |  |  |  | 10 | $self->_exit('version'); | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 | 42 |  | 66 |  |  | 278 | $application_class = $self->{application_class} ||= $self->_generate_application_class($code); | 
| 50 |  |  |  |  |  |  | $app = $application_class->new( | 
| 51 | 42 |  |  |  |  | 204 | {map { my $k = $self->_option_to_attr($_); $k => $self->_upgrade($k, $options{$_}) } keys %options}); | 
|  | 118 |  |  |  |  | 388 |  | 
|  | 118 |  |  |  |  | 587 |  | 
| 52 |  |  |  |  |  |  |  | 
| 53 | 42 | 50 |  |  |  | 607 | return $app if defined wantarray;    # $app = do $script_file; | 
| 54 | 0 |  |  |  |  | 0 | $self->_exit($app->run(@ARGV)); | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | sub documentation { | 
| 58 | 67 | 100 |  | 67 | 1 | 71097 | return $_[0]->{documentation} if @_ == 1; | 
| 59 | 9 | 100 |  |  |  | 56 | $_[0]->{documentation} = $_[1] or die 'Usage: documentation $file|$module_name;'; | 
| 60 | 8 |  |  |  |  | 86 | return $_[0]; | 
| 61 |  |  |  |  |  |  | } | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | sub extends { | 
| 64 | 4 |  |  | 4 | 1 | 11 | my $self = shift; | 
| 65 | 4 |  |  |  |  | 31 | $self->{extends} = [@_]; | 
| 66 | 4 |  |  |  |  | 102 | return $self; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub import { | 
| 70 | 21 |  |  | 21 |  | 14150 | my ($class, %args) = @_; | 
| 71 | 21 |  |  |  |  | 281 | my @caller = caller; | 
| 72 | 21 |  |  |  |  | 141 | my $self   = $class->new({caller => \@caller}); | 
| 73 | 21 |  |  |  |  | 80 | my $ns     = $caller[0] . '::'; | 
| 74 | 21 |  |  |  |  | 44 | my %export; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 21 |  |  |  |  | 163 | strict->import; | 
| 77 | 21 |  |  |  |  | 318 | warnings->import; | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 21 |  |  |  |  | 138 | $self->{skip_subs} = {app => 1, option => 1, version => 1, documentation => 1, extends => 1, subcommand => 1}; | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 14 |  |  | 14 |  | 105 | no strict 'refs'; | 
|  | 14 |  |  |  |  | 29 |  | 
|  | 14 |  |  |  |  | 1817 |  | 
| 82 | 21 |  |  |  |  | 1781 | for my $name (keys %$ns) { | 
| 83 | 5118 |  |  |  |  | 9777 | $self->{'skip_subs'}{$name} = 1; | 
| 84 |  |  |  |  |  |  | } | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 21 |  |  |  |  | 301 | for my $k (qw(app extends option version documentation subcommand)) { | 
| 87 | 126 |  | 66 |  |  | 447 | my $name = $args{$k} // $k; | 
| 88 | 126 | 50 |  |  |  | 262 | next unless $name; | 
| 89 | 126 | 50 |  |  |  | 528 | $export{$k} = $name =~ /::/ ? $name : "$caller[0]\::$name"; | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 14 |  |  | 14 |  | 98 | no warnings 'redefine';    # need to allow redefine when loading a new app | 
|  | 14 |  |  |  |  | 26 |  | 
|  | 14 |  |  |  |  | 24412 |  | 
| 93 | 21 |  |  | 22 |  | 125 | *{$export{app}}           = sub (&) { $self->app(@_) }; | 
|  | 21 |  |  |  |  | 169 |  | 
|  | 22 |  |  |  |  | 299 |  | 
| 94 | 21 |  |  | 30 |  | 86 | *{$export{option}}        = sub     { $self->option(@_) }; | 
|  | 21 |  |  |  |  | 105 |  | 
|  | 30 |  |  |  |  | 185 |  | 
| 95 | 21 |  |  | 4 |  | 342 | *{$export{version}}       = sub     { $self->version(@_) }; | 
|  | 21 |  |  |  |  | 126 |  | 
|  | 4 |  |  |  |  | 24 |  | 
| 96 | 21 |  |  | 6 |  | 84 | *{$export{documentation}} = sub     { $self->documentation(@_) }; | 
|  | 21 |  |  |  |  | 119 |  | 
|  | 6 |  |  |  |  | 45 |  | 
| 97 | 21 |  |  | 4 |  | 77 | *{$export{extends}}       = sub     { $self->extends(@_) }; | 
|  | 21 |  |  |  |  | 87 |  | 
|  | 4 |  |  |  |  | 31 |  | 
| 98 | 21 |  |  | 8 |  | 77 | *{$export{subcommand}}    = sub     { $self->subcommand(@_) }; | 
|  | 21 |  |  |  |  | 2607 |  | 
|  | 8 |  |  |  |  | 66 |  | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | sub new { | 
| 102 | 21 |  |  | 21 | 1 | 72 | my ($class, $args) = @_; | 
| 103 | 21 |  |  |  |  | 66 | my $self = bless $args, $class; | 
| 104 |  |  |  |  |  |  |  | 
| 105 | 21 |  | 50 |  |  | 273 | $self->{options} ||= []; | 
| 106 | 21 | 50 |  |  |  | 86 | $self->{caller} or die 'Usage: $self->new({ caller => [...], ... })'; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 21 |  |  |  |  | 63 | return $self; | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | sub option { | 
| 112 | 39 |  |  | 39 | 1 | 4898 | my $self          = shift; | 
| 113 | 39 | 100 |  |  |  | 129 | my $type          = shift or die 'Usage: option $type => ...'; | 
| 114 | 38 | 100 |  |  |  | 113 | my $name          = shift or die 'Usage: option $type => $name => ...'; | 
| 115 | 37 | 100 |  |  |  | 117 | my $documentation = shift or die 'Usage: option $type => $name => $documentation, ...'; | 
| 116 | 36 |  |  |  |  | 69 | my ($default, %args); | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 36 | 100 |  |  |  | 122 | if (@_ % 2) { | 
| 119 | 6 |  |  |  |  | 14 | $default = shift; | 
| 120 | 6 |  |  |  |  | 19 | %args    = @_; | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  | else { | 
| 123 | 30 |  |  |  |  | 77 | %args = @_; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 36 | 100 | 66 |  |  | 120 | if ($args{alias} and !ref $args{alias}) { | 
| 127 | 1 |  |  |  |  | 4 | $args{alias} = [$args{alias}]; | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 36 |  |  |  |  | 62 | push @{$self->{options}}, {default => $default, %args, type => $type, name => $name, documentation => $documentation}; | 
|  | 36 |  |  |  |  | 212 |  | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 36 |  |  |  |  | 397 | return $self; | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 4 |  |  | 4 | 1 | 29 | sub options { $_[0]->{options} } | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | sub print_help { | 
| 138 | 6 |  |  | 6 | 1 | 5635 | my $self    = shift; | 
| 139 | 6 |  |  |  |  | 16 | my @options = @{$self->{options}}; | 
|  | 6 |  |  |  |  | 26 |  | 
| 140 | 6 |  |  |  |  | 16 | my $width   = 0; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 6 |  |  |  |  | 28 | push @options, {name => ''}; | 
| 143 | 6 |  |  |  |  | 30 | push @options, {name => 'help', documentation => 'Print this help text'}; | 
| 144 | 6 | 100 |  |  |  | 28 | push @options, {name => 'man', documentation => 'Display manual for this application'} if $self->documentation; | 
| 145 | 6 | 100 |  |  |  | 26 | push @options, {name => 'version', documentation => 'Print application name and version'} if $self->version; | 
| 146 | 6 |  |  |  |  | 26 | push @options, {name => ''}; | 
| 147 |  |  |  |  |  |  |  | 
| 148 | 6 |  |  |  |  | 33 | $self->_print_synopsis; | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | OPTION: | 
| 151 | 6 |  |  |  |  | 22 | for my $option (@options) { | 
| 152 | 40 |  |  |  |  | 83 | my $length = length $option->{name}; | 
| 153 | 40 | 100 |  |  |  | 120 | $width = $length if $width < $length; | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 6 |  |  |  |  | 66 | print "Usage:\n"; | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 6 | 100 |  |  |  | 20 | if (%{$self->{subcommands} || {}}) { | 
|  | 6 | 100 |  |  |  | 49 |  | 
| 159 | 2 |  |  |  |  | 4 | my $subcmds = [sort { $a->{name} cmp $b->{name} } values %{$self->{subcommands}}]; | 
|  | 2 |  |  |  |  | 16 |  | 
|  | 2 |  |  |  |  | 18 |  | 
| 160 | 2 |  |  |  |  | 9 | my ($width) = sort { $b <=> $a } map { length($_->{name}) } @$subcmds; | 
|  | 2 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 16 |  | 
| 161 | 2 |  |  |  |  | 137 | print "\n    ", File::Basename::basename($0), " [command] [options]\n"; | 
| 162 | 2 |  |  |  |  | 9 | print "\ncommands:\n"; | 
| 163 | 2 |  |  |  |  | 13 | printf("    %-${width}s  %s\n", @{$_}{'name', 'desc'}) for @$subcmds; | 
|  | 4 |  |  |  |  | 31 |  | 
| 164 | 2 |  |  |  |  | 9 | print "\noptions:\n"; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | OPTION: | 
| 168 | 6 |  |  |  |  | 24 | for my $option (@options) { | 
| 169 | 40 | 100 |  |  |  | 115 | my $name = $self->_attr_to_option($option->{name}) or do { print "\n"; next OPTION }; | 
|  | 12 |  |  |  |  | 36 |  | 
|  | 12 |  |  |  |  | 34 |  | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | printf( | 
| 172 |  |  |  |  |  |  | " %s %2s%-${width}s  %s\n", | 
| 173 |  |  |  |  |  |  | $option->{required} ? '*'  : ' ', | 
| 174 |  |  |  |  |  |  | length($name) > 1   ? '--' : '-', | 
| 175 |  |  |  |  |  |  | $name, $option->{documentation}, | 
| 176 | 28 | 100 |  |  |  | 242 | ); | 
|  |  | 100 |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 | 6 |  |  |  |  | 58 | return $self; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub print_version { | 
| 183 | 4 |  |  | 4 | 1 | 877 | my $self = shift; | 
| 184 | 4 | 100 |  |  |  | 13 | my $version = $self->version or die 'Cannot print version without version()'; | 
| 185 |  |  |  |  |  |  |  | 
| 186 | 3 | 100 |  |  |  | 27 | unless ($version =~ m!^\d!) { | 
| 187 | 1 | 50 |  |  |  | 69 | eval "require $version; 1" or die "Could not load $version: $@"; | 
| 188 | 1 |  |  |  |  | 20 | $version = $version->VERSION; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 3 |  |  |  |  | 231 | printf "%s version %s\n", File::Basename::basename($0), $version; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | sub subcommand { | 
| 195 | 23 |  |  | 23 | 1 | 74 | my ($self, $name) = (shift, shift); | 
| 196 | 23 | 100 |  |  |  | 148 | return $self->{subcommand} unless @_; | 
| 197 | 8 |  |  |  |  | 40 | $self->{subcommands}{$name} = {name => $name, desc => $_[0], adaptation => $_[1]}; | 
| 198 | 8 |  |  |  |  | 100 | return $self; | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | sub version { | 
| 202 | 63 | 100 |  | 63 | 1 | 2349 | return $_[0]->{version} if @_ == 1; | 
| 203 | 8 | 100 |  |  |  | 39 | $_[0]->{version} = $_[1] or die 'Usage: version $module_name|$num;'; | 
| 204 | 7 |  |  |  |  | 59 | return $_[0]; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | sub _attr_to_option { | 
| 208 | 282 | 100 |  | 282 |  | 823 | local $_ = $_[1] or return; | 
| 209 | 270 |  |  |  |  | 728 | s!_!-!g; | 
| 210 | 270 |  |  |  |  | 705 | $_; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | sub _calculate_option_spec { | 
| 214 | 126 |  |  | 126 |  | 1027 | my ($self, $option) = @_; | 
| 215 | 126 |  |  |  |  | 294 | my $spec = $self->_attr_to_option($option->{name}); | 
| 216 |  |  |  |  |  |  |  | 
| 217 | 126 | 100 |  |  |  | 744 | if (ref $option->{alias} eq 'ARRAY') { | 
| 218 | 2 |  |  |  |  | 6 | $spec .= join '|', '', @{$option->{alias}}; | 
|  | 2 |  |  |  |  | 9 |  | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 126 | 100 |  |  |  | 1044 | if    ($option->{type} =~ /^(?:bool|flag)/i) { $spec .= '!' } | 
|  | 7 | 100 |  |  |  | 21 |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 222 | 1 |  |  |  |  | 4 | elsif ($option->{type} =~ /^inc/)            { $spec .= '+' } | 
| 223 | 55 |  |  |  |  | 145 | elsif ($option->{type} =~ /^str/)            { $spec .= '=s' } | 
| 224 | 1 |  |  |  |  | 3 | elsif ($option->{type} =~ /^int/i)           { $spec .= '=i' } | 
| 225 | 3 |  |  |  |  | 8 | elsif ($option->{type} =~ /^num/i)           { $spec .= '=f' } | 
| 226 | 48 |  |  |  |  | 593 | elsif ($option->{type} =~ /^file/)           { $spec .= '=s' }    # TODO | 
| 227 | 10 |  |  |  |  | 30 | elsif ($option->{type} =~ /^dir/)            { $spec .= '=s' }    # TODO | 
| 228 | 1 |  |  |  |  | 12 | else                                         { die 'Usage: option {bool|flag|inc|str|int|num|file|dir} ...' } | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 125 | 100 |  |  |  | 344 | if (my $n_of = $option->{n_of}) { | 
| 231 | 26 | 100 |  |  |  | 68 | $spec .= $n_of eq '@' ? $n_of : "{$n_of}"; | 
| 232 |  |  |  |  |  |  | $option->{default} | 
| 233 | 26 | 50 | 66 |  |  | 118 | and ref $option->{default} ne 'ARRAY' | 
| 234 |  |  |  |  |  |  | and die 'Usage option ... default => [Need to be an array ref]'; | 
| 235 | 26 |  | 100 |  |  | 84 | $option->{default} ||= []; | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 | 125 |  |  |  |  | 395 | return $spec; | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | sub _default_options { | 
| 242 | 44 |  |  | 44 |  | 2348 | my $self = shift; | 
| 243 | 44 |  |  |  |  | 84 | my @default; | 
| 244 |  |  |  |  |  |  |  | 
| 245 | 44 |  |  |  |  | 109 | push @default, 'help'; | 
| 246 | 44 | 100 |  |  |  | 138 | push @default, 'man' if $self->documentation; | 
| 247 | 44 | 100 |  |  |  | 166 | push @default, 'version' if $self->version; | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 44 |  |  |  |  | 235 | return @default; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | sub _exit { | 
| 253 | 4 |  |  | 4 |  | 21 | my ($self, $reason) = @_; | 
| 254 | 4 | 100 |  |  |  | 47 | exit 0 unless ($reason =~ /^\d+$/);    # may change without warning... | 
| 255 | 4 |  |  |  |  | 31 | exit $reason; | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | sub _generate_application_class { | 
| 259 | 22 |  |  | 22 |  | 85 | my ($self, $code) = @_; | 
| 260 | 22 |  |  |  |  | 84 | my $application_class = $self->{caller}[1]; | 
| 261 | 22 |  | 100 |  |  | 142 | my $extends = $self->{extends} || []; | 
| 262 | 22 |  |  |  |  | 62 | my ($meta, @required); | 
| 263 |  |  |  |  |  |  |  | 
| 264 | 22 |  |  |  |  | 235 | $application_class =~ s!\W!_!g; | 
| 265 | 22 |  |  |  |  | 156 | $application_class = join '::', ref($self), "__ANON__${ANON}__", $application_class; | 
| 266 | 22 |  |  |  |  | 64 | $ANON++; | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 13 | 50 |  | 13 |  | 124 | eval qq[ | 
|  | 13 |  |  |  |  | 31 |  | 
|  | 13 |  |  |  |  | 60843 |  | 
|  | 22 |  |  |  |  | 2067 |  | 
| 269 |  |  |  |  |  |  | package $application_class; | 
| 270 |  |  |  |  |  |  | use base qw(@$extends); | 
| 271 |  |  |  |  |  |  | 1; | 
| 272 |  |  |  |  |  |  | ] or die "Failed to generate application class: $@"; | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | { | 
| 275 | 14 |  |  | 14 |  | 127 | no strict 'refs'; | 
|  | 14 |  |  |  |  | 37 |  | 
|  | 14 |  |  |  |  | 14099 |  | 
|  | 22 |  |  |  |  | 70 |  | 
| 276 | 42 |  |  | 42 |  | 145 | _sub("$application_class\::new" => sub { my $class = shift; bless shift, $class }) | 
|  | 42 |  |  |  |  | 124 |  | 
| 277 | 22 | 50 |  |  |  | 256 | unless grep { $_->can('new') } @$extends; | 
|  | 3 |  |  |  |  | 76 |  | 
| 278 | 22 |  |  | 27 |  | 183 | _sub("$application_class\::_script" => sub {$self}); | 
|  | 27 |  |  |  |  | 3842 |  | 
| 279 |  |  |  |  |  |  | _sub( | 
| 280 |  |  |  |  |  |  | "$application_class\::run" => sub { | 
| 281 | 7 |  |  | 7 |  | 2682 | my ($app, @extra) = @_; | 
| 282 |  |  |  |  |  |  |  | 
| 283 | 7 | 100 |  |  |  | 36 | if (@required = grep { not defined $app->{$_} } @required) { | 
|  | 2 |  |  |  |  | 21 |  | 
| 284 | 1 |  |  |  |  | 5 | my $required = join ', ', map { '--' . $self->_attr_to_option($_) } @required; | 
|  | 1 |  |  |  |  | 6 |  | 
| 285 | 1 |  |  |  |  | 5 | $app->_script->print_help; | 
| 286 | 1 |  |  |  |  | 8 | die "Required attribute missing: $required\n"; | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | # get subcommand code - which should have a registered subroutine | 
| 290 |  |  |  |  |  |  | # or fallback to app {} block. | 
| 291 | 6 |  | 66 |  |  | 25 | $code = $app->_script->_subcommand_code($app) || $code; | 
| 292 | 6 |  |  |  |  | 172 | return $app->$code(@extra); | 
| 293 |  |  |  |  |  |  | } | 
| 294 | 22 |  |  |  |  | 211 | ); | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 22 |  |  |  |  | 92 | for ('app', $self->{caller}[0]) { | 
| 297 | 44 |  |  |  |  | 87 | my $ns = \%{"$_\::"}; | 
|  | 44 |  |  |  |  | 172 |  | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 44 |  |  |  |  | 1545 | for my $name (keys %$ns) { | 
| 300 | 5580 | 100 |  |  |  | 12289 | $self->{skip_subs}{$name} and next; | 
| 301 | 129 | 100 |  |  |  | 239 | my $code = eval { ref $ns->{$name} eq 'CODE' ? $ns->{$name} : *{$ns->{$name}}{CODE} } or next; | 
|  | 129 | 100 |  |  |  | 401 |  | 
|  | 119 |  |  |  |  | 703 |  | 
| 302 | 14 |  |  |  |  | 68 | my $fqn = join '::', $application_class, $name; | 
| 303 | 14 |  |  |  |  | 69 | _sub($fqn => $code); | 
| 304 | 14 |  |  |  |  | 62 | delete $ns->{$name};    # may be a bit too destructive? | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  |  | 
| 308 | 22 | 50 | 33 |  |  | 356 | $meta = $application_class->meta if $application_class->isa('Moose::Object') and $application_class->can('meta'); | 
| 309 |  |  |  |  |  |  |  | 
| 310 | 22 |  |  |  |  | 67 | for my $option (@{$self->{options}}) { | 
|  | 22 |  |  |  |  | 89 |  | 
| 311 | 32 |  |  |  |  | 77 | my $name = $option->{name}; | 
| 312 | 32 |  |  |  |  | 97 | my $fqn = join '::', $application_class, $name; | 
| 313 | 32 | 50 |  |  |  | 85 | if ($meta) { | 
| 314 | 0 |  |  |  |  | 0 | $meta->add_attribute($name => {is => 'rw', default => $option->{default}}); | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | else { | 
| 317 | 32 | 100 |  | 63 |  | 166 | _sub($fqn => sub { @_ == 2 and $_[0]->{$name} = $_[1]; $_[0]->{$name} }); | 
|  | 63 |  |  |  |  | 4352 |  | 
|  | 63 |  |  |  |  | 391 |  | 
| 318 |  |  |  |  |  |  | } | 
| 319 | 32 | 100 |  |  |  | 158 | push @required, $name if $option->{required}; | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  |  | 
| 323 | 22 |  |  |  |  | 129 | return $application_class; | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | sub _load_class { | 
| 327 | 62 | 100 |  | 62 |  | 378 | my $class = shift or return undef; | 
| 328 | 26 | 100 |  |  |  | 745 | return $class if $class->can('new'); | 
| 329 | 1 | 50 |  |  |  | 134 | return eval "require $class; 1" ? $class : ""; | 
| 330 |  |  |  |  |  |  | } | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | sub _option_parser { | 
| 333 | 45 |  | 66 | 45 |  | 178 | $_[0]->{_option_parser} ||= do { | 
| 334 | 21 |  |  |  |  | 7606 | require Getopt::Long; | 
| 335 | 21 |  |  |  |  | 161809 | Getopt::Long::Parser->new(config => [qw(no_auto_help no_auto_version pass_through)]); | 
| 336 |  |  |  |  |  |  | }; | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | sub _option_to_attr { | 
| 340 | 118 | 50 |  | 118 |  | 324 | local $_ = $_[1] or return; | 
| 341 | 118 |  |  |  |  | 367 | s!-!_!g; | 
| 342 | 118 |  |  |  |  | 456 | $_; | 
| 343 |  |  |  |  |  |  | } | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | sub _print_synopsis { | 
| 346 | 6 |  |  | 6 |  | 27 | my $self = shift; | 
| 347 | 6 | 100 |  |  |  | 20 | my $documentation = $self->documentation or return; | 
| 348 | 3 |  |  |  |  | 8 | my $print; | 
| 349 |  |  |  |  |  |  |  | 
| 350 | 3 | 50 |  |  |  | 264 | unless (-e $documentation) { | 
| 351 | 0 | 0 |  |  |  | 0 | eval "use $documentation; 1" or die "Could not load $documentation: $@"; | 
| 352 | 0 |  |  |  |  | 0 | $documentation =~ s!::!/!g; | 
| 353 | 0 |  |  |  |  | 0 | $documentation = $INC{"$documentation.pm"}; | 
| 354 |  |  |  |  |  |  | } | 
| 355 |  |  |  |  |  |  |  | 
| 356 | 3 | 50 |  |  |  | 113 | open my $FH, '<', $documentation or die "Failed to read synopsis from $documentation: $@"; | 
| 357 |  |  |  |  |  |  |  | 
| 358 | 3 |  |  |  |  | 74 | while (<$FH>) { | 
| 359 | 175 | 100 | 100 |  |  | 442 | last if $print and /^=(?:cut|head1)/; | 
| 360 | 173 | 100 |  |  |  | 339 | print if $print; | 
| 361 | 173 | 100 |  |  |  | 572 | $print = 1 if /^=head1 SYNOPSIS/; | 
| 362 |  |  |  |  |  |  | } | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | sub _sub { | 
| 366 | 112 |  |  | 112 |  | 279 | my ($fqn, $code) = @_; | 
| 367 | 14 |  |  | 14 |  | 123 | no strict 'refs'; | 
|  | 14 |  |  |  |  | 39 |  | 
|  | 14 |  |  |  |  | 2376 |  | 
| 368 | 112 | 100 |  |  |  | 674 | return if *$fqn{CODE}; | 
| 369 | 111 |  |  |  |  | 422 | *$fqn = SUB_NAME_IS_AVAILABLE ? Sub::Name::subname($fqn, $code) : $code; | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | sub _subcommand_activate { | 
| 373 | 43 |  |  | 43 |  | 148 | my ($self, $name) = @_; | 
| 374 | 43 | 100 | 100 |  |  | 391 | return undef unless $name and $name =~ /^\w+/; | 
| 375 | 5 | 100 |  |  |  | 28 | return undef unless $self->{subcommands}{$name}; | 
| 376 | 4 |  |  |  |  | 15 | $self->{subcommand} = $name; | 
| 377 |  |  |  |  |  |  | { | 
| 378 | 14 |  |  | 14 |  | 107 | no warnings 'redefine'; | 
|  | 14 |  |  |  |  | 32 |  | 
|  | 14 |  |  |  |  | 7029 |  | 
|  | 4 |  |  |  |  | 9 |  | 
| 379 |  |  |  |  |  |  | local *Applify::app = sub { | 
| 380 | 1 |  |  | 1 |  | 241 | Carp::confess( | 
| 381 |  |  |  |  |  |  | "Looks like you have a typo in your script! Cannot have app{} inside a subcommand options block."); | 
| 382 | 4 |  |  |  |  | 27 | }; | 
| 383 | 4 |  |  |  |  | 107 | $self->{subcommands}{$name}{adaptation}->($self); | 
| 384 |  |  |  |  |  |  | } | 
| 385 | 3 |  |  |  |  | 16 | return 1; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | sub _subcommand_code { | 
| 389 | 10 |  |  | 10 |  | 819 | my ($self, $app, $name) = (shift, shift); | 
| 390 | 10 | 100 |  |  |  | 41 | return undef unless $name = $self->subcommand; | 
| 391 | 4 |  |  |  |  | 51 | return $app->can("${SUBCMD_PREFIX}_${name}"); | 
| 392 |  |  |  |  |  |  | } | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | sub _upgrade { | 
| 395 | 118 |  |  | 118 |  | 297 | my ($self, $name, $input) = @_; | 
| 396 | 118 | 100 |  |  |  | 436 | return $input unless defined $input; | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 62 |  |  |  |  | 132 | my ($option) = grep { $_->{name} eq $name } @{$self->{options}}; | 
|  | 305 |  |  |  |  | 858 |  | 
|  | 62 |  |  |  |  | 147 |  | 
| 399 | 62 | 100 |  |  |  | 219 | return $input unless my $class = _load_class($option->{isa}); | 
| 400 | 25 | 100 |  |  |  | 146 | return ref $input eq 'ARRAY' ? [map { $class->new($_) } @$input] : $class->new($input); | 
|  | 5 |  |  |  |  | 26 |  | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | 1; | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | =encoding utf8 | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | =head1 NAME | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | Applify - Write object oriented scripts with ease | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | =head1 VERSION | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | 0.14 | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | This module should keep all the noise away and let you write scripts | 
| 418 |  |  |  |  |  |  | very easily. These scripts can even be unittested even though they | 
| 419 |  |  |  |  |  |  | are define directly in the script file and not in a module. | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  | #!/usr/bin/perl | 
| 424 |  |  |  |  |  |  | use Applify; | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | option file => input_file => 'File to read from'; | 
| 427 |  |  |  |  |  |  | option dir => output_dir => 'Directory to write files to'; | 
| 428 |  |  |  |  |  |  | option flag => dry_run => 'Use --no-dry-run to actually do something', 1; | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | documentation __FILE__; | 
| 431 |  |  |  |  |  |  | version 1.23; | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | sub generate_exit_value { | 
| 434 |  |  |  |  |  |  | return int rand 100; | 
| 435 |  |  |  |  |  |  | } | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | app { | 
| 438 |  |  |  |  |  |  | my($self, @extra) = @_; | 
| 439 |  |  |  |  |  |  | my $exit_value = 0; | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | print "Extra arguments: @extra\n" if(@extra); | 
| 442 |  |  |  |  |  |  | print "Will read from: ", $self->input_file, "\n"; | 
| 443 |  |  |  |  |  |  | print "Will write files to: ", $self->output_dir, "\n"; | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | if($self->dry_run) { | 
| 446 |  |  |  |  |  |  | die 'Will not run script'; | 
| 447 |  |  |  |  |  |  | } | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | return $self->generate_exit_value; | 
| 450 |  |  |  |  |  |  | }; | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | =head1 APPLICATION CLASS | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | This module will generate an application class, which C<$self> inside the | 
| 455 |  |  |  |  |  |  | L block refere to. This class will have: | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | =over 4 | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | =item * new() | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | An object constructor. This method will not be auto generated if any of | 
| 462 |  |  |  |  |  |  | the classes given to L has the method C. | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | =item * run() | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | This method is basically the code block given to L. | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | =item * Other methods | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | Other methods defined in the script file will be accesible from C<$self> | 
| 471 |  |  |  |  |  |  | inside C. | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | =item * _script() | 
| 474 |  |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | This is an accessor which return the L object which | 
| 476 |  |  |  |  |  |  | is refered to as C<$self> in this documentation. | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | NOTE: This accessor starts with an underscore to prevent conflicts | 
| 479 |  |  |  |  |  |  | with L. | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | =item * Other accessors | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | Any L (application switch) will be available as an accessor on the | 
| 484 |  |  |  |  |  |  | application object. | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | =back | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | =head1 EXPORTED FUNCTIONS | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | =head2 option | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | option $type => $name => $documentation; | 
| 493 |  |  |  |  |  |  | option $type => $name => $documentation, $default; | 
| 494 |  |  |  |  |  |  | option $type => $name => $documentation, $default, @args; | 
| 495 |  |  |  |  |  |  | option $type => $name => $documentation, @args; | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | This function is used to define options which can be given to this | 
| 498 |  |  |  |  |  |  | application. See L for example code. This function can also be | 
| 499 |  |  |  |  |  |  | called as a method on C<$self>. | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | =over 4 | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | =item * $type | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | Used to define value types for this input. | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | =over 4 | 
| 508 |  |  |  |  |  |  |  | 
| 509 |  |  |  |  |  |  | =item bool, flag | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | =item inc | 
| 512 |  |  |  |  |  |  |  | 
| 513 |  |  |  |  |  |  | =item str | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | =item int | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | =item num | 
| 518 |  |  |  |  |  |  |  | 
| 519 |  |  |  |  |  |  | =item file (TODO) | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  | =item dir (TODO) | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | =back | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | =item * $name | 
| 526 |  |  |  |  |  |  |  | 
| 527 |  |  |  |  |  |  | The name of an application switch. This name will also be used as | 
| 528 |  |  |  |  |  |  | accessor name inside the application. Example: | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | # define an application switch: | 
| 531 |  |  |  |  |  |  | option file => some_file => '...'; | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | # call the application from command line: | 
| 534 |  |  |  |  |  |  | > myapp.pl --some-file /foo/bar | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | # run the application code: | 
| 537 |  |  |  |  |  |  | app { | 
| 538 |  |  |  |  |  |  | my $self = shift; | 
| 539 |  |  |  |  |  |  | print $self->some_file # prints "/foo/bar" | 
| 540 |  |  |  |  |  |  | return 0; | 
| 541 |  |  |  |  |  |  | }; | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | =item * C<$documentation> | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | Used as description text when printing the usage text. | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | =item * C<@args> | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | =over 4 | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | =item * C | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | The script will not start if a required field is omitted. | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | =item * C | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | Allow the option to hold a list of values. Examples: "@", "4", "1,3". | 
| 558 |  |  |  |  |  |  | See L for details. | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | =item * C | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | Specify the class an option should be instantiated as. Example: | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | option file => output => "output file", isa => "Mojo::File"; | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | The C | 
| 567 |  |  |  |  |  |  | instead of just a plain string. | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | =item * Other | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | Any other L attribute argument may/will be supported in | 
| 572 |  |  |  |  |  |  | future release. | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | =back | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | =back | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | =head2 documentation | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | documentation __FILE__; # current file | 
| 581 |  |  |  |  |  |  | documentation '/path/to/file'; | 
| 582 |  |  |  |  |  |  | documentation 'Some::Module'; | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | Specifies where to retrieve documentaion from when giving the C<--man> | 
| 585 |  |  |  |  |  |  | switch to your script. | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | =head2 version | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | version 'Some::Module'; | 
| 590 |  |  |  |  |  |  | version $num; | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | Specifies where to retrieve the version number from when giving the | 
| 593 |  |  |  |  |  |  | C<--version> switch to your script. | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | =head2 extends | 
| 596 |  |  |  |  |  |  |  | 
| 597 |  |  |  |  |  |  | extends @classes; | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | Specify which classes this application should inherit from. These | 
| 600 |  |  |  |  |  |  | classes can be L based. | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | =head2 subcommand | 
| 603 |  |  |  |  |  |  |  | 
| 604 |  |  |  |  |  |  | subcommand list => 'provide a listing objects' => sub { | 
| 605 |  |  |  |  |  |  | option flag => long => 'long listing'; | 
| 606 |  |  |  |  |  |  | option flag => recursive => 'recursively list objects'; | 
| 607 |  |  |  |  |  |  | }; | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | subcommand create => 'create a new object' => sub { | 
| 610 |  |  |  |  |  |  | option str => name => 'name of new object', required => 1; | 
| 611 |  |  |  |  |  |  | option str => description => 'description for the object', required => 1; | 
| 612 |  |  |  |  |  |  | }; | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | sub command_create { | 
| 615 |  |  |  |  |  |  | my ($self, @extra) = @_; | 
| 616 |  |  |  |  |  |  | ## do creating | 
| 617 |  |  |  |  |  |  | return 0; | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  |  | 
| 620 |  |  |  |  |  |  | sub command_list { | 
| 621 |  |  |  |  |  |  | my ($self, @extra) = @_; | 
| 622 |  |  |  |  |  |  | ## do listing | 
| 623 |  |  |  |  |  |  | return 0; | 
| 624 |  |  |  |  |  |  | } | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | app { | 
| 627 |  |  |  |  |  |  | my ($self, @extra) = @_; | 
| 628 |  |  |  |  |  |  | ## fallback when no command given. | 
| 629 |  |  |  |  |  |  | $self->_script->print_help; | 
| 630 |  |  |  |  |  |  | return 0; | 
| 631 |  |  |  |  |  |  | }; | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | This function allows for creating multiple related sub commands within the same | 
| 634 |  |  |  |  |  |  | script in a similar fashion to C. The L, L and | 
| 635 |  |  |  |  |  |  | L exported functions may sensibly be called within the | 
| 636 |  |  |  |  |  |  | subroutine. Calling the function with no arguments will return the running | 
| 637 |  |  |  |  |  |  | subcommand, i.e. a valid C<$ARGV[0]>. Non valid values for the subcommand given | 
| 638 |  |  |  |  |  |  | on the command line will result in the help being displayed. | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | =head2 app | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | app CODE; | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  | This function will define the code block which is called when the application | 
| 645 |  |  |  |  |  |  | is started. See L for example code. This function can also be | 
| 646 |  |  |  |  |  |  | called as a method on C<$self>. | 
| 647 |  |  |  |  |  |  |  | 
| 648 |  |  |  |  |  |  | IMPORTANT: This function must be the last function called in the script file | 
| 649 |  |  |  |  |  |  | for unittests to work. Reason for this is that this function runs the | 
| 650 |  |  |  |  |  |  | application in void context (started from command line), but returns the | 
| 651 |  |  |  |  |  |  | application object in list/scalar context (from L). | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | =head1 ATTRIBUTES | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | =head2 options | 
| 656 |  |  |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | $array_ref = $self->options; | 
| 658 |  |  |  |  |  |  |  | 
| 659 |  |  |  |  |  |  | Holds the application options given to L. | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | =head1 METHODS | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | =head2 new | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | $self = $class->new({ options => $array_ref, ... }); | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | Object constructor. Creates a new object representing the script meta | 
| 668 |  |  |  |  |  |  | information. | 
| 669 |  |  |  |  |  |  |  | 
| 670 |  |  |  |  |  |  | =head2 print_help | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  | Will print L to selected filehandle (STDOUT by default) in | 
| 673 |  |  |  |  |  |  | a normalized matter. Example: | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | Usage: | 
| 676 |  |  |  |  |  |  | --foo      Foo does this and that | 
| 677 |  |  |  |  |  |  | * --bar      Bar does something else | 
| 678 |  |  |  |  |  |  |  | 
| 679 |  |  |  |  |  |  | --help     Print this help text | 
| 680 |  |  |  |  |  |  | --man      Display manual for this application | 
| 681 |  |  |  |  |  |  | --version  Print application name and version | 
| 682 |  |  |  |  |  |  |  | 
| 683 |  |  |  |  |  |  | =head2 print_version | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  | Will print L to selected filehandle (STDOUT by default) in | 
| 686 |  |  |  |  |  |  | a normalized matter. Example: | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | some-script.pl version 1.23 | 
| 689 |  |  |  |  |  |  |  | 
| 690 |  |  |  |  |  |  | =head2 import | 
| 691 |  |  |  |  |  |  |  | 
| 692 |  |  |  |  |  |  | Will export the functions listed under L. The functions | 
| 693 |  |  |  |  |  |  | will act on a L object created by this method. | 
| 694 |  |  |  |  |  |  |  | 
| 695 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | This library is free software. You can redistribute it and/or modify | 
| 698 |  |  |  |  |  |  | it under the same terms as Perl itself. | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | =head1 AUTHORS | 
| 701 |  |  |  |  |  |  |  | 
| 702 |  |  |  |  |  |  | Jan Henning Thorsen - C | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | Roy Storey - C | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | =cut |