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

use strict;
use warnings;

our $VERSION = '1.001007'; # VERSION

use lib '../lib';
use Getopt::Long;
use File::Path;
use File::Copy;
use File::Find;
use Cwd;

require File::Spec;

my %Show_Plugins;


my %opts = (
    site         => 'zcms',
    core         => '',
    plugins      => '',
    nocore       => '',
    cpan         => '',
    pages        => '',
    show_plugins => '',
);

GetOptions(
    'site=s'    => \ $opts{site},
    'core=s'    => \ $opts{core},
    'plugins=s' => \ $opts{plugins},
    'nocore'    => \ $opts{nocore},
    'show_plugins'    => \ $opts{show_plugins},
    'cpan=s'    => \ $opts{cpan},
    'pages=s'   => \ $opts{pages},
    'overwrite_existing_plugins' => \ $opts{overwrite_existing_plugins},
);

$opts{core} = $opts{site} . '_site'
    unless length $opts{core};


@opts{ qw/core_modules data templates cpan_dir/ } = (
    File::Spec->catdir( $opts{core}, 'App', 'ZofCMS' ),
    File::Spec->catdir( $opts{core}, 'data' ),
    File::Spec->catdir( $opts{core}, 'templates' ),
    File::Spec->catdir( $opts{core}, 'CPAN' ),
);

@opts{ qw/plug_dir execs/ } = (
    File::Spec->catdir( $opts{core_modules}, 'Plugin' ),
    File::Spec->catdir( $opts{core_modules}, 'Execs' ),
);


mkpath(
    [ @opts{ qw/site data templates plug_dir execs  cpan_dir/ } ],
    1, 0777
);

if ( $opts{cpan} ) {
    prepare_cpan( @opts{ qw/core cpan/} );
}

if ( $opts{show_plugins} ) {
    show_plugins( @opts{qw/core  site/} );
    exit;
}

if ( $opts{plugins} ) {
    prepare_plugins( @opts{qw/core plugins  overwrite_existing_plugins/} );
}

