put parser object in an attribute, use methods for it
[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;
7use Cwd qw(cwd);
8use File::Find qw(find);
9use File::Spec::Functions qw(
10 catdir splitpath splitdir catpath rel2abs abs2rel
11);
12use File::Copy qw(copy);
4d5603b7 13use File::Path qw(mkpath rmtree);
48af1939 14use B qw(perlstring);
15
4d5603b7 16our $VERSION = '0.009006'; # 0.9.6
f5a54fa1 17
18$VERSION = eval $VERSION;
19
48af1939 20sub call_parser {
24d68aa2 21 my $self = shift;
22 my ( $args, $options ) = @_;
23
24 local *ARGV = [ @{$args} ];
25 $self->{'option_parser'}->getoptions( @{$options} );
26
27 return [ @ARGV ];
48af1939 28}
29
30sub lines_of {
31 map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> };
32}
33
34sub stripspace {
35 my ($text) = @_;
36 $text =~ /^(\s+)/ && $text =~ s/^$1//mg;
37 $text;
38}
39
48af1939 40sub import {
24d68aa2 41 $_[1] && $_[1] eq '-run_script'
48af1939 42 and return shift->new->run_script;
43}
44
24d68aa2 45sub new {
46 bless {
47 option_parser => Getopt::Long::Parser->new(
48 config => [ qw(require_order pass_through bundling no_auto_abbrev) ]
49 ),
50 }, $_[0];
51}
48af1939 52
53sub run_script {
54 my ($self, $args) = @_;
55 my @args = $args ? @$args : @ARGV;
3e4dadce 56 (my $cmd = shift @args || 'help') =~ s/-/_/g;
24d68aa2 57
48af1939 58 if (my $meth = $self->can("script_command_${cmd}")) {
59 $self->$meth(\@args);
60 } else {
61 die "No such command ${cmd}";
62 }
63}
64
3e4dadce 65sub script_command_help {
66 print "Try `perldoc fatpack` for how to use me\n";
67}
68
48af1939 69sub script_command_trace {
70 my ($self, $args) = @_;
24d68aa2 71
72 $args = $self->call_parser( $args => [
48af1939 73 'to=s' => \my $file,
74 'to-stderr' => \my $to_stderr,
3fdf85ca 75 'use=s' => \my @additional_use
24d68aa2 76 ] );
48af1939 77
78 die "Can't use to and to-stderr on same call" if $file && $to_stderr;
79
3fdf85ca 80 $file ||= 'fatpacker.trace';
81 if (!$to_stderr and -e $file) {
82 unlink $file or die "Couldn't remove old trace file: $!";
48af1939 83 }
84 my $arg = do {
3fdf85ca 85 if ($to_stderr) {
48af1939 86 "=>&STDERR"
3fdf85ca 87 } elsif ($file) {
88 "=>>${file}"
48af1939 89 }
90 };
3fdf85ca 91
92 if(@additional_use) {
93 $arg .= "," . join ",", @additional_use;
94 }
95
48af1939 96 {
97 local $ENV{PERL5OPT} = '-MApp::FatPacker::Trace'.$arg;
98 system $^X, @$args;
99 }
100}
101
102sub script_command_packlists_for {
103 my ($self, $args) = @_;
104 foreach my $pl ($self->packlists_containing($args)) {
105 print "${pl}\n";
106 }
107}
108
109sub packlists_containing {
110 my ($self, $targets) = @_;
111 my @targets = @$targets;
112 require $_ for @targets;
113 my @search = grep -d $_, map catdir($_, 'auto'), @INC;
114 my %pack_rev;
115 my $cwd = cwd;
116 find(sub {
117 return unless $_ eq '.packlist' && -f $_;
118 $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
119 }, @search);
120 chdir($cwd) or die "Couldn't chdir back to ${cwd} after find: $!";
f5a54fa1 121 my %found; @found{map +($pack_rev{$INC{$_}}||()), @targets} = ();
48af1939 122 sort keys %found;
123}
124
125sub script_command_tree {
126 my ($self, $args) = @_;
127 my $base = catdir(cwd,'fatlib');
128 $self->packlists_to_tree($base, $args);
129}
130
131sub packlists_to_tree {
132 my ($self, $where, $packlists) = @_;
4d5603b7 133 rmtree $where;
134 mkpath $where;
48af1939 135 foreach my $pl (@$packlists) {
136 my ($vol, $dirs, $file) = splitpath $pl;
137 my @dir_parts = splitdir $dirs;
138 my $pack_base;
139 PART: foreach my $p (0 .. $#dir_parts) {
140 if ($dir_parts[$p] eq 'auto') {
141 # $p-2 since it's <wanted path>/$Config{archname}/auto
142 $pack_base = catpath $vol, catdir @dir_parts[0..$p-2];
143 last PART;
144 }
145 }
146 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
147 foreach my $source (lines_of $pl) {
148 # there is presumably a better way to do "is this under this base?"
149 # but if so, it's not obvious to me in File::Spec
150 next unless substr($source,0,length $pack_base) eq $pack_base;
151 my $target = rel2abs( abs2rel($source, $pack_base), $where );
152 my $target_dir = catpath((splitpath $target)[0,1]);
4d5603b7 153 mkpath $target_dir;
48af1939 154 copy $source => $target;
155 }
156 }
157}
158
159sub script_command_file {
160 my ($self, $args) = @_;
161 my $file = shift @$args;
162 my $cwd = cwd;
163 my @dirs = map rel2abs($_, $cwd), ('lib','fatlib');
164 my %files;
165 foreach my $dir (@dirs) {
166 find(sub {
167 return unless -f $_;
168 !/\.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;
169 $files{abs2rel($File::Find::name,$dir)} = do {
170 local (@ARGV, $/) = ($File::Find::name); <>
171 };
172 }, $dir);
173 }
174 my $start = stripspace <<' END_START';
175 # This chunk of stuff was generated by App::FatPacker. To find the original
176 # file's code, look for the end of this BEGIN block or the string 'FATPACK'
177 BEGIN {
178 my %fatpacked;
179 END_START
180 my $end = stripspace <<' END_END';
181 s/^ //mg for values %fatpacked;
182
183 unshift @INC, sub {
184 if (my $fat = $fatpacked{$_[1]}) {
002ecfea 185 open my $fh, '<', \$fat
186 or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
48af1939 187 return $fh;
188 }
189 return
190 };
191
192 } # END OF FATPACK CODE
193 END_END
194 my @segments = map {
195 (my $stub = $_) =~ s/\.pm$//;
196 my $name = uc join '_', split '/', $stub;
cc5db92a 197 my $data = $files{$_}; $data =~ s/^/ /mg; $data =~ s/(?<!\n)\z/\n/;
48af1939 198 '$fatpacked{'.perlstring($_).qq!} = <<'${name}';\n!
199 .qq!${data}${name}\n!;
200 } sort keys %files;
201 print join "\n", $start, @segments, $end;
202}
f5a54fa1 203
204=head1 NAME
205
206App::FatPacker - pack your dependencies onto your script file
207
208=head1 SYNOPSIS
209
210 $ fatpack trace myscript.pl
62ceea28 211 $ fatpack packlists-for `cat fatpacker.trace` >packlists
212 $ fatpack tree `cat packlists`
f5a54fa1 213 $ (fatpack file; cat myscript.pl) >myscript.packed.pl
214
215See the documentation for the L<fatpack> script itself for more information.
216
3fdf85ca 217The programmatic API for this code is not yet fully decided, hence the 0.9
f5a54fa1 218release version. Expect that to be cleaned up for 1.0.
219
220=head1 SUPPORT
221
222Your current best avenue is to come annoy annoy mst on #toolchain on
223irc.perl.org. There should be a non-IRC means of support by 1.0.
224
225=head1 AUTHOR
226
227Matt S. Trout (mst) <mst@shadowcat.co.uk>
228
229=head2 CONTRIBUTORS
230
231None as yet, though I probably owe lots of people thanks for ideas. Yet
232another doc nit to fix.
233
234=head1 COPYRIGHT
235
236Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
237as listed above.
238
239=head1 LICENSE
240
241This library is free software and may be distributed under the same terms
242as perl itself.
243
244=cut
245
48af1939 2461;
24d68aa2 247