File Coverage

bin/implode
Criterion Covered Total %
statement 111 141 78.7
branch 25 52 48.0
condition 11 27 40.7
subroutine 30 33 90.9
pod n/a
total 177 253 69.9


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package App::implode::cli;
3 2     2   766 use strict;
  2         4  
  2         73  
4 2     2   8 use warnings;
  2         2  
  2         44  
5 2     2   1261 use Archive::Tar;
  2         169571  
  2         119  
6 2     2   15 use Cwd 'abs_path';
  2         2  
  2         76  
7 2     2   941 use Carton ();
  2         4148  
  2         34  
8 2     2   808 use Carton::Builder;
  2         57513  
  2         72  
9 2     2   912 use Carton::Environment;
  2         205742  
  2         66  
10 2     2   865 use Carton::Mirror;
  2         1719  
  2         58  
11 2     2   11 use File::Basename 'basename';
  2         2  
  2         129  
12 2     2   12 use File::Find ();
  2         3  
  2         30  
13 2     2   11 use File::Path ();
  2         3  
  2         39  
14 2     2   1084 use File::Spec::Functions qw( catdir catfile );
  2         1264  
  2         140  
15 2     2   3306 use File::Temp 'tempdir';
  2         16628  
  2         146  
16 2     2   17 use IO::Compress::Bzip2;
  2         2  
  2         3079  
17              
18             our $PATH = abs_path(__FILE__);
19              
20             sub DESTROY {
21 4     4   2054 my $self = shift;
22 4 100       59 return chdir $self->[0] if UNIVERSAL::isa($self, 'ARRAY');
23 2 50 33     60 File::Path::remove_tree($self->{tmpdir}) if $self->{cleanup} and $self->{tmpdir};
24             }
25              
26 0   0 0   0 sub mirror { shift->{mirror} ||= Carton::Mirror->new($ENV{PERL_CARTON_MIRROR} || $Carton::Mirror::DefaultMirror) }
      0        
