fix missing whitespace before (
[p5sagit/App-FatPacker.git] / lib / App / FatPacker.pm
CommitLineData
48af1939 1package App::FatPacker;
2
3use strict;
4use warnings FATAL => 'all';
f5a54fa1 5use 5.008001;
48af1939 6use Getopt::Long;
e0cd89ab 7use Capture::Tiny ();
48af1939 8use Cwd qw(cwd);
9use File::Find qw(find);
10use File::Spec::Functions qw(
11 catdir splitpath splitdir catpath rel2abs abs2rel
12);
13use File::Copy qw(copy);
4d5603b7 14use File::Path qw(mkpath rmtree);
48af1939 15use B qw(perlstring);
16
4d5603b7 17our $VERSION = '0.009006'; # 0.9.6
f5a54fa1 18
19$VERSION = eval $VERSION;
20
48af1939 21sub call_parser {
24d68aa2 22 my $self = shift;
23 my ( $args, $options ) = @_;
24
25 local *ARGV = [ @{$args} ];
26 $self->{'option_parser'}->getoptions( @{$options} );
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
48af1939 70sub script_command_trace {
71 my ($self, $args) = @_;
24d68aa2 72
73 $args = $self->call_parser( $args => [
48af1939 74 'to=s' => \my $file,
75 'to-stderr' => \my $to_stderr,
3fdf85ca 76 'use=s' => \my @additional_use
24d68aa2 77 ] );
48af1939 78
79 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
80
3fdf85ca 81 $file ||= 'fatpacker.trace';
82 if (!$to_stderr and -e $file) {
83 unlink $file or die "Couldn't remove old trace file: $!";
48af1939 84 }
85 my $arg = do {
3fdf85ca 86 if ($to_stderr) {
276a30c9 87 "=&STDERR"
3fdf85ca 88 } elsif ($file) {
276a30c9 89 ">>${file}"
48af1939 90 }
91 };
3fdf85ca 92
b4704b1a 93 $self->trace(
5e1de95d 94 use => \@additional_use,
95 args => $args,
7a3662c8 96 output => $arg,
b4704b1a 97 );
abd7cf01 98}
99
100sub trace {
b4704b1a 101 my ($self, %opts) = @_;
f1374d23 102 my $use = defined $opts{'use'} ? $opts{'use'} : [];
103 my $args = defined $opts{'args'} ? $opts{'args'} : [];
7a3662c8 104 my $output = $opts{'output'};
e0cd89ab 105 my $capture;
106
107 # if the user doesn't provide output, they want to actually
108 # capture the output and receive it back
109 if (!$output) {
110 # throw to STDOUT to differ from STDERR
111 $output .= '>&STDOUT';
112
113 # raise capture flag
114 $capture++;
115 }
7a3662c8 116
117 if(@$use) {
118 $output .= "," . join ",", @$use;
119 }
abd7cf01 120
e0cd89ab 121 my $trace_sub = sub {
122 local $ENV{PERL5OPT} = '-MApp::FatPacker::Trace='.$output;
123 system $^X, @$args;
124 };
125
126 if ($capture) {
127 # capture both STDOUT and STDERR so we could throw away STDERR
128 # STDOUT will contain the trace
129 # STDERR will contain the "syntax OK" statement
130 my ($stdout, $stderr) = Capture::Tiny::capture {$trace_sub->()};
131 return $stdout;
132 } else {
133 $trace_sub->();
134 }
48af1939 135}
136
137sub script_command_packlists_for {
138 my ($self, $args) = @_;
139 foreach my $pl ($self->packlists_containing($args)) {
140 print "${pl}\n";
141 }
142}
143
144sub packlists_containing {
145 my ($self, $targets) = @_;
146 my @targets = @$targets;
147 require $_ for @targets;
148 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
149 my %pack_rev;
150 my $cwd = cwd;
151 find(sub {
152 return unless $_ eq '.packlist' && -f $_;
153 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
154 }, @search);
155 chdir($cwd) or die "Couldn't chdir back to ${cwd} after find: $!";
f5a54fa1 156 my %found; @found{map +($pack_rev{$INC{$_}}||()), @targets} = ();
48af1939 157 sort keys %found;
158}
159
160sub script_command_tree {
161 my ($self, $args) = @_;
162 my $base = catdir(cwd,'fatlib');
163 $self->packlists_to_tree($base, $args);
164}
165
166sub packlists_to_tree {
167 my ($self, $where, $packlists) = @_;
4d5603b7 168 rmtree $where;
169 mkpath $where;
48af1939 170 foreach my $pl (@$packlists) {
171 my ($vol, $dirs, $file) = splitpath $pl;
172 my @dir_parts = splitdir $dirs;
173 my $pack_base;
174 PART: foreach my $p (0 .. $#dir_parts) {
175 if ($dir_parts[$p] eq 'auto') {
176 # $p-2 since it's <wanted path>/$Config{archname}/auto
177 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
178 last PART;
179 }
180 }
181 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
182 foreach my $source (lines_of $pl) {
183 # there is presumably a better way to do "is this under this base?"
184 # but if so, it's not obvious to me in File::Spec
185 next unless substr($source,0,length $pack_base) eq $pack_base;
186 my $target = rel2abs( abs2rel($source, $pack_base), $where );
187 my $target_dir = catpath((splitpath $target)[0,1]);
4d5603b7 188 mkpath $target_dir;
48af1939 189 copy $source => $target;
190 }
191 }
192}
193
194sub script_command_file {
195 my ($self, $args) = @_;
196 my $file = shift @$args;
197 my $cwd = cwd;
198 my @dirs = map rel2abs($_, $cwd), ('lib','fatlib');
199 my %files;
200 foreach my $dir (@dirs) {
201 find(sub {
202 return unless -f $_;
203 !/\.pm$/ and warn "File ${File::Find::name} isn't a .pm file - can't pack this and if you hoped we were going to things may not be what you expected later\n" and return;
204 $files{abs2rel($File::Find::name,$dir)} = do {
205 local (@ARGV, $/) = ($File::Find::name); <>
206 };
207 }, $dir);
208 }
209 my $start = stripspace <<' END_START';
210 # This chunk of stuff was generated by App::FatPacker. To find the original
211 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
212 BEGIN {
213 my %fatpacked;
214 END_START
215 my $end = stripspace <<' END_END';
216 s/^ //mg for values %fatpacked;
217
218 unshift @INC, sub {
219 if (my $fat = $fatpacked{$_[1]}) {
002ecfea 220 open my $fh, '<', \$fat
221 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
48af1939 222 return $fh;
223 }
224 return
225 };
226
227 } # END OF FATPACK CODE
228 END_END
229 my @segments = map {
230 (my $stub = $_) =~ s/\.pm$//;
231 my $name = uc join '_', split '/', $stub;
cc5db92a 232 my $data = $files{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 233 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
234 .qq!${data}${name}\n!;
235 } sort keys %files;
236 print join "\n", $start, @segments, $end;
237}
f5a54fa1 238
239=head1 NAME
240
241App::FatPacker - pack your dependencies onto your script file
242
243=head1 SYNOPSIS
244
245 $ fatpack trace myscript.pl
62ceea28 246 $ fatpack packlists-for `cat fatpacker.trace` >packlists
247 $ fatpack tree `cat packlists`
f5a54fa1 248 $ (fatpack file; cat myscript.pl) >myscript.packed.pl
249
250See the documentation for the L<fatpack> script itself for more information.
251
3fdf85ca 252The programmatic API for this code is not yet fully decided, hence the 0.9
f5a54fa1 253release version. Expect that to be cleaned up for 1.0.
254
255=head1 SUPPORT
256
257Your current best avenue is to come annoy annoy mst on #toolchain on
258irc.perl.org. There should be a non-IRC means of support by 1.0.
259
260=head1 AUTHOR
261
262Matt S. Trout (mst) <mst@shadowcat.co.uk>
263
264=head2 CONTRIBUTORS
265
266None as yet, though I probably owe lots of people thanks for ideas. Yet
267another doc nit to fix.
268
269=head1 COPYRIGHT
270
271Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
272as listed above.
273
274=head1 LICENSE
275
276This library is free software and may be distributed under the same terms
277as perl itself.
278
279=cut
280
48af1939 2811;
24d68aa2 282