if ( $opts{pages} ) {
    for my $page ( split /,/, $opts{pages} ) {
        my @dirs = split '/', $page;
        if ( @dirs > 1 ) {
            mkpath( [
                    File::Spec->catdir( $opts{core}, 'data', @dirs[0 .. $#dirs - 1] )
                ], 1, 0777
            );
            mkpath( [
                    File::Spec->catdir( $opts{core}, 'templates', @dirs[0 .. $#dirs - 1] )
                ], 1, 0777
            );
        }
        $dirs[-1] .= '.tmpl';
        my $data = File::Spec->catfile( $opts{core}, 'data', @dirs );

        my @template_dirs = @dirs;
        my $c = do( File::Spec->catfile( $opts{core}, 'config.txt' ) )
            or die "Failed to load config.txt $@ $!";

        if ( $c->{zcms_template_extension} ) {
            $template_dirs[-1] =~ s/\.tmpl/$c->{zcms_template_extension}/;
        }

        my $template = File::Spec->catfile(
                $opts{core}, 'templates', @template_dirs
        );

        if ( -e $data ) {
            print "$data file exists, omitting...\n";
        }
        else {
            my $fh_data;
            unless ( open $fh_data, '>', $data ) {
                warn "Failed to open $data [$!]\n";
            }
            else {
                close $fh_data;
            }
        }

        if ( -e $template ) {
            print "$template file exists, omitting...\n";
        }
        else {
            my $fh_template;
            unless ( open $fh_template, '>', $template ) {
                warn "Failed to open $template [$!]\n";
            }
            else {
                my $body_file = File::Spec->catfile( @dirs );
                my $title = join ' - ', map do { s/-(.)/ \u$1/g; $_ },
                    map ucfirst, grep length, reverse split m{/}, $page;

                print $fh_template <<"END";
use strict;
use warnings;

{
    body => \\'$body_file',
    title => '$title',
    plugins => [
    ],
};

__END__
END
                close $fh_template;
            }
        }
    }
}

exit
    if $opts{nocore};

@opts{ qw/index base index_tmpl index_html_tmpl 404  config_file/ } = (
    File::Spec->catfile( $opts{site}, 'index.pl' ),
    File::Spec->catfile( $opts{core}, 'data', 'base.tmpl' ),
    File::Spec->catfile( $opts{core}, 'templates', 'index.tmpl' ),
    File::Spec->catfile( $opts{core}, 'data', 'index.tmpl' ),
    File::Spec->catfile( $opts{core}, 'templates', '404.tmpl' ),
    File::Spec->catfile( $opts{core}, 'config.txt' ),
);

open my $fh, '>', $opts{index}
    or die "Failed to open/create $opts{index} [$!]";

print $fh make_index_pl( $opts{core} );
close $fh;
chmod 0755, $opts{index};

open $fh, '>', $opts{base}
    or die "Failed to open/create $opts{base} [$!]";

print $fh make_base();

open $fh, '>', $opts{index_tmpl}
    or die "Failed to open/create $opts{index_tmpl} [$!]";

print $fh make_index_tmpl();


open $fh, '>', $opts{index_html_tmpl}
    or die "Failed to open/create $opts{index_html_tmpl} [$!]";

print $fh make_index_html_tmpl();


open $fh, '>', $opts{404}
    or die "Failed to open/create $opts{404} [$!]";

print $fh make_404();


open $fh, '>', $opts{config_file}
    or die "Failed to open/create $opts{config_file} [$!]";

print $fh make_config_file( $opts{core} );


copy_module( $_, $opts{core} )
    for qw/
        App::ZofCMS::Config
        App::ZofCMS::Template
        App::ZofCMS::Output
    /;

sub show_plugins {
    my ( $core, $site ) = @_;

    find(\&show_plugins_plugin_found, File::Spec->catdir($core, 'templates') );

    if ( my $t = do File::Spec->catfile( $core, 'config.txt' ) ) {
        for my $key ( grep /^plugins\d*/, keys %$t ) {
            ref $t->{$key} eq 'ARRAY'
                or next;

            for ( @{ $t->{$key} || [] } ) {
                if ( ref $_ eq 'HASH' ) {
                    $Show_Plugins{ (keys %$_)[0] } = 1;
                    next;
                }
                $Show_Plugins{ $_ } = 1;
            }
        }

        for my $key ( grep /^plugins\d*/, keys %{ $t->{template_defaults} } ) {
            ref $t->{template_defaults}{$key} eq 'ARRAY'
                or next;

            for ( @{ $t->{template_defaults}{$key} || [] } ) {
                if ( ref $_ eq 'HASH' ) {
                    $Show_Plugins{ (keys %$_)[0] } = 1;
                    next;
                }
                $Show_Plugins{ $_ } = 1;
            }
        }
    }

    print "The following plugins were found:\n";
    print "$_\n" for map "App::ZofCMS::Plugin::$_", sort keys %Show_Plugins;
    print "--end--\n";
    print "zofcms_helper --nocore --site $site --plugins "
        . join(q|,|, sort keys %Show_Plugins) . "\n";
    exit;
}

sub show_plugins_plugin_found {

    return
        if -d;

    print "About to process template: $File::Find::name\n";

    my $t = do $_
        or return;

    ref $t eq 'HASH'
        or return;

    for my $key ( grep /^plugins\d*/, keys %$t ) {
        ref $t->{$key} eq 'ARRAY'
            or return;

        for ( @{ $t->{$key} || [] } ) {
            if ( ref $_ eq 'HASH' ) {
                $Show_Plugins{ (keys %$_)[0] } = 1;
                next;
            }
            $Show_Plugins{ $_ } = 1;
        }
    }
}


sub prepare_plugins {
    my ( $core, $plugins, $do_overwrite ) = @_;
    my @plugins = map "App::ZofCMS::Plugin::$_", split /\s*,\s*/, $plugins;

    copy_module( $_, $core, $do_overwrite )
        for @plugins;
}

sub prepare_cpan {
    my ( $core, $mods ) = @_;
    my @mods = split ',', $mods;

    my @paths;
    for ( @mods ) {
        my @bits = split /::/;
        pop @bits;
        push @paths, File::Spec->catdir( $core, 'CPAN', @bits );
    }
    mkpath( \@paths, 1, 0777 );

    copy_module( $_, File::Spec->catdir($core, 'CPAN') )
        for @mods;
}

sub copy_module {
    my ( $mod, $core, $do_overwrite ) = @_;
    eval "use $mod;";
    if ( $@ ) {
        print "ERROR: $@\n";
        return;
    }

    my $mod_file = $mod . '.pm';
    $mod_file =~ s|::|/|g;

    my $mod_path = $mod_file;
    $mod_path =~ s|[^\\/]+$||;

    mkpath( [ File::Spec->catdir( $core, $mod_path ) ], 1, 0777 );

    my $core_mod_file = File::Spec->catfile( $core, $mod_file );

    if ( -e $core_mod_file and not $do_overwrite ) {
        print "$core_mod_file already exists. Use `--overwrite_existing_plugins` option to overwrite\n";
    }
    else {
        copy $INC{$mod_file}, $core_mod_file
            or print "ERROR: failed to copy $mod [$!]";
    }
}

sub make_index_pl {
    my $core = '../' . shift;
    if ( substr($core, -1) eq '/' ) {
        $core = substr $core, 0, -1;
    }
    my $code = <<'END_CODE';
#!/usr/bin/env perl

use strict;
use warnings;

use lib qw([{LIB}] [{LIB}]/CPAN);
use App::ZofCMS::Config;
use App::ZofCMS::Template;
use App::ZofCMS::Output;

use CGI::Carp qw/fatalsToBrowser/;

my $config = App::ZofCMS::Config->new;

my $conf = $config->load( '[{CONFIG}]' );

my $template;

RELOADS: {
    $template = App::ZofCMS::Template->new( $config );

    $template->load;
    $template->prepare_defaults;
    $template->execute_before;
    $template->assemble;
    $template->execute
        or redo RELOADS;
}

my $output = App::ZofCMS::Output->new( $config, $template );
print $output->headers;
print $output->output;
exit;
END_CODE

    $code =~ s/\Q[{LIB}]/$core/g;
    $code =~ s|\Q[{CONFIG}]|${core}/config.txt|;
    return $code;
}

sub make_base {
    return <<'END_HTML';
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title><tmpl_var name="title"></title>

<link rel="stylesheet" type="text/css" href="/main.css" media="screen,tv,projection">
<link rel="stylesheet" type="text/css" href="/print.css" media="print">

<!--[if IE]>
    <link rel="stylesheet" type="text/css" href="/ie.css" media="screen,tv,projection">
<![endif]-->

<div id="main">
    <tmpl_var name="body">
</div>
END_HTML
}

sub make_index_tmpl {
    return <<'END_CODE';
use strict;
use warnings;

{
    body => \'index.tmpl',
    title => '',
}
END_CODE
}

sub make_index_html_tmpl {
    return 'Fill me up!'
}

sub make_404 {
    return <<'END_CODE';
{
    body => \'404.tmpl',
    title => '404 - Page Not Found',
}
END_CODE
}

sub make_config_file {
    my $core_dir = shift;
    return <<"END_CONFIG";
use strict;
use warnings;

{
    valid_pages => {
        dirs => [
            '/'
        ],
    },
    data_store => '../$core_dir/data',
    templates    => '../$core_dir/templates',
    template_defaults => {
        conf => {
            base => 'base.tmpl',
        },
    },
}
END_CONFIG
}

1;
__END__

=head1 NAME

zofcms_helper - helper script for ZofCMS web-framework/templating system

=head1 SYNOPSYS

mkdir site_dir;
cd site_dir;
zofcms_helper --site example --plugins QueryToTemplate,DBI,SomeOtherPlugin;

=head1 DESCRIPTION

C<zofcms_helper> is a helper script supplied with L<App::ZofCMS>. It is used
to create "startup" structure for your site as well as adding plugin files,
which isn't necessary if you installed them on your server via CPAN.

=head1 SUPPORTED ARGUMENTS

=head2 C<--site>

    zofcms_helper --site example

The C<--site> argument specifies the directory to use for your web
accessible directory of ZofCMS, this is where you'll have your C<index.pl>
file (the helper script will create it). If you don't specify the
C<--core> argument (see below) it will automatically created from the
C<--site> argument by appending C<_site> to whatever you specify. If you
don't provide C<--site> argument, the default is C<zcms>.

=head2 C<--core>

    zofcms_helper --site example --core example_core

The C<--core> argument specifies the name of directory for the "data"
directory of ZofCMS, in other words, this is web-inaccessible directory
in which you will have your config file, "data_storage" and "templates"
directory (see L<App::ZofCMS::Config> for description) as well as
any of ZofCMS plugins. The helper script creates config file, all of those
directories as well as directory C<Execs> (see L<App::ZofCMS::Template>)
and C<Extras> (see L<App::ZofCMS::Extras>). The helper script also creates
basic C<base.tmpl>, C<index.tmpl> and C<404.tmpl> in C<data/> directory
inside the directory specified by C<--core> as well as basic
C<index.tmpl> inside C<templates/> directory. If C<--core> parameter is not
specified it will be created by appending C<_site> to whatever you've
gave to C<--site> argument.

B<Note:> currently there is no support [in the helper script]
to have C<--core> point to a directory on a different level than C<--site>.
If you desperatelly need this please let me know and I will add that
support. For now, you can simply edit the created C<index.pl> file, in
particular it's two lines:

    use lib '../core_dir';

    ....

    my $conf = $config->load( '../core_dir/config.txt' );


=head2 C<--plugins>

    zofcms_helper --site zcms --plugins DBI,QueryToTemplate,OtherPlugin

This options takes a comma (C<,>) separated list of plugin names to copy
over into your "core" directory. This is useful if your server does
not support module installation from CPAN; i.e. the helper script
copies the modules installed on your system into "core" directory from
where ZofCMS can load them, thus when you are done with your site you
can simply upload C<--site> and C<--core> directories to your server and
everything should work just fine.

B<NOTE:> do not include the C<App::ZofCMS::Plugin::> part of the name
of the modules, the above command installs L<App::ZofCMS::Plugin::DBI>,
L<App::ZofCMS::Plugin::QueryToTemplate> and
C<App::ZofCMS::Plugin::OtherPlugin> modules into the "core" directory.

B<Note:> the command presented above will recreate the "core" files and
C<index.pl> file. To avoid that, i.e. just add the plugins, use the
<--nocore> option

=head2 C<--overwrite_existing_plugins>

    zofcms_helper --site zcms --plugins DBI,QueryToTemplate,OtherPlugin --overwrite_existing_plugins

The C<--plugins> option above will NOT overwrite any existing files by default. To force overwriting,
use the C<--overwrite_existing_plugins> option.

=head2 C<--cpan>

    zofcms_helper --site zcms --cpan Data::Tranformer,Foo::Bar::Baz

This option has the same purpose as the C<--plugins> except this one
copies your installed CPAN modules to $core_dir/CPAN/

For example, Data::Transformer module is required by L<App::ZofCMS::Tagged>
plugin, using the C<--cpan> command you can copy it over into ZofCMS
"core directory" and upload your application to the server that does
not have Data::Transformer installed. B<Note:> this option is not that
smart, it's not going to copy anything but the actual .pm file for
the module you've specified.

=head2 C<--nocore>

    zofcms_helper --nocore --site zcms --plugins DBI,QueryToTemplate

The C<--nocore> option tells the helper script not to create any "core"
directories or files, the command above will only install C<DBI> and
C<QueryToTemplate> plugins into the "core" directory.

=head2 C<--pages>

    zofcms_helper --nocore --site zcms --pages foo,foo/bar,foo/bar/baz

The C<--pages> argument tells C<zofcms_helper> to create "page templates". The value to
C<--pages> argument is a comma separated list of pages you wish to create. With this argument
the script will try to be smart and create an empty file in C<data> directory as well as
a ZofCMS Template file in C<templates> directory. Just run the script with the example above
and check out your data and templates dirs to see the result.

=head2 C<--show_plugins>

    zofcms_helper --nocore --site zcms --show_plugins

Takes no arguments, when present, zofcms_helper will show a list of all the plugins
used by the site (note that plugins listed in Main Config File under C<dir_defaults> key
are currently not shown by this function).

=head1 REPOSITORY

Fork this module on GitHub:
L<https://github.com/zoffixznet/App-ZofCMS>

=head1 BUGS

To report bugs or request features, please use
L<https://github.com/zoffixznet/App-ZofCMS/issues>

If you can't access GitHub, you can email your request
to C<bug-App-ZofCMS at rt.cpan.org>

=head1 AUTHOR

Zoffix Znet <zoffix at cpan.org>
(L<http://zoffix.com/>, L<http://haslayout.net/>)

=head1 LICENSE

You can use and distribute this module under the same terms as Perl itself.
See the C<LICENSE> file included in this distribution for complete
details.

=cut