File Coverage

blib/lib/App/Yath/Script.pm
Criterion Covered Total %
statement 116 164 70.7
branch 43 80 53.7
path n/a
condition 15 28 53.5
subroutine 16 19 84.2
pod 8 13 61.5
total 198 304 65.1


line stmt bran path cond sub pod time code
1               package App::Yath::Script;
2 14       14   206719 use strict;
  14           35  
  14           532  
3 14       14   66 use warnings;
  14           20  
  14           806  
4                
5 14       14   73 use Cwd qw/realpath/;
  14           27  
  14           966  
6 14       14   84 use Carp qw/confess/;
  14           18  
  14           841  
7 14       14   96 use File::Spec();
  14           46  
  14           492  
8                
9 14       14   8737 use Importer Importer => 'import';
  14           90821  
  14           115  
10                
11               our @EXPORT_OK = (
12               qw{
13               script
14               module
15                
16               do_exec
17                
18               clean_path
19               find_in_updir
20               find_rc_updir
21               mod2file
22               },
23               );
24                
25               our $VERSION = '2.000011';
26                
27               our ($SCRIPT, $MOD);
28                
29 0       0 1 0 sub script { $SCRIPT }
30 0       0 1 0 sub module { $MOD }
31                
32               sub do_begin {
33               # Check for an explicit version as the very first argument (V# or v#).
34               # Strip it from @ARGV before anything else sees it.
35 13       13 1 25 my $cli_version;
36 13 100   100     151 if (@ARGV && $ARGV[0] =~ /^[Vv](\d+)$/) {
37 4           19 $cli_version = int($1);
38 4           8 shift @ARGV;
39               }
40                
41 13           41 my $argv = [@ARGV];
42 13           59 my @caller = caller();
43                
44 13           34 my $exec = 0;
45                
46 13           85 $SCRIPT = clean_path($caller[1]);
47 13           224 $ENV{YATH_SCRIPT} = $SCRIPT;
48                
49 13           46 inject_includes();
50                
51 13 50         29 $exec = 1 if seed_hash();
52 13 50         34 $exec = 1 if find_alt_script();
53 13 50         39 $exec = 1 if parse_new_dev_libs();
54                
55 13 50         41 do_exec($argv) if $exec;
56                
57 13           22 my $version;
58 13           66 my ($config, $user_config);
59                
60 13 100         106 if (defined $cli_version) {
61               # Explicit version on CLI -- only look for versioned RC files.
62               # Accept both .yath.v#.rc and .yath.V#.rc.
63 4     66     15 $config = find_in_updir(".yath.v${cli_version}.rc") // find_in_updir(".yath.V${cli_version}.rc");
64 4     33     11 $user_config = find_in_updir(".yath.user.v${cli_version}.rc") // find_in_updir(".yath.user.V${cli_version}.rc");
65 4           11 $version = $cli_version;
66               }
67               else {
68 9           17 my $config_version;
69 9           23 ($config, $config_version) = find_rc_updir('.yath');
70                
71 9           23 my $user_version;
72 9           83 ($user_config, $user_version) = find_rc_updir('.yath.user');
73                
74               # .yath.user(.v#).rc version takes precedence over .yath(.v#).rc
75 9     33     63 $version = $user_version // $config_version;
76               }
77                
78 13 50         46 if (defined $version) {
79 13 50         650 warn "Warning: Version '0' is for validating the yath script only, it should not be used for any real testing.\n"
80               if $version == 0;
81                
82 13           59 $MOD = "App::Yath::Script::V${version}";
83                
84 13           55 my $file = mod2file($MOD);
85 13 50         35 eval { require $file; 1 } or die "Could not load $MOD: $@";
  13           5734  
  13           87  
86               }
87               else {
88               # No config file found -- scan @INC for available V# modules and
89               # try the highest version first so we default to the latest.
90 0           0 my %found;
91 0           0 for my $inc (@INC) {
92 0 0         0 next if ref $inc;
93 0           0 my $dir = File::Spec->catdir($inc, 'App', 'Yath', 'Script');
94 0 0         0 next unless -d $dir;
95 0 0         0 opendir(my $dh, $dir) or next;
96 0           0 for my $entry (readdir $dh) {
97 0 0         0 $found{$1} = 1 if $entry =~ /^V(\d+)\.pm$/;
98               }
99 0           0 closedir $dh;
100               }
101                
102               # V0 is for script validation only, never auto-select it
103 0           0 delete $found{0};
104                
105 0           0 my @err;
106 0           0 for my $v (sort { $b <=> $a } keys %found) {
  0           0  
107 0           0 my $mod = "App::Yath::Script::V${v}";
108                
109 0           0 my $file = mod2file($mod);
110 0 0         0 if (eval { require $file; 1 }) {
  0           0  
  0           0  
111 0           0 $MOD = $mod;
112 0           0 last;
113               }
114                
115 0           0 push @err => $@;
116               }
117                
118 0 0         0 die join "\n" => (
119               "No Test2::Harness (App::Yath) versions appear to be installed...",
120               @err,
121               ) unless $MOD;
122               }
123                
124 13 50         44 die "Could not find a App::Yath::Script::V{X} module to use...\n"
125               unless $MOD;
126                
127 13           94 $MOD->do_begin(
128               script => $SCRIPT,
129               argv => $argv,
130               config => $config,
131               user_config => $user_config,
132               );
133               }
134                
135 13       13 1 120 sub do_runtime { $MOD->do_runtime(@_) }
136                
137               sub do_exec {
138 0       0 1 0 my ($argv) = @_;
139 0           0 $ENV{T2_HARNESS_INCLUDES} = join ';' => @INC;
140 0           0 exec($^X, $SCRIPT, @$argv);
141               }
142                
143               sub find_alt_script {
144 13       13 0 24 my $script = './scripts/yath';
145 13 50         554 return 0 unless -f $script;
146 0 0         0 return 0 unless -x $script;
147                
148 0           0 $script = clean_path($script);
149                
150 0 0         0 return 0 if $script eq clean_path($SCRIPT);
151                
152 0           0 $SCRIPT = $script;
153                
154 0           0 return 1;
155               }
156                
157               sub parse_new_dev_libs {
158 16       16 0 2484 my @add;
159 16           36 for my $arg (@ARGV) {
160 32 100         103 last if $arg eq '::';
161 31 100         106 last if $arg eq '--';
162                
163 30 50         138 next unless $arg =~ m/^(?:-D|--dev-libs?)(?:=(.+))?$/;
164 0           0 my $arg = $1;
165                
166 0 0         0 unless ($arg) {
167 0           0 push @add => map { clean_path($_) } 'lib', 'blib/lib', 'blib/arch';
  0           0  
168 0           0 next;
169               }
170                
171 0           0 for my $path (split /,/, $arg) {
172 0 0         0 if ($path =~ m/\*/) {
173 0           0 push @add => glob($path);
174               }
175               else {
176 0           0 push @add => $path;
177               }
178               }
179               }
180                
181 16 50         72 return 0 unless @add;
182                
183 0           0 my %seen = map { ($_ => 1, clean_path($_) => 1) } @INC;
  0           0  
184 0     0     0 @add = grep { !($seen{$_} || $seen{clean_path($_)}) } @add;
  0           0  
185 0 0         0 return 0 unless @add;
186                
187 0           0 unshift @INC => @add;
188 0           0 return 1;
189               }
190                
191               sub inject_includes {
192 15 100     15 0 4284 return unless $ENV{T2_HARNESS_INCLUDES};
193 2           13 @INC = split /;/, $ENV{T2_HARNESS_INCLUDES};
194               }
195                
196               sub seed_hash {
197 15 100     15 0 1689 return 0 if $ENV{PERL_HASH_SEED};
198                
199 1           30 my @ltime = localtime;
200 1           6 my $seed = sprintf('%04d%02d%02d', 1900 + $ltime[5], 1 + $ltime[4], $ltime[3]);
201 1           4 print "PERL_HASH_SEED not set, setting to '$seed' for more reproducible results.\n";
202                
203 1           5 $ENV{PERL_HASH_SEED} = $seed;
204                
205 1           6 return 1;
206               }
207                
208               sub clean_path {
209 22       22 1 3079 my ( $path, $absolute ) = @_;
210                
211 22 100         532 confess "No path was provided to clean_path()" unless $path;
212                
213 20     100     143 $absolute //= 1;
214 20 100   33     940 $path = realpath($path) // $path if $absolute;
215                
216 20           1051 return File::Spec->rel2abs($path);
217               }
218                
219               sub find_rc_updir {
220 31       31 0 35322 my ($prefix) = @_;
221                
222 31           972 my $versioned_pattern = qr/^\Q$prefix\E\.[Vv](\d+)\.rc$/;
223 31           102 my $plain_name = "$prefix.rc";
224                
225 31           49 my $abs = eval { realpath(File::Spec->rel2abs('.')) };
  31           1343  
226 31           68 my %seen;
227 31     66     296 while ($abs && !$seen{$abs}++) {
228               # Priority 1: plain name that is a symlink to a versioned file.
229 52           451 my $plain_path = File::Spec->catfile($abs, $plain_name);
230 52 100   66     2572 if (-l $plain_path && -f $plain_path) {
231 5     50     56 my $target = readlink($plain_path) // '';
232 5 50         112 if ((File::Spec->splitpath($target))[2] =~ $versioned_pattern) {
233 5           40 return ($plain_path, int($1));
234               }
235               }
236                
237               # Priority 2: explicitly versioned file (.yath.v#.rc).
238 47 50         1322 if (opendir(my $dh, $abs)) {
239 47           3909 for my $entry (readdir $dh) {
240 379 100         1071 if ($entry =~ $versioned_pattern) {
241 14           53 my $v = int($1);
242 14           199 my $path = File::Spec->catfile($abs, $entry);
243 14           152 closedir $dh;
244 14           130 return ($path, $v);
245               }
246               }
247 33           374 closedir $dh;
248               }
249                
250               # Priority 3: plain unversioned file, default to V1.
251 33 100         325 if (-f $plain_path) {
252 2           23 return ($plain_path, 1);
253               }
254                
255 31           68 $abs = eval { realpath(File::Spec->catdir($abs, '..')) };
  31           723  
256               }
257                
258 10           54 return;
259               }
260                
261               sub find_in_updir {
262 15       15 1 2803 my $path = shift;
263 15 100         473 return clean_path($path) if -e $path;
264                
265 10           22 my %seen;
266 10           14 while(1) {
267 32           243 $path = File::Spec->catdir('..', $path);
268 32           53 my $check = eval { realpath(File::Spec->rel2abs($path)) };
  32           1733  
269 32 50         91 last unless $check;
270 32 100         100 last if $seen{$check}++;
271 22 50         114 return $check if -e $check;
272               }
273                
274 10           60 return;
275               }
276                
277               sub mod2file {
278 18       18 1 173002 my ($mod) = @_;
279 18 100         545 confess "No module name provided" unless $mod;
280 16           46 my $file = $mod;
281 16           107 $file =~ s{::}{/}g;
282 16           28 $file .= ".pm";
283 16           54 return $file;
284               }
285                
286               1;
287                
288               __END__