The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use strict;
use warnings;

use FindBin qw($Bin);

use IO::File;
use IO::Dir;
use File::Spec::Functions qw(catfile  no_upwards rel2abs splitdir);

#----------------------------------------------------------------------
# Configuration

# Prefix that preceds every command in data section
# Must agree with Initialize.pm
use constant CMD_PREFIX => '#>>>';

# The location of the initialization module relative to this file
my $output = 'lib/App/Followme/Initialize.pm';

# The files that will be included in the data section
# using unix style wild cards
my @patterns = qw(index.html *.cfg *.htm);

#----------------------------------------------------------------------
# Main routine

my $dir = shift(@ARGV) or die "Must supply site directory\n";
$dir  = rel2abs($dir);

@patterns = glob_patterns(@patterns);

chdir ($Bin);
my $out = copy_script($output);

chdir($dir);
my $visitor = get_visitor(@patterns);

while (my $file = &$visitor) {
    append_file($out, $file);
}

close($out);

chdir($Bin);
rename("$output.TMP", $output);

#----------------------------------------------------------------------
# Append a text file to the bundle

sub append_file {
    my ($out, $file) = @_;

    my $text = read_file($file);
    $file = join('/', splitdir($file));

    print $out CMD_PREFIX, "copy $file\n";
    print $out $text;

    return;
}

#----------------------------------------------------------------------
# Copy the script to start

sub copy_script {
    my ($output) = @_;

    my @path = split(/\//, $output);
    $output = catfile(@path);

    my $last = "__DATA__\n";
    my $text = read_file($output, $last);

    $output .= '.TMP';
    my $out = IO::File->new($output, 'w');
    die "Couldn't write to script: $output\n" unless $out;

    print $out $text;
    return $out;
}

#----------------------------------------------------------------------
# Return a closure that visits files in a directory

sub get_visitor {
    my (@patterns) = @_;

    my @dirlist;
    my @filelist;
    push(@dirlist, '.');

    return sub {
        for (;;) {
            my $file = shift @filelist;
            return $file if defined $file;

            my $dir = shift @dirlist;
            return unless defined $dir;

            my $dd = IO::Dir->new($dir) or die "Couldn't open $dir: $!\n";

            while (defined ($file = $dd->read())) {
                my $path = $dir ne '.' ? catfile($dir, $file) : $file;

                if (-d $path) {
                    push(@dirlist, $path) if no_upwards($file);

                } else {
                    my $match = 0;
                    foreach my $pattern (@patterns) {
                        if ($path =~ /$pattern/) {
                            $match = 1;
                            last;
                        }
                    }
                    push(@filelist, $path) if $match;
                }
            }

            $dd->close;
        }
    };
}

#----------------------------------------------------------------------
# Map filename globbing metacharacters onto regexp metacharacters

sub glob_patterns {
    my (@patterns) = @_;

    my @globbed_patterns;
    foreach my $pattern (@patterns) {
        my $start;
        if ($pattern =~ s/^\*//) {
            $start = '';
        } else {
            $start = '^';
        }

        my $finish;
        if ($pattern =~ s/\*$//) {
            $finish = '';
        } else {
            $finish = '$';
        }

        $pattern =~ s/\./\\./g;
        $pattern =~ s/\*/\.\*/g;
        $pattern =~ s/\?/\.\?/g;

        push(@globbed_patterns, $start . $pattern . $finish);
    }

    return @globbed_patterns;
}

#----------------------------------------------------------------------
# Read a text file into a string

sub read_file {
    my ($file, $last) = @_;

    my $in = IO::File->new($file, 'r') or
        die "Couldn't read $file: $!\n";

    my @text;
    while (<$in>) {
        push(@text, $_);
        last if $last && $_ eq $last;
    }

    close($in);

    return join('', @text);
}

__END__

=encoding utf-8

=head1 NAME

bundle.pl - Combine website files with Initialize module

=head1 SYNOPSIS

    perl bundle.pl directory

=head1 DESCRIPTION

When followme is called with the -i flag it creates a new website in a directory,
including the files it needs to run. These files are extraced from the DATA
section at the end of the Initialize.pm module. This script updates that DATA section
from a sample website. It is for developers of this code and not for end users.

Run this script with the name of the directory containing the sample website on
the command line.

=head1 CONFIGURATION

The following variabless are defined in the configuration section at the top of
the script:

=over 4

=item CMD_PREFIX

The string which marks a line in the DATA section as a command. It must match
the constant of the same name in the Initialize.pm module.

=item $output

The file path to the Initialize.pm module relative to the location of this script.
Directories should be separated by forward slashes (/) regardless of the convention
of the operating system.

=item @patterns

A list of wildcard patterns using Unix filename wildcard syntax. If a file matches
any of the patterns it will be added to the DATA section.

=back

=head1 LICENSE

Copyright (C) Bernie Simon.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

Bernie Simon E<lt>bernie.simon@gmail.comE<gt>

=cut