27 1 0 33 1   4 sub tmpdir { shift->{tmpdir} //= tempdir(CLEANUP => $ENV{IMPLODE_NO_CLEANUP} ? 0 : 1) }
28 3 50 66 3   16 sub verbose { shift->{verbose} //= $ENV{APP_IMPLODE_VERBOSE} ? 0 : -t STDOUT }
29              
30             sub bundle {
31 0     0   0 my $self = shift;
32 0         0 my $script = $self->slurp($self->{script});
33 0         0 my $exploder = $self->code('exploder');
34 0         0 my $id = basename $self->{out};
35              
36 0         0 $exploder =~ s!^sub.*\@_;!BEGIN{my \$id='$id';!s;
37 0         0 $exploder =~ s!^\s+!!mg;
38 0         0 $exploder =~ s!\n!!g;
39              
40 0 0       0 open my $OUT, '>', $self->{out} or die "Could not write $self->{out}: $!\n";
41 0 0       0 warn sprintf "Generating $self->{out} with embedded bzip archive...\n" if $self->verbose;
42 0 0       0 print $OUT $script =~ s/^(#!.+?[\r\n]+)//m ? $1 : "#!/usr/bin/perl\n";
43 0         0 print $OUT $exploder, "\n", $script, "\n__END__\n";
44 0         0 $self->tarball->write(IO::Compress::Bzip2->new($OUT), COMPRESS_GZIP);
45 0         0 close $OUT;
46 0         0 chmod 0755, $self->{out};
47 0 0       0 warn sprintf "$self->{out} is generated.\n" if $self->verbose;
48             }
49              
50             sub chdir {
51 2     2   2 my $self = shift;
52 2         12 my $guard = bless [abs_path], ref($self);
53 2 50       24 chdir $_[0] or die "chdir $_[0]: $!";
54 2         4 $guard;
55             }
56              
57             sub code {
58 1     1   2 my ($self, $name) = @_;
59 1 50       31 open my $SELF, '<', $PATH or die "Read $PATH: $!";
60 1         86 return join '', grep { /^sub $name/ .. /^\}/ } <$SELF>;
  159         208  
61             }
62              
63             sub deps {
64 0     0   0 my $self = shift;
65 0         0 my $env = Carton::Environment->build('cpanfile', $self->tmpdir);
66 0         0 my $builder = Carton::Builder->new(mirror => $self->mirror, cpanfile => $env->cpanfile);
67              
68 0 0       0 $self->dir_is_empty($self->tmpdir) or die "Cannot build $self->{script}: @{[$env->install_path]} already exists.\n";
  0         0  
69 0         0 $self->{cleanup} = 1;
70 0         0 $builder->install($env->install_path);
71             }
72              
73             sub dir_is_empty {
74 3     3   300 my ($self, $dir) = @_;
75 3 100       69 opendir(my $DH, $dir) or return 1;
76 2         26 not scalar grep {/\w/} readdir $DH;
  9         34  
77             }
78              
79             sub exploder {
80 1     1   464 my ($self, $id) = @_;
81 1         6 require Archive::Tar;
82 1         4 require File::Path;
83 1         3 require File::Spec;
84 1         4 require IO::Uncompress::Bunzip2;
85 1     1   1418 sub App::implode::temp::DESTROY { File::Path::remove_tree(${$_[0]}) }
  1         625  
86 1   33     101 $App::implode::explodedir
87             = bless \($ENV{APP_EXPLODE_DIR} || File::Spec->catdir(File::Spec->tmpdir, "app-implode-$id")), 'App::implode::temp';
88 1 50       7 warn "[App::implode] cd $$App::implode::explodedir; tar -xfz $0\n" if $ENV{APP_EXPLODE_VERBOSE};
89 1         8 my $tar = Archive::Tar->new;
90             $tar->read(
91             IO::Uncompress::Bunzip2->new(
92 1         14 do {
93 1         27 open my $FH, '<', $0;
94 1         2 my $m = 0;
95 1 100 100     61 \join '', grep { $m++ if /^__END__\r?\n/ || $m; $m > 1; } <$FH>;
  44         124  
  44         62  
96             }
97             )
98             );
99 1         3405 $tar->setcwd($$App::implode::explodedir);
100 1 50       10 $tar->extract or die "[App::implode] tar -xfz $0 failed: @{[$tar->error]}";
  0         0  
101 1         1399 unshift @INC, File::Spec->catdir($$App::implode::explodedir, 'lib', 'perl5');
102 1         9 $ENV{PATH} = join ':', grep {defined} File::Spec->catdir($$App::implode::explodedir, 'bin'), $ENV{PATH};
  2         8  
103 1         37 $ENV{PERL5LIB} = join ':', @INC;
104             }
105              
106             sub slurp {
107 1     1   2 my ($self, $file) = @_;
108 1 50       22 open my $FH, '<', $file or die "Could not read $file: $!\n";
109 1         3 local $/;
110 1         22 readline $FH;
111             }
112              
113             sub tarball {
114 1     1   1 my $self = shift;
115 1         2 my $chdir = $self->chdir($self->tmpdir);
116 1 100   8   3 my $chmod = sub { -f and chmod 0600 | (0777 & (stat _)[2]), $_ };
  8         346  
117 1 50   2   2 my $files = sub { @_ > 1 and File::Find::find({no_chdir => 1, wanted => shift}, @_) };
  2         109  
118 1         6 my $tar = Archive::Tar->new;
119              
120             $files->(
121             sub {
122 5 100   5   453 return unless $chmod->();
123 2 50       8 warn sprintf "Add @{[catfile $self->{tmpdir}, $_]}\n" if $self->verbose;
  0         0  
124 2         8 $tar->add_files($_);
125             },
126 1         13 grep {-d} qw( bin lib )
  2         16  
127             );
128              
129 1         284 undef $chdir;
130             $files->(
131             sub {
132 3 100 66 3   4 return unless $chmod->() and s!lib!lib/perl5!;
133 1 50       4 warn sprintf "Add $_\n" if $self->verbose;
134 1         3 $tar->add_data($_, $self->slurp($File::Find::name));
135             },
136 1         3 grep {-d} qw( lib )
  1         9  
137             );
138              
139 1         184 return $tar;
140             }
141              
142             sub run {
143 2     2   717 my $self = shift;
144              
145 2 100       14 $self->{script} = shift or die "Usage: implode myapp.pl [path/to/outfile.pl]\n\n";
146 1   33     50 $self->{out} = shift || basename $self->{script};
147 1 50       31 -r $self->{script} or die "Cannot read '$self->{script}'.\n";
148 0 0         -e $self->{out} and die "Outfile '$self->{out}' already exists.\n";
149              
150 0 0         warn sprintf "Building application in %s\n", $self->tmpdir if $self->verbose;
151 0           $self->deps;
152 0           $self->bundle;
153              
154 0           return 0;
155             }
156              
157             exit((bless {})->run(@ARGV)) unless defined wantarray;
158 2     2   12 no warnings;
  2         4  
  2         122  
159             'App::implode::cli';