Hatena::Groupcatalyst

dann@catalyst このページをアンテナに追加 RSSフィード

2008-03-26

catsetup.pl - Catalystアプリ生成用スターター

01:04 |  catsetup.pl - Catalystアプリ生成用スターター - dann@catalyst を含むブックマーク はてなブックマーク -  catsetup.pl - Catalystアプリ生成用スターター - dann@catalyst  catsetup.pl - Catalystアプリ生成用スターター - dann@catalyst のブックマークコメント

フレームワーク用のStarterでは、テンプレート群やCSSJavaScriptのライブラリや、各種のベースクラスなどファイル群など、生成したりそのままコピーしたいファイル群が多いので、pmsetupなどのように1ファイルだけで管理するより、各種ファイルはディレクトリ構成のあるSkeltonディレクトリ中にテンプレートという形で置き、そのテンプレート中の変数や変数化されたディレクトリ名を置換するという形で、アプリケーションの雛形を生成するほうがよいのではないかと思っていました。

というような先週tomyhero邸でしていたところ、tomyheroさんがOreOre starter(http://svn.coderepos.org/share/lang/perl/OreOre-Starter/trunk/lib/OreOre/Starter.pm)を作ってくれたので、全力でパクってみました。

ロジックは再構成して、1ファイルにまとめました。また、starterでは設定ファイルを必要としない形のほうがいいかなというのと、汎用性を捨ててCatalyst専用Starterにしたほうが使いやすいかなということで、汎用性を捨ててConfigurationいらずで使えるようにしています。

後でCodeReposCatalystアプリのSkeltonと一緒にUpします。

catsetup.pl

#!/usr/bin/env perl

use strict;
use warnings;

use File::Spec;
use File::Path;
use Path::Class;
use IO::All;
use Getopt::Long;
use Pod::Usage;

GetOptions( \my %opt, qw/help/, 'skel=s', 'skel_path=s' );
pod2usage(2) if $opt{help};

check_dependencies();

my $app_name       = shift @ARGV;      # MyApp
my $lower_app_name = lc($app_name);    # myapp
my $upper_app_name = $app_name;        # MYAPP

my @exclude_rules = qw/\.svn \.cvs/;

my $app_dir = $app_name;

my $skelton_dir = skelton_dir();

pod2usage(2) unless $skelton_dir;

my $variables = {
    app_name       => $app_name,
    lower_app_name => $lower_app_name,
    upper_app_name => $upper_app_name,
};

my @all_files = ();
my @all_dirs  = ();
mk_app();

sub skelton_dir {
    if (   !$opt{skel}
        && !$opt{skel_path}
        && -d "$ENV{HOME}/.catsetup/skelton/default" )
    {
        return "$ENV{HOME}/.catsetup/skelton/default";
    }

    if ( $opt{skel_path} ) {
        return $opt{skel_path};
    }

    if ( $opt{skel} && -d "$ENV{HOME}/.catsetup/skelton/$opt{skel}" ) {
        return "$ENV{HOME}/.catsetup/skelton/$opt{skel}";
    }
    return;
}

sub check_dependencies {

    #basic func
    require Catalyst::Plugin::BuildURI;
    require Catalyst::Plugin::Static::Simple;
    require Catalyst::Plugin::RequestToken;

    # Encoding and I18N
    require Catalyst::Plugin::FillInForm::ForceUTF8;
    require Catalyst::View::TT::ForceUTF8;
    require Catalyst::Plugin::I18N;
    require Catalyst::Plugin::Unicode;

    # Authentication
    require Catalyst::Plugin::Authentication;
    require Catalyst::Plugin::Session;
    require Catalyst::Plugin::Session::Store::FastMmap;
    require Catalyst::Plugin::Session::State::Cookie;

    # Validation
    require Catalyst::Plugin::FormValidator::Simple;
    require Catalyst::Plugin::FormValidator::Simple::Auto;

    # Email
    require Catalyst::Plugin::Email::Japanese;

    # API
    require Catalyst::Action::REST;
    require Catalyst::Controller::Resources;
    require Catalyst::View::JSON;
}

sub mk_app {
    mk_app_dir();
    collect_skelton_dir_and_files();
    mk_module_dirs( \@all_dirs );
    render_templates( \@all_files );
    make_scripts_executable();
}

sub mk_app_dir {
    mk_dir($app_dir);
}

sub mk_module_dirs {
    my $dirs = shift;
    for my $dir (@$dirs) {
        my $to_dir = to_file_path($dir);
        mkpath($to_dir);
    }
}

sub collect_skelton_dir_and_files {
    dir($skelton_dir)->recurse(
        callback => sub {
            my $file = shift;

            for my $exclude_rule (@exclude_rules) {
                if ( $file =~ /$exclude_rule/ ) {
                    return;
                }
            }

            if ( -f $file ) {
                $file =~ s/$skelton_dir//;
                $file =~ s/^\///;
                push @all_files, $file;
            }

            if ( -d $file ) {
                $file =~ s/$skelton_dir//;
                $file =~ s/^\///;
                push @all_dirs, $file;
            }
        }
    );

}

sub render_templates {
    my $templates = shift;
    my $tt        = Template->new(
        { INCLUDE_PATH => $skelton_dir, TAG_STYLE => 'star' } );
    for my $t (@$templates) {
        my $to_file = to_file_path($t);
        my $output;
        $tt->process( $t, $variables, \$output )
            || die $tt->error();
        $output > io($to_file);
        print qq/generated "$to_file"\n/;
    }
}

sub to_file_path {
    my $original_file_path = shift;
    foreach my $key ( keys %{$variables} ) {
        $original_file_path =~ s/__$key\__/$variables->{$key}/g;
    }
    return File::Spec->catfile( $app_dir, $original_file_path );
}

sub mk_dir {
    my ($dir) = @_;
    if ( -d $dir ) {
        print qq/ exists "$dir"\n/;
        return 0;
    }
    if ( mkpath $dir ) {
        print qq/created "$dir"\n/;
        return 1;
    }

    die "Couldn't create $dir";
}

sub make_scripts_executable {
    !system "chmod 755 ./$app_dir/script/*" or die $?;
}


=head1 SYNOPSIS

catsetup.pl MyApp

Options:
  --skel Catalystアプリのskelton
        ~/.catsetup/skelton/<skel>
  --skel_path Catalystアプリのskeltonのディレクトリ

  何もオプションが指定されない場合は、
  ~/.catsetup/skelton/default
  のskeltonが選択される。

=cut

=head1 DESCRIPTION

Catalstアプリの雛形から、Catalystアプリを生成するStarter

# 誰かがこれをさらにパクって修正しちゃってくれたりするといいなぁ。

# 週末の昼の部は何を作るかなぁ。週末はこれ作ろうかと思っていたんだけど。

TODO

  • パスの扱い方がいまいち
    • 収集するディレクトリとファイルのパスはPath::Classのオブジェクトとして扱う
  • 変数化されたパスのreplaceはPath::Classにメソッドはやしてやってしまいたい
  • ディレクトリとファイルのパス群がグローバル変数になっているのを修正したい
トラックバック - http://catalyst.g.hatena.ne.jp/dann/20080326