File Coverage

blib/lib/App/FatPacker/Simple.pm
Criterion Covered Total %
statement 42 168 25.0
branch 0 52 0.0
condition 0 6 0.0
subroutine 14 31 45.1
pod 0 12 0.0
total 56 269 20.8


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