File Coverage

blib/lib/App/FatPacker/Simple.pm
Criterion Covered Total %
statement 38 165 23.0
branch 0 52 0.0
condition 0 6 0.0
subroutine 13 30 43.3
pod 0 12 0.0
total 51 265 19.2


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