#!/usr/bin/perl

# This chunk of stuff was generated by App::FatPacker. To find the original
# file's code, look for the end of this BEGIN block or the string 'FATPACK'
BEGIN {
my %fatpacked;

$fatpacked{"App/FatPacker.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_FATPACKER';
  package App::FatPacker;
  
  use strict;
  use warnings FATAL => 'all';
  use 5.008001;
  use Getopt::Long;
  use Cwd qw(cwd);
  use File::Find qw(find);
  use File::Spec::Functions qw(
    catdir splitpath splitdir catpath rel2abs abs2rel
  );
  use File::Spec::Unix;
  use File::Copy qw(copy);
  use File::Path qw(mkpath rmtree);
  use B qw(perlstring);
  
  our $VERSION = '0.010008'; # v0.10.8
  
  $VERSION = eval $VERSION;
  
  sub call_parser {
    my $self = shift;
    my ($args, $options) = @_;
  
    local *ARGV = [ @{$args} ];
    $self->{option_parser}->getoptions(@$options);
  
    return [ @ARGV ];
  }
  
  sub lines_of {
    map +(chomp,$_)[1], do { local @ARGV = ($_[0]); <> };
  }
  
  sub stripspace {
    my ($text) = @_;
    $text =~ /^(\s+)/ && $text =~ s/^$1//mg;
    $text;
  }
  
  sub import {
    $_[1] && $_[1] eq '-run_script'
      and return shift->new->run_script;
  }
  
  sub new {
    bless {
      option_parser => Getopt::Long::Parser->new(
        config => [ qw(require_order pass_through bundling no_auto_abbrev) ]
      ),
    }, $_[0];
  }
  
  sub run_script {
    my ($self, $args) = @_;
    my @args = $args ? @$args : @ARGV;
    (my $cmd = shift @args || 'help') =~ s/-/_/g;
  
    if (my $meth = $self->can("script_command_${cmd}")) {
      $self->$meth(\@args);
    } else {
      die "No such command ${cmd}";
    }
  }
  
  sub script_command_help {
    print {*STDERR} "Try `perldoc fatpack` for how to use me\n";
  }
  
  sub script_command_pack {
    my ($self, $args) = @_;
  
    my @modules = split /\r?\n/, $self->trace(args => $args);
    my @packlists = $self->packlists_containing(\@modules);
  
    my $base = catdir(cwd, 'fatlib');
    $self->packlists_to_tree($base, \@packlists);
  
    my $file = shift @$args;
    print $self->fatpack_file($file);
  }
  
  sub script_command_trace {
    my ($self, $args) = @_;
  
    $args = $self->call_parser($args => [
      'to=s' => \my $file,
      'to-stderr' => \my $to_stderr,
      'use=s' => \my @additional_use
    ]);
  
    die "Can't use to and to-stderr on same call" if $file && $to_stderr;
  
    $file ||= 'fatpacker.trace';
  
    if (!$to_stderr and -e $file) {
      unlink $file or die "Couldn't remove old trace file: $!";
    }
    my $arg = do {
      if ($to_stderr) {
        ">&STDERR"
      } elsif ($file) {
        ">>${file}"
      }
    };
  
    $self->trace(
      use => \@additional_use,
      args => $args,
      output => $arg,
    );
  }
  
  
  sub _fatpacked_save_Trace_for_INC {
    my ($self, $instance) = @_;
    require File::Temp;
    my $dir = File::Temp::tempdir(CLEANUP => 1);
  
    require File::Spec;
    my ($v, $ds) = File::Spec->splitpath($dir, 'no-file-please');
    my @ds = File::Spec->splitdir($ds);
  
    require File::Path;
    $ds = File::Spec->catdir(@ds, 'App');
    File::Path::make_path(File::Spec->catpath($v, $ds, 'FatPacker'));
  
    $ds = File::Spec->catdir(@ds, 'App', 'FatPacker');
    my $trace_file = File::Spec->catpath($v, $ds, 'Trace.pm');
  
    open my $fh, '>:raw', $trace_file or die "open('$trace_file'): $!\n";
    print {$fh} $instance->{'App/FatPacker/Trace.pm'};
    close $fh;
  
    return $dir;
  }
  
  sub trace {
    my ($self, %opts) = @_;
  
    # save App::FatPacker::Trace to the filesystem and adjust PERL5LIB
    # if using a fatpacked fatpacker.
    local $ENV{PERL5LIB} = $ENV{PERL5LIB} || '';
    if (my ($instance) = grep {ref($_) =~ m{^FatPacked}mxs} @INC) {
      my $lib_dir = $self->_fatpacked_save_Trace_for_INC($instance);
      $ENV{PERL5LIB} = join ':', $lib_dir, ($ENV{PERL5LIB} || ());
    }
  
    my $output = $opts{output};
    my $trace_opts = join ',', $output||'>&STDOUT', @{$opts{use}||[]};
  
    local $ENV{PERL5OPT} = join ' ',
      ($ENV{PERL5OPT}||()), '-MApp::FatPacker::Trace='.$trace_opts;
  
    my @args = @{$opts{args}||[]};
  
    if ($output) {
      # user specified output target, JFDI
      system $^X, @args;
      return;
    } else {
      # no output target specified, slurp
      open my $out_fh, "$^X @args |";
      return do { local $/; <$out_fh> };
    }
  }
  
  sub script_command_packlists_for {
    my ($self, $args) = @_;
    foreach my $pl ($self->packlists_containing($args)) {
      print "${pl}\n";
    }
  }
  
  sub packlists_containing {
    my ($self, $targets) = @_;
    my @targets;
    {
      local @INC = ('lib', @INC);
      foreach my $t (@$targets) {
        unless (eval { require $t; 1}) {
          warn "Failed to load ${t}: $@\n"
              ."Make sure you're not missing a packlist as a result\n";
          next;
        }
        push @targets, $t;
      }
    }
    my @search = grep -d $_, map catdir($_, 'auto'), @INC;
    my %pack_rev;
    find({
      no_chdir => 1,
      wanted => sub {
        return unless /[\\\/]\.packlist$/ && -f $_;
        $pack_rev{$_} = $File::Find::name for lines_of $File::Find::name;
      },
    }, @search);
    my %found; @found{map +($pack_rev{Cwd::abs_path($INC{$_})}||()), @targets} = ();
    sort keys %found;
  }
  
  sub script_command_tree {
    my ($self, $args) = @_;
    my $base = catdir(cwd,'fatlib');
    $self->packlists_to_tree($base, $args);
  }
  
  sub packlists_to_tree {
    my ($self, $where, $packlists) = @_;
    rmtree $where;
    mkpath $where;
    foreach my $pl (@$packlists) {
      my ($vol, $dirs, $file) = splitpath $pl;
      my @dir_parts = splitdir $dirs;
      my $pack_base;
      PART: foreach my $p (0 .. $#dir_parts) {
        if ($dir_parts[$p] eq 'auto') {
          # $p-2 normally since it's <wanted path>/$Config{archname}/auto but
          # if the last bit is a number it's $Config{archname}/$version/auto
          # so use $p-3 in that case
          my $version_lib = 0+!!($dir_parts[$p-1] =~ /^[0-9.]+$/);
          $pack_base = catpath $vol, catdir @dir_parts[0..$p-(2+$version_lib)];
          last PART;
        }
      }
      die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
      foreach my $source (lines_of $pl) {
        # there is presumably a better way to do "is this under this base?"
        # but if so, it's not obvious to me in File::Spec
        next unless substr($source,0,length $pack_base) eq $pack_base;
        my $target = rel2abs( abs2rel($source, $pack_base), $where );
        my $target_dir = catpath((splitpath $target)[0,1]);
        mkpath $target_dir;
        copy $source => $target;
      }
    }
  }
  
  sub script_command_file {
    my ($self, $args) = @_;
    my $file = shift @$args;
    print $self->fatpack_file($file);
  }
  
  sub fatpack_file {
    my ($self, $file) = @_;
  
    my $shebang = "";
    my $script = "";
    if ( defined $file and -r $file ) {
      ($shebang, $script) = $self->load_main_script($file);
    }
  
    my @dirs = $self->collect_dirs();
    my %files;
    $self->collect_files($_, \%files) for @dirs;
  
    return join "\n", $shebang, $self->fatpack_code(\%files), $script;
  }
  
  # This method can be overload in sub classes
  # For example to skip POD
  sub load_file {
    my ($self, $file) = @_;
    my $content = do {
      local (@ARGV, $/) = ($file);
      <>
    };
    close ARGV;
    return $content;
  }
  
  sub collect_dirs {
    my ($self) = @_;
    my $cwd = cwd;
    return grep -d, map rel2abs($_, $cwd), ('lib','fatlib');
  }
  
  sub collect_files {
    my ($self, $dir, $files) = @_;
    find(sub {
      return unless -f $_;
      !/\.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;
      $files->{File::Spec::Unix->abs2rel($File::Find::name,$dir)} =
        $self->load_file($File::Find::name);
    }, $dir);
  }
  
  sub load_main_script {
    my ($self, $file) = @_;
    open my $fh, "<", $file or die("Can't read $file: $!");
    my $shebang = <$fh>;
    my $script = join "", <$fh>;
    close $fh;
    unless ( index($shebang, '#!') == 0 ) {
      $script = $shebang . $script;
      $shebang = "";
    }
    return ($shebang, $script);
  }
  
  sub fatpack_start {
    return stripspace <<'  END_START';
      # This chunk of stuff was generated by App::FatPacker. To find the original
      # file's code, look for the end of this BEGIN block or the string 'FATPACK'
      BEGIN {
      my %fatpacked;
    END_START
  }
  
  sub fatpack_end {
    return stripspace <<'  END_END';
      s/^  //mg for values %fatpacked;
  
      my $class = 'FatPacked::'.(0+\%fatpacked);
      no strict 'refs';
      *{"${class}::files"} = sub { keys %{$_[0]} };
  
      if ($] < 5.008) {
        *{"${class}::INC"} = sub {
          if (my $fat = $_[0]{$_[1]}) {
            my $pos = 0;
            my $last = length $fat;
            return (sub {
              return 0 if $pos == $last;
              my $next = (1 + index $fat, "\n", $pos) || $last;
              $_ .= substr $fat, $pos, $next - $pos;
              $pos = $next;
              return 1;
            });
          }
        };
      }
  
      else {
        *{"${class}::INC"} = sub {
          if (my $fat = $_[0]{$_[1]}) {
            open my $fh, '<', \$fat
              or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
            return $fh;
          }
          return;
        };
      }
  
      unshift @INC, bless \%fatpacked, $class;
    } # END OF FATPACK CODE
    END_END
  }
  
  sub fatpack_code {
    my ($self, $files) = @_;
    my @segments = map {
      (my $stub = $_) =~ s/\.pm$//;
      my $name = uc join '_', split '/', $stub;
      my $data = $files->{$_}; $data =~ s/^/  /mg; $data =~ s/(?<!\n)\z/\n/;
      '$fatpacked{'.perlstring($_).qq!} = '#line '.(1+__LINE__).' "'.__FILE__."\\"\\n".<<'${name}';\n!
      .qq!${data}${name}\n!;
    } sort keys %$files;
  
    return join "\n", $self->fatpack_start, @segments, $self->fatpack_end;
  }
  
  =encoding UTF-8
  
  =head1 NAME
  
  App::FatPacker - pack your dependencies onto your script file
  
  =head1 SYNOPSIS
  
    $ fatpack pack myscript.pl >myscript.packed.pl
  
  Or, with more step-by-step control:
  
    $ fatpack trace myscript.pl
    $ fatpack packlists-for `cat fatpacker.trace` >packlists
    $ fatpack tree `cat packlists`
    $ fatpack file myscript.pl >myscript.packed.pl
  
  Each command is designed to be simple and self-contained so that you can modify
  the input/output of each step as needed. See the documentation for the
  L<fatpack> script itself for more information.
  
  The programmatic API for this code is not yet fully decided, hence the 0.x
  release version. Expect that to be cleaned up for 1.0.
  
  =head1 CAVEATS
  
  As dependency module code is copied into the resulting file as text, only
  pure-perl dependencies can be packed, not compiled XS code.
  
  The currently-installed dependencies to pack are found via F<.packlist> files,
  which are generally only included in non-core distributions that were installed
  by a CPAN installer. This is a feature; see L<fatpack/packlists-for> for
  details. (a notable exception to this is FreeBSD, which, since its packaging
  system is designed to work equivalently to a source install, does preserve
  the packlist files)
  
  =head1 SEE ALSO
  
  L<article for Perl Advent 2012|http://www.perladvent.org/2012/2012-12-14.html>
  
  L<pp> - PAR Packager, a much more complex architecture-dependent packer that
  can pack compiled code and even a Perl interpreter
  
  =head1 SUPPORT
  
  Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=App-FatPacker>
  (or L<bug-App-FatPacker@rt.cpan.org|mailto:bug-App-FatPacker@rt.cpan.org>).
  
  You can normally also obtain assistance on irc, in #toolchain on irc.perl.org.
  
  =head1 AUTHOR
  
  Matt S. Trout (mst) <mst@shadowcat.co.uk>
  
  =head2 CONTRIBUTORS
  
  miyagawa - Tatsuhiko Miyagawa (cpan:MIYAGAWA) <miyagawa@bulknews.net>
  
  tokuhirom - MATSUNO★Tokuhiro (cpan:TOKUHIROM) <tokuhirom@gmail.com>
  
  dg - David Leadbeater (cpan:DGL) <dgl@dgl.cx>
  
  gugod - 劉康民 (cpan:GUGOD) <gugod@cpan.org>
  
  t0m - Tomas Doran (cpan:BOBTFISH) <bobtfish@bobtfish.net>
  
  sawyer - Sawyer X (cpan:XSAWYERX) <xsawyerx@cpan.org>
  
  ether - Karen Etheridge (cpan:ETHER) <ether@cpan.org>
  
  Mithaldu - Christian Walde (cpan:MITHALDU) <walde.christian@googlemail.com>
  
  dolmen - Olivier Mengué (cpan:DOLMEN) <dolmen@cpan.org>
  
  djerius - Diab Jerius (cpan:DJERIUS) <djerius@cpan.org>
  
  haarg - Graham Knop (cpan:HAARG) <haarg@haarg.org>
  
  grinnz - Dan Book (cpan:DBOOK) <dbook@cpan.org>
  
  Many more people are probably owed thanks for ideas. Yet
  another doc nit to fix.
  
  =head1 COPYRIGHT
  
  Copyright (c) 2010 the App::FatPacker L</AUTHOR> and L</CONTRIBUTORS>
  as listed above.
  
  =head1 LICENSE
  
  This library is free software and may be distributed under the same terms
  as perl itself.
  
  =cut
  
  1;
  
APP_FATPACKER

$fatpacked{"App/FatPacker/Trace.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_FATPACKER_TRACE';
  package App::FatPacker::Trace;
  
  use strict;
  use warnings FATAL => 'all';
  use B ();
  
  my $trace_file;
  my %initial_inc;
  
  sub import {
    my (undef, $file, @extras) = @_;
  
    $trace_file = $file || '>>fatpacker.trace';
    # For filtering out our own deps later.
    # (Not strictly required as these are core only and won't have packlists, but 
    # looks neater.)
    %initial_inc = %INC;
  
    # Use any extra modules specified
    eval "use $_" for @extras;
  
    B::minus_c;
  }
  
  CHECK {
    return unless $trace_file; # not imported
  
    open my $trace, $trace_file
        or die "Couldn't open $trace_file to trace to: $!";
  
    for my $inc (keys %INC) {
      next if exists $initial_inc{$inc};
      next unless defined($INC{$inc}) and $INC{$inc} =~ /\Q${inc}\E\Z/;
      print $trace "$inc\n";
    }
  }
  
  1;
  
  __END__
  
  =head1 NAME
  
  App::FatPacker::Trace - Tracing module usage using compilation checking
  
  =head1 SYNOPSIS
  
      # open STDERR for writing
      # will be like: open my $fh, '>', '&STDERR'...
      perl -MApp::FatPacker::Trace=>&STDERR myscript.pl
  
      # open a file for writing
      # will be like: open my $fh, '>>', 'fatpacker.trace'
      perl -MApp::FatPacker::Trace=>>fatpacker.trace myscript.pl
  
  =head1 DESCRIPTION
  
  This module allows tracing the modules being used by your code. It does that
  using clever trickery using the C<import> method, the C<CHECK> block and
  L<B>'s C<minus_c> function.
  
  When App::FatPacker::Trace is being used, the import() method will call
  C<B::minus_c> in order to set up the global compilation-only flag perl
  (the interpreter) has. This will prevent any other code from being run.
  
  Then in the C<CHECK> block which is reached at the end of the compilation
  phase (see L<perlmod>), it will gather all modules that have been loaded,
  using C<%INC>, and will write it to a file or to STDERR, determined by
  parameters sent to the C<import> method.
  
  =head1 METHODS
  
  =head2 import
  
  This method gets run when you just load L<App::FatPacker::Trace>. It will
  note the current C<%INC> and will set up the output to be written to, and
  raise the compilation-only flag, which will prevent anything from being
  run past that point. This flag cannot be unset, so this is most easily run
  from the command line as such:
  
      perl -MApp::FatPacker::Trace [...]
  
  You can control the parameters to the import using an equal sign, as such:
  
      # send the parameter "hello"
      perl -MApp::FatPacker::Trace=hello [...]
  
      # send the parameter ">&STDERR"
      perl -MApp::FatPacker::Trace=>&STDERR [...]
  
  The import method accepts a first parameter telling it which output to open
  and how. These are both sent in a single parameter.
  
      # append to mytrace.txt
      perl -MApp::FatPacker::Trace=>>mytrace.txt myscript.pl
  
      # write to STDERR
      perl -MApp::FatPacker::Trace=>&STDERR myscript.pl
  
  The import method accepts additional parameters of extra modules to load.
  It will then add these modules to the trace. This is helpful if you want
  to explicitly indicate additional modules to trace, even if they aren't
  used in your script. Perhaps you're conditionally using them, perhaps
  they're for additional features, perhaps they're loaded lazily, whatever
  the reason.
  
      # Add Moo to the trace, even if you don't trace it in myscript.pl
      perl -MApp::FatPacker::Trace=>&STDERR,Moo myscript.pl
  
APP_FATPACKER_TRACE

s/^  //mg for values %fatpacked;

my $class = 'FatPacked::'.(0+\%fatpacked);
no strict 'refs';
*{"${class}::files"} = sub { keys %{$_[0]} };

if ($] < 5.008) {
  *{"${class}::INC"} = sub {
    if (my $fat = $_[0]{$_[1]}) {
      my $pos = 0;
      my $last = length $fat;
      return (sub {
        return 0 if $pos == $last;
        my $next = (1 + index $fat, "\n", $pos) || $last;
        $_ .= substr $fat, $pos, $next - $pos;
        $pos = $next;
        return 1;
      });
    }
  };
}

else {
  *{"${class}::INC"} = sub {
    if (my $fat = $_[0]{$_[1]}) {
      open my $fh, '<', \$fat
        or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
      return $fh;
    }
    return;
  };
}

unshift @INC, bless \%fatpacked, $class;
  } # END OF FATPACK CODE


use App::FatPacker -run_script;

=head1 NAME

fatpack - Command line frontend for App::FatPacker

=head1 COMMANDS

=head2 pack

  $ fatpack pack myscript.pl > myscript.packed.pl

A shortcut to do all the work of tracing, collecting packlists,
extracting modules in fatlib, then concatenating into a packed script
- in one shot. If you need more detailed controls for additional
modules, use the following commands separately (see L</RECIPES>).

=head2 trace

  $ fatpack trace [--to=trace-file|--to-stderr] [--use=MODULE]
      myscript.pl

Compiles myscript.pl (as in "perl -c") and writes out a trace file containing
every module require()d during the compilation.

The trace file is called 'fatpacker.trace' by default; the --to option
overrides this.

If you pass --to-stderr fatpack writes the trace to STDERR instead.

You cannot pass both --to and --to-stderr.

If the --use option specifies a module (or modules, if used multiple
times) those modules will be additionally included in the trace output.

=head2 packlists-for

  $ fatpack packlists-for Module1 Module2 Module3

Searches your perl's @INC for .packlist files containing the .pm files for
the modules requested and emits a list of unique packlist files to STDOUT.

These packlists will, in a pure cpan-installation environment, be all non-core
distributions required for those modules.

Unfortunately most vendors strip the .packlist files so if you installed
modules via e.g. apt-get you may be missing those modules; installing your
dependencies into a L<local::lib|local::lib> first is the preferred workaround.

=head2 tree

  $ fatpack tree fatlib packlist1 packlist2 packlist3

Takes a list of packlist files and copies their contents into a tree at the
requested location.

This tree should be sufficient to 'use lib' to make available all modules
provided by the distributions whose packlists were specified.

=head2 file

  $ fatpack file

Recurses into the 'lib' and 'fatlib' directories and bundles all .pm files
found into a BEGIN block which adds a virtual @INC entry to load these files
from the bundled code rather than disk.

=head1 RECIPES

Current basic recipe for packing:

  $ fatpack trace myscript.pl
  $ fatpack packlists-for `cat fatpacker.trace` >packlists
  $ fatpack tree `cat packlists`
  $ fatpack file myscript.pl >myscript.packed.pl

=head1 COPYRIGHT, LICENSE, AUTHOR

See the corresponding sections in L<App::FatPacker>.

=cut
