File Coverage

blib/lib/App/FatPacker/Simple.pm
Criterion Covered Total %
statement 42 161 26.0
branch 0 50 0.0
condition 0 6 0.0
subroutine 14 30 46.6
pod 0 11 0.0
total 56 258 21.7


line stmt bran cond sub pod time code
1             package App::FatPacker::Simple;
2 1     1   12898 use strict;
  1         3  
  1         23  
3 1     1   4 use warnings;
  1         2  
  1         20  
4 1     1   445 use utf8;
  1         15  
  1         4  
5 1     1   383 use App::FatPacker;
  1         14794  
  1         6  
6 1     1   31 use Config;
  1         2  
  1         32  
7 1     1   6 use Cwd 'abs_path';
  1         2  
  1         32  
8 1     1   411 use Distribution::Metadata;
  1         39635  
  1         31  
9 1     1   7 use File::Basename 'basename';
  1         2  
  1         54  
10 1     1   6 use File::Find 'find';
  1         2  
  1         29  
11 1     1   5 use File::Spec::Functions 'catdir';
  1         2  
  1         47  
12 1     1   4 use File::Spec::Unix;
  1         2  
  1         21  
13 1     1   4 use Getopt::Long qw(:config no_auto_abbrev no_ignore_case);
  1         2  
  1         7  
14 1     1   591 use Perl::Strip;
  1         86119  
  1         31  
15 1     1   414 use Pod::Usage 'pod2usage';
  1         34022  
  1         1314  
16              
17             our $VERSION = '0.08';
18              
19             our $IGNORE_FILE = [
20             qr/\.pod$/,
21             qr/\.packlist$/,
22             qr/MYMETA\.json$/,
23             qr/install\.json$/,
24             ];
25              
26             sub new {
27 0     0 0   my $class = shift;
28 0           bless {@_}, $class;
29             }
30              
31             sub parse_options {
32 0     0 0   my $self = shift;
33 0           local @ARGV = @_;
34             GetOptions
35             "d|dir=s" => \(my $dir = 'lib,fatlib,local,extlib'),
36             "e|exclude=s" => \(my $exclude),
37 0     0     "h|help" => sub { pod2usage(0) },
38             "o|output=s" => \(my $output),
39             "q|quiet" => \(my $quiet),
40             "s|strict" => \(my $strict),
41 0     0     "v|version" => sub { printf "%s %s\n", __PACKAGE__, __PACKAGE__->VERSION; exit },
  0            
42 0 0         "color!" => \(my $color = 1),
43             "shebang=s" => \(my $custom_shebang),
44             "exclude-strip=s@" => \(my $exclude_strip),
45             "no-strip|no-perl-strip" => \(my $no_perl_strip),
46             or pod2usage(1);
47 0 0         $self->{script} = shift @ARGV or do { warn "Missing scirpt.\n"; pod2usage(1) };
  0            
  0            
48 0           $self->{dir} = $self->build_dir($dir);
49 0           $self->{output} = $output;
50 0           $self->{quiet} = $quiet;
51 0           $self->{strict} = $strict;
52 0           $self->{color} = $color;
53 0 0         $self->{perl_strip} = $no_perl_strip ? undef : Perl::Strip->new;
54 0           $self->{custom_shebang} = $custom_shebang;
55 0 0         $self->{exclude_strip} = [map { qr/$_/ } @{$exclude_strip || []}];
  0            
  0            
56 0           $self->{exclude} = [];
57 0 0         if ($exclude) {
58 0           for my $e (split /,/, $exclude) {
59             my $dist = Distribution::Metadata->new_from_module(
60             $e, inc => $self->{dir},
61 0           );
62 0 0         if (my $files = $dist->files) {
63 0           push @{$self->{exclude}}, @$files;
  0            
64             } else {
65 0           $self->warning("Missing $e in $dir");
66             }
67             }
68             }
69 0           $self;
70             }
71              
72             sub warning {
73 0     0 0   my ($self, $msg) = @_;
74 0           chomp $msg;
75             my $color = $self->{color}
76 0     0     ? sub { "\e[31m$_[0]\e[m", "\n" }
77 0 0   0     : sub { "$_[0]\n" };
  0            
78 0 0         if ($self->{strict}) {
    0          
79 0           die $color->("=> ERROR $msg");
80             } elsif (!$self->{quiet}) {
81 0           warn $color->("=> WARN $msg");
82             }
83             }
84              
85             sub debug {
86 0     0 0   my ($self, $msg) = @_;
87 0           chomp $msg;
88 0 0         if (!$self->{quiet}) {
89 0           warn "-> $msg\n";
90             }
91             }
92              
93             sub output_filename {
94 0     0 0   my $self = shift;
95 0 0         return $self->{output} if $self->{output};
96              
97 0           my $script = basename $self->{script};
98 0           my ($suffix, @other) = reverse split /\./, $script;
99 0 0         if (!@other) {
100 0           "$script.fatpack";
101             } else {
102 0           unshift @other, "fatpack";
103 0           join ".", reverse(@other), $suffix;
104             }
105             }
106              
107             sub run {
108 0     0 0   my $self = shift;
109 0           my $fatpacked = $self->fatpack_file($self->{script});
110 0           my $output_filename = $self->output_filename;
111 0 0         open my $fh, ">", $output_filename
112             or die "Cannot open '$output_filename': $!\n";
113 0           print {$fh} $fatpacked;
  0            
114 0           close $fh;
115 0           my $mode = (stat $self->{script})[2];
116 0           chmod $mode, $output_filename;
117 0           $self->debug("Successfully created $output_filename");
118             }
119              
120             # In order not to depend on App::FatPacker internals,
121             # we use only App::FatPacker::fatpack_code method.
122             sub fatpack_file {
123 0     0 0   my ($self, $file) = @_;
124 0           my ($shebang, $script) = $self->load_main_script($file);
125 0 0         $shebang = $self->{custom_shebang} if $self->{custom_shebang};
126 0           my %files;
127 0           $self->collect_files($_, \%files) for @{ $self->{dir} };
  0            
128 0           my $fatpacker = App::FatPacker->new;
129 0           return join "\n", $shebang, $fatpacker->fatpack_code(\%files), $script;
130             }
131              
132             # almost copy from App::FatPacker::load_main_script
133             sub load_main_script {
134 0     0 0   my ($self, $file) = @_;
135 0 0         open my $fh, "<", $file or die "Cannot open '$file': $!\n";
136 0           my @lines = <$fh>;
137 0           my @shebang;
138 0 0 0       if (@lines && index($lines[0], '#!') == 0) {
139 0           while (1) {
140 0           push @shebang, shift @lines;
141 0 0         last if $shebang[-1] =~ m{^\#\!.*perl};
142             }
143             }
144 0           ((join "", @shebang), (join "", @lines));
145             }
146              
147             sub load_file {
148 0     0 0   my ($self, $absolute, $relative, $original) = @_;
149              
150 0           my $content = do {
151 0 0         open my $fh, "<", $absolute or die "Cannot open '$absolute': $!\n";
152 0           local $/; <$fh>;
  0            
153             };
154              
155 0 0 0       if ($self->{perl_strip} and !grep { $original =~ $_ } @{$self->{exclude_strip}}) {
  0            
  0            
156 0           $self->debug("fatpack $relative (with perl-strip)");
157 0           return $self->{perl_strip}->strip($content);
158             } else {
159 0           $self->debug("fatpack $relative (without perl-strip)");
160 0           return $content;
161             }
162             }
163              
164             sub collect_files {
165 0     0 0   my ($self, $dir, $files) = @_;
166              
167 0           my $absolute_dir = abs_path($dir);
168             # When $dir is not an archlib,
169             # and we are about to search $dir/archlib, skip it!
170             # because $dir/archlib itself will be searched another time.
171 0           my $skip_dir = catdir($absolute_dir, $Config{archname});
172 0           $skip_dir = qr/\Q$skip_dir\E/;
173              
174             my $find = sub {
175 0 0   0     return unless -f $_;
176 0           for my $ignore (@$IGNORE_FILE) {
177 0 0         $_ =~ $ignore and return;
178             }
179 0           my $original = $_;
180 0           my $absolute = abs_path($original);
181 0 0         return if $absolute =~ $skip_dir;
182 0           my $relative = File::Spec::Unix->abs2rel($absolute, $absolute_dir);
183 0           for my $exclude (@{$self->{exclude}}) {
  0            
184 0 0         if ($absolute eq $exclude) {
185 0           $self->debug("exclude $relative");
186 0           return;
187             }
188             }
189 0 0         if (!/\.(?:pm|ix|al|pl)$/) {
190 0           $self->warning("skip non perl module file $relative");
191 0           return;
192             }
193 0           $files->{$relative} = $self->load_file($absolute, $relative, $original);
194 0           };
195 0           find({wanted => $find, no_chdir => 1}, $dir);
196             }
197              
198             sub build_dir {
199 0     0 0   my ($self, $dir_string) = @_;
200 0           my @dir;
201 0           for my $d (grep -d, split /,/, $dir_string) {
202 0           my $try = catdir($d, "lib/perl5");
203 0 0         if (-d $try) {
204 0           push @dir, $try, catdir($try, $Config{archname});
205             } else {
206 0           push @dir, $d, catdir($d, $Config{archname});
207             }
208             }
209 0           return [ grep -d, @dir ];
210             }
211              
212             1;
213             __END__