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