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

# TODO: (in priority order)

# Also, see if a top-level Makefile.PL can be built so that the user can have
# the option of using 'perl Makefile.PL' or ./configure

# Should make it run against MySQL too, although I'm not sure how many of the
# constructs are accessible through the DBI. I know you cannot get comments,
# but you might/might not be able to get at foreign keys/constraints.
# The user can always fill these in by hand, or they can just use a "propper"
# database.

# Could probably make the generation of Makefile.am files a bit nicer
# Its a bit of a mess but its OK for now.

use Template;
use DBI;
use DB::Table;

# Used in debugging
use Carp qw(cluck);
use Data::Dumper;

use Getopt::Long;
use strict;

my ($database, $host, $username, $password, $help, $appName, $emailAddress);
my ($templatePath, $outputPath, @buildComponents, $build, $overwrite);

# some defaults
$appName      = 'mySpiffyApp';
$emailAddress = 'bradley-cpan@kitefamily.co.uk';
$host         = '127.0.0.1';
$templatePath = '@PREFIX@/share/gestalt';

GetOptions('database=s'      => \$database,
           'host=s'          => \$host,
           'username=s'      => \$username,
           'password=s'      => \$password,
           'help'            => \$help,
           'app-name=s'      => \$appName,
           'email-address=s' => \$emailAddress,
           'template-path=s' => \$templatePath,
           'output-path=s'   => \$outputPath,
           'generate=s'      => \@buildComponents,
           'overwrite'       => \$overwrite);

foreach my $c (@buildComponents)
{
    if ($c ne 'controller' && $c ne 'model' && $c ne 'view' && $c ne 'build')
    {
        $help = 1;
    }
    $build->{$c} = 1;
}

# Default to build everything.
unless ($build->{'controller'} || $build->{'model'} || $build->{'view'} || $build->{'build'})
{
    $build->{'controller'} = 1;
    $build->{'model'}      = 1;
    $build->{'view'}       = 1;
    $build->{'build'}      = 1;
}

$outputPath ||= $appName || 'output';

if (defined ($help) || !defined ($database))
{
    print <<"EOF";
Usage:

$0 --database <db_name> --username <username> --password <password>
  [ --app-name <name> ] [ --email-address <addr> ]
  [ --template-path <path> ] [ --output-path <path> ]
  [ --generate controller|model|view|build ] [--generate controller|model|view|build]
  [ --overwrite ]

This application is designed to connect to the database specified, and based on the
schema found there-in, will generate a model, controller, view, and the build-files
which represent the schema within the database.

By default, the controller, model, view and build files are all generated, however,
you may use the --generate option to restrict which components are generated, eg:

    $0 [other options] --generate controller --generate model

will only generate the controller and model components.

Existing files will not be overwritten unless the --overwrite parameter is set.

Other options: --help        This screen

EOF

    exit 1;
}

my $dbh = DBI->connect("dbi:Pg:dbname=$database;host=$host", $username, $password) || die "Could not connect to database: " . $DBI::errstr;

my $tableSth = $dbh->table_info(undef, undef, undef, "TABLE");

my @tables;
while (my $t = $tableSth->fetchrow_hashref)
{
    if ($t->{'TABLE_TYPE'} eq 'TABLE' && $t->{'TABLE_SCHEM'} eq 'public')
    {
        push @tables, DB::Table->_init($dbh, $t->{'TABLE_NAME'});
    }
}

system("mkdir -p $outputPath/DB/Table");
system("mkdir -p $outputPath/DB/Table/Row");
system("mkdir -p $outputPath/Apache/Request/Controller");

my $template = new Template({INCLUDE_PATH => $templatePath});

print "Processing Templates...\n";

if ($build->{'build'})
{
    processTemplate('Makefile.PL', {moduleName => 'DB'},              'DB/Makefile.PL');
    processTemplate('Makefile.PL', {moduleName => 'DB::Table'},       'DB/Table/Makefile.PL');
    processTemplate('Makefile.PL', {moduleName => 'DB::Table::Row'},  'DB/Table/Row/Makefile.PL');
    processTemplate('Makefile.PL', {moduleName => 'Apache'},          'Apache/Makefile.PL');
    processTemplate('Makefile.PL', {moduleName => 'Apache::Request'}, 'Apache/Request/Makefile.PL');
    processTemplate('Makefile.PL', {moduleName => 'Apache::Request::Controller'},
                    'Apache/Request/Controller/Makefile.PL');
}

if ($build->{'view'})
{
    system("mkdir -p $outputPath/templates");
    if ($overwrite)
    {
        system("cp -R -i --reply=yes $templatePath/templates/*.tt2 $outputPath/templates/");
    }
    else
    {
        system("cp -R -i --reply=no $templatePath/templates/*.tt2 $outputPath/templates/");
    }

    system("mkdir -p $outputPath/html");
    if ($overwrite)
    {
        system("cp -R -i --reply=yes $templatePath/html/* $outputPath/html/");
    }
    elsif (-d "$outputPath/html")
    {
        system("cp -R -i --reply=no $templatePath/html/* $outputPath/html/");
    }
}

