typo fixes
[p5sagit/App-FatPacker.git] / lib / App / FatPacker.pm
CommitLineData
48af1939 1package App::FatPacker;
2
3use strict;
4use warnings FATAL => 'all';
8572221e 5use 5.008001;
48af1939 6use Getopt::Long;
7use Cwd qw(cwd);
8use File::Find qw(find);
9use File::Spec::Functions qw(
10 catdir splitpath splitdir catpath rel2abs abs2rel
11);
cbd99f49 12use File::Spec::Unix;
48af1939 13use File::Copy qw(copy);
4d5603b7 14use File::Path qw(mkpath rmtree);
48af1939 15use B qw(perlstring);
16
3326f144 17our $VERSION = '0.010000'; # 0.10.0
f5a54fa1 18
19$VERSION = eval $VERSION;
20
48af1939 21sub call_parser {
24d68aa2 22 my $self = shift;
69667cc8 23 my ($args, $options) = @_;
24d68aa2 24
25 local *ARGV = [ @{$args} ];
69667cc8 26 $self->{option_parser}->getoptions(@$options);
24d68aa2 27
28 return [ @ARGV ];
48af1939 29}
30
31sub lines_of {
32 map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> };
33}
34
35sub stripspace {
36 my ($text) = @_;
37 $text =~ /^(\s+)/ && $text =~ s/^$1//mg;
38 $text;
39}
40
48af1939 41sub import {
24d68aa2 42 $_[1] && $_[1] eq '-run_script'
48af1939 43 and return shift->new->run_script;
44}
45
24d68aa2 46sub new {
47 bless {
48 option_parser => Getopt::Long::Parser->new(
49 config => [ qw(require_order pass_through bundling no_auto_abbrev) ]
50 ),
51 }, $_[0];
52}
48af1939 53
54sub run_script {
55 my ($self, $args) = @_;
56 my @args = $args ? @$args : @ARGV;
3e4dadce 57 (my $cmd = shift @args || 'help') =~ s/-/_/g;
24d68aa2 58
48af1939 59 if (my $meth = $self->can("script_command_${cmd}")) {
60 $self->$meth(\@args);
61 } else {
62 die "No such command ${cmd}";
63 }
64}
65
3e4dadce 66sub script_command_help {
67 print "Try `perldoc fatpack` for how to use me\n";
68}
69
cb3e6884 70sub script_command_pack {
71 my ($self, $args) = @_;
72
73 my @modules = split /\r?\n/, $self->trace(args => $args);
74 my @packlists = $self->packlists_containing(\@modules);
75
76 my $base = catdir(cwd, 'fatlib');
77 $self->packlists_to_tree($base, \@packlists);
78
79 my $file = shift @$args;
7dabafaa 80 print $self->fatpack_file($file);
cb3e6884 81}
82
48af1939 83sub script_command_trace {
84 my ($self, $args) = @_;
24d68aa2 85
69667cc8 86 $args = $self->call_parser($args => [
48af1939 87 'to=s' => \my $file,
88 'to-stderr' => \my $to_stderr,
3fdf85ca 89 'use=s' => \my @additional_use
69667cc8 90 ]);
48af1939 91
92 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
93
3fdf85ca 94 $file ||= 'fatpacker.trace';
69667cc8 95
3fdf85ca 96 if (!$to_stderr and -e $file) {
97 unlink $file or die "Couldn't remove old trace file: $!";
48af1939 98 }
99 my $arg = do {
3fdf85ca 100 if ($to_stderr) {
020d9b76 101 ">&STDERR"
3fdf85ca 102 } elsif ($file) {
276a30c9 103 ">>${file}"
48af1939 104 }
105 };
3fdf85ca 106
b4704b1a 107 $self->trace(
5e1de95d 108 use => \@additional_use,
109 args => $args,
7a3662c8 110 output => $arg,
b4704b1a 111 );
abd7cf01 112}
113
114sub trace {
b4704b1a 115 my ($self, %opts) = @_;
69667cc8 116
1ea23474 117 my $output = $opts{output};
69667cc8 118 my $trace_opts = join ',', $output||'>&STDOUT', @{$opts{use}||[]};
7a3662c8 119
69667cc8 120 local $ENV{PERL5OPT} = '-MApp::FatPacker::Trace='.$trace_opts;
abd7cf01 121
69667cc8 122 my @args = @{$opts{args}||[]};
e0cd89ab 123
69667cc8 124 if ($output) {
125 # user specified output target, JFDI
126 system $^X, @args;
127 return;
e0cd89ab 128 } else {
69667cc8 129 # no output target specified, slurp
9317190f 130 open my $out_fh, "$^X @args |";
69667cc8 131 return do { local $/; <$out_fh> };
132 }
48af1939 133}
134
135sub script_command_packlists_for {
136 my ($self, $args) = @_;
137 foreach my $pl ($self->packlists_containing($args)) {
138 print "${pl}\n";
139 }
140}
141
142sub packlists_containing {
143 my ($self, $targets) = @_;
144 my @targets = @$targets;
a976705b 145 foreach my $t (@targets) {
146 require $t;
147 }
48af1939 148 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
149 my %pack_rev;
0612cca1 150 find({
151 no_chdir => 1,
152 wanted => sub {
153 return unless /[\\\/]\.packlist$/ && -f $_;
154 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
155 },
48af1939 156 }, @search);
0612cca1 157 my %found; @found{map +($pack_rev{Cwd::abs_path($INC{$_})}||()), @targets} = ();
48af1939 158 sort keys %found;
159}
160
161sub script_command_tree {
162 my ($self, $args) = @_;
163 my $base = catdir(cwd,'fatlib');
164 $self->packlists_to_tree($base, $args);
165}
166
167sub packlists_to_tree {
168 my ($self, $where, $packlists) = @_;
4d5603b7 169 rmtree $where;
170 mkpath $where;
48af1939 171 foreach my $pl (@$packlists) {
172 my ($vol, $dirs, $file) = splitpath $pl;
173 my @dir_parts = splitdir $dirs;
174 my $pack_base;
175 PART: foreach my $p (0 .. $#dir_parts) {
176 if ($dir_parts[$p] eq 'auto') {
177 # $p-2 since it's <wanted path>/$Config{archname}/auto
178 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
179 last PART;
180 }
181 }
182 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
183 foreach my $source (lines_of $pl) {
184 # there is presumably a better way to do "is this under this base?"
185 # but if so, it's not obvious to me in File::Spec
186 next unless substr($source,0,length $pack_base) eq $pack_base;
187 my $target = rel2abs( abs2rel($source, $pack_base), $where );
188 my $target_dir = catpath((splitpath $target)[0,1]);
4d5603b7 189 mkpath $target_dir;
48af1939 190 copy $source => $target;
191 }
192 }
193}
194
195sub script_command_file {
196 my ($self, $args) = @_;
197 my $file = shift @$args;
9be5f3c0 198 print $self->fatpack_file($file);
199}
200
201sub fatpack_file {
202 my ($self, $file) = @_;
30c64724 203
204 my $shebang = "";
205 my $script = "";
206 if ( defined $file and -r $file ) {
207 ($shebang, $script) = $self->load_main_script($file);
208 }
209
210 my @dirs = $self->collect_dirs();
48af1939 211 my %files;
30c64724 212 $self->collect_files($_, \%files) for @dirs;
213
214 return join "\n", $shebang, $self->fatpack_code(\%files), $script;
215}
216
217# This method can be overload in sub classes
218# For example to skip POD
219sub load_file {
220 my ($self, $file) = @_;
221 my $content = do {
222 local (@ARGV, $/) = ($file);
223 <>
224 };
225 close ARGV;
226 return $content;
227}
228
229sub collect_dirs {
230 my ($self) = @_;
231 my $cwd = cwd;
232 return grep -d, map rel2abs($_, $cwd), ('lib','fatlib');
233}
234
235sub collect_files {
236 my ($self, $dir, $files) = @_;
237 find(sub {
238 return unless -f $_;
239 !/\.pm$/ and warn "File ${File::Find::name} isn't a .pm file - can't pack this -- if you hoped we were going to, things may not be what you expected later\n" and return;
240 $files->{File::Spec::Unix->abs2rel($File::Find::name,$dir)} =
241 $self->load_file($File::Find::name);
242 }, $dir);
243}
244
245sub load_main_script {
246 my ($self, $file) = @_;
247 open my $fh, "<", $file or die("Can't read $file: $!");
248 my $shebang = <$fh>;
249 my $script = join "", <$fh>;
250 close $fh;
251 unless ( index($shebang, '#!') == 0 ) {
252 $script = $shebang . $script;
253 $shebang = "";
48af1939 254 }
30c64724 255 return ($shebang, $script);
256}
257
258sub fatpack_start {
259 return stripspace <<' END_START';
48af1939 260 # This chunk of stuff was generated by App::FatPacker. To find the original
261 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
262 BEGIN {
263 my %fatpacked;
264 END_START
30c64724 265}
266
267sub fatpack_end {
268 return stripspace <<' END_END';
48af1939 269 s/^ //mg for values %fatpacked;
270
f147c6f0 271 my $class = 'FatPacked::'.(0+\%fatpacked);
272 no strict 'refs';
33020868 273 *{"${class}::files"} = sub { keys %{$_[0]} };
e7051d24 274
275 if ($] < 5.008) {
33020868 276 *{"${class}::INC"} = sub {
f147c6f0 277 if (my $fat = $_[0]{$_[1]}) {
e7051d24 278 return sub {
279 return 0 unless length $fat;
280 $fat =~ s/^([^\n]*\n?)//;
281 $_ = $1;
282 return 1;
283 };
284 }
285 return;
15bd679e 286 };
e7051d24 287 }
288
289 else {
e7051d24 290 *{"${class}::INC"} = sub {
15bd679e 291 if (my $fat = $_[0]{$_[1]}) {
e7051d24 292 open my $fh, '<', \$fat
293 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
294 return $fh;
295 }
296 return;
297 };
e7051d24 298 }
48af1939 299
33020868 300 unshift @INC, bless \%fatpacked, $class;
e7051d24 301 } # END OF FATPACK CODE
48af1939 302 END_END
30c64724 303}
304
305sub fatpack_code {
306 my ($self, $files) = @_;
48af1939 307 my @segments = map {
308 (my $stub = $_) =~ s/\.pm$//;
309 my $name = uc join '_', split '/', $stub;
30c64724 310 my $data = $files->{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
56a51caa 311 '$fatpacked{'.perlstring($_).qq!} = '#line '.(1+__LINE__).' "'.__FILE__."\\"\\n".<<'${name}';\n!
48af1939 312 .qq!${data}${name}\n!;
30c64724 313 } sort keys %$files;
314
315 return join "\n", $self->fatpack_start, @segments, $self->fatpack_end;
48af1939 316}
f5a54fa1 317
bb49414d 318=encoding UTF-8
319
f5a54fa1 320=head1 NAME
321
322App::FatPacker - pack your dependencies onto your script file
323
324=head1 SYNOPSIS
325
6da38a2d 326 $ fatpack pack myscript.pl >myscript.packed.pl
327
328Or, with more step-by-step control:
329
f5a54fa1 330 $ fatpack trace myscript.pl
62ceea28 331 $ fatpack packlists-for `cat fatpacker.trace` >packlists
332 $ fatpack tree `cat packlists`
7dabafaa 333 $ fatpack file myscript.pl >myscript.packed.pl
cb50b68f 334
f5a54fa1 335See the documentation for the L<fatpack> script itself for more information.
336
3326f144 337The programmatic API for this code is not yet fully decided, hence the 0.x
f5a54fa1 338release version. Expect that to be cleaned up for 1.0.
339
66a19d01 340=head1 SEE ALSO
341
342L<article for Perl Advent 2012|http://www.perladvent.org/2012/2012-12-14.html>
343
f5a54fa1 344=head1 SUPPORT
345
ef3dc772 346Your current best avenue is to come annoy mst on #toolchain on
f5a54fa1 347irc.perl.org. There should be a non-IRC means of support by 1.0.
348
349=head1 AUTHOR
350
351Matt S. Trout (mst) <mst@shadowcat.co.uk>
352
353=head2 CONTRIBUTORS
354
99b15200 355miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) <miyagawa@bulknews.net>
356
357tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) <tokuhirom@gmail.com>
358
359dg - David Leadbeater (cpan:DGL) <dgl@dgl.cx>
360
361gugod - 劉康民 (cpan:GUGOD) <gugod@cpan.org>
362
363t0m - Tomas Doran (cpan:BOBTFISH) <bobtfish@bobtfish.net>
364
365sawyer - Sawyer X (cpan:XSAWYERX) <xsawyerx@cpan.org>
366
367ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
368
cbd99f49 369Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
370
56a51caa 371dolmen - Olivier Mengué (cpan:DOLMEN) <dolmen@cpan.org>
372
ab7608ee 373djerius - Diab Jerius (cpan:DJERIUS) <djerius@cpan.org>
374
3c29415b 375haarg - Graham Knop (cpan:HAARG> <haarg@haarg.org>
376
99b15200 377Many more people are probably owed thanks for ideas. Yet
f5a54fa1 378another doc nit to fix.
379
380=head1 COPYRIGHT
381
382Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
383as listed above.
384
385=head1 LICENSE
386
387This library is free software and may be distributed under the same terms
388as perl itself.
389
390=cut
391
48af1939 3921;
24d68aa2 393