my @tableNames;
foreach my $table (@tables)
{
    my $moduleName = ucfirst(lc($table->{'tableName'}));
    $table->{'moduleName'} = $moduleName;
    my $text = Dumper($table);
    $text =~ s/\$VAR1\s+\=\s*//g;

    system("mkdir -p $outputPath/DB/Table/$moduleName"); 
    system("mkdir -p $outputPath/DB/Table/Row/$moduleName"); 
    system("mkdir -p $outputPath/Apache/Request/Controller/$moduleName");

    if ($build->{'build'})
    {
        processTemplate('Makefile.PL', {moduleName => "DB::Table::$moduleName"},
                        "DB/Table/$moduleName/Makefile.PL");
        processTemplate('Makefile.PL', {moduleName => "DB::Table::Row::$moduleName"},
                        "DB/Table/Row/$moduleName/Makefile.PL");
        processTemplate('Makefile.PL', {moduleName => "Apache::Request::Controller::$moduleName"},
                        "Apache/Request/Controller/$moduleName/Makefile.PL");
        processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                        EMAIL     => "$emailAddress",
                                        tableName => $moduleName,
                                        ROW       => 1},
                        "DB/Table/Row/$moduleName/Makefile.am");
        processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                        EMAIL     => "$emailAddress",
                                        tableName => $moduleName,
                                        TABLE     => 1},
                        "DB/Table/$moduleName/Makefile.am");
        processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                        EMAIL     => "$emailAddress",
                                        tableName => $moduleName,
                                        CONTROLLER => 1},
                        "Apache/Request/Controller/$moduleName/Makefile.am");
        processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                        EMAIL     => "$emailAddress",
                                        tableName => $moduleName,
                                        TEMPLATE  => 1},
                        "templates/$moduleName/Makefile.am");
    }

    if ($build->{'model'})
    {
        processTemplate('Table.pm', {DATA => $text, TABLE => $table},
                        "DB/Table/$moduleName/$moduleName.pm");
        processTemplate('Row.pm', {TABLE => $table},
                        "DB/Table/Row/$moduleName/$moduleName.pm");
    }

    if ($build->{'controller'})
    {
        processTemplate('Controller.pm', {TABLE => $table},
                        "Apache/Request/Controller/$moduleName/$moduleName.pm");
    }

    if ($build->{'view'})
    {
        system("mkdir -p $outputPath/templates/$moduleName");
        foreach my $t (qw(show.tt2 list.tt2 edit.tt2 create.tt2))
        {
            if (-e "$outputPath/templates/$moduleName/$t" && $overwrite)
            {
                system("rm -f $outputPath/templates/$moduleName/$t");
            }
            if (!-e "$outputPath/templates/$moduleName/$t")
            {
                system("ln -s ../$t $outputPath/templates/$moduleName/$t");
            }
        }
    }
    push @tableNames, $moduleName;
}

if ($build->{'build'})
{
    processTemplate('configure.in', {APP_NAME => $appName,
                                     EMAIL    => $emailAddress,
                                     TABLES   => \@tableNames},
                    'configure.in');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    TOP_LEVEL => 1},
                    'Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    DB        => 1},
                    'DB/Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    APACHE    => 1},
                    'Apache/Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    REQUEST       => 1},
                    'Apache/Request/Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    ROW       => 1,
                                    BASE      => 1},
                    'DB/Table/Row/Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    TABLE     => 1,
                                    BASE      => 1},
                    'DB/Table/Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    BASE      => 1,
                                    CONTROLLER => 1},
                    'Apache/Request/Controller/Makefile.am');

    processTemplate('Makefile.am', {APP_NAME  => "$appName",
                                    EMAIL     => "$emailAddress",
                                    TABLES    => \@tableNames,
                                    BASE      => 1,
                                    TEMPLATE => 1},
                    'templates/Makefile.am');

    processTemplate('appConfig.cfg.in', {APP_NAME => "$appName",
                                         EMAIL    => $emailAddress,
                                         TABLES   => \@tableNames,
                                         DATABASE_NAME => $database,
                                         DATABASE_HOST => $host,
                                         DATABASE_USERNAME => $username,
                                         DATABASE_PASSWORD => $password},
                    "$appName.cfg.in");

    processTemplate('appSpec.in', {APP_NAME => "$appName",
                                   EMAIL    => $emailAddress,
                                   TABLES   => \@tableNames},
                    "$appName.spec.in");

    processTemplate('apache.conf.in', {APP_NAME => "$appName",
                                       EMAIL    => $emailAddress,
                                       TABLES   => \@tableNames},
                    "$appName.apache.conf.in");

    processTemplate('appStartup.pl.in', {APP_NAME => "$appName",
                                         EMAIL    => $emailAddress,
                                         TABLES   => \@tableNames},
                    "$appName.pl.in");

    foreach my $f (qw(NEWS README AUTHORS ChangeLog bootstrap))
    {
        processTemplate($f,   {APP_NAME => "$appName",
                               EMAIL    => $emailAddress,
                               TABLES   => \@tableNames,
                               DATABASE_NAME => $database,
                               DATABASE_HOST => $host,
                               DATABASE_USERNAME => $username,
                               DATABASE_PASSWORD => $password},
                        $f);
    }
}

system("chmod +x $outputPath/bootstrap");

exit 0;

sub processTemplate
{
    my $input  = shift;
    my $data   = shift;
    my $output = shift;

    if ((-f "$outputPath/$output" && $overwrite) || (!-f "$outputPath/$output"))
    {
        $template->process($input, $data, "$outputPath/$output") || die $template->error;
        print "$outputPath/$output\n";
    }
}