Hatena::Groupcatalyst

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

2008-01-02

Catalystのstarter script

04:51 |  Catalystのstarter script - dann@catalyst を含むブックマーク はてなブックマーク -  Catalystのstarter script - dann@catalyst  Catalystのstarter script - dann@catalyst のブックマークコメント

http://coderepos.org/share/browser/lang/perl/misc/catstarter/typester-catstarter.pl

を見て、自分用に少し書き換えました。typestarさんのstarterは、簡単に必要なファイルが追加できるようになっている点が素晴らしいです。

変更点

typesterさんのに加えて、以下を追加。

テンプレート群をざっと追加してやることで、大分開発を開始しやすくなった気がします。

TODO

今後のTODOとしては、

  • 使うプラグインの見直し
  • ConfigLoaderをConfigLoader::Multiを使うようにする
  • css, template周りの見直し

あたりかなぁ。

ディレクトリ構成

├─lib
│  └─MyApp
│      ├─Controller
│      ├─I18N
│      ├─Model
│      ├─Schema
│      └─View
├─root
│  ├─forms
│  ├─static
│  │  ├─css
│  │  │  └─common
│  │  ├─images
│  │  └─js
│  └─templates
│      ├─common
│      │  ├─config
│      │  └─site
│      └─errors
├─schema
│  └─lib
│      └─MyApp
│          └─Schema
├─script
├─sql
├─t
└─tmp

使い方

新規プロジェクトの作成

% catastarter.pl MyApp -new

スキーマの更新
  • lib/schema/MyApp/Schema以下に関連や追加したいメソッドを追加したスキーマファイルを作成
  • sqlitedb作成後に、以下を実行。

これで、lib/MyApp/Schema以下にスキーマファイルが生成される。

% ./script/myapp_schema_dumper.pl dbi:SQLite:/path/to/sqlite.db

関連や追加のメソッドをスキーマのほうに残して、残りはDBから生成というのは楽でいいなぁ。cool!

スクリプト

#!/usr/bin/env perl

use strict;
use warnings;

use Pod::Usage;
use Getopt::Long;
use Path::Class qw/file dir/;
use Template;
use YAML;

my @render_options = qw/json db email cache authentication i18n templates css perllib helper/;
GetOptions(
    \my %opt,
    qw/help new/, @render_options,
);
pod2usage(0) if $opt{help};

require Catalyst;
require Catalyst::Utils;

# dependency check
require Catalyst::View::TT::ForceUTF8;
require Catalyst::View::JSON;
require JSON::XS;
require Catalyst::Plugin::FillInForm::ForceUTF8;
require Catalyst::Plugin::Authentication;
require Catalyst::Plugin::Session;
require Catalyst::Plugin::Session::Store::FastMmap;
require Catalyst::Plugin::Session::State::Cookie;
require Catalyst::Plugin::I18N;
require Catalyst::Plugin::Static::Simple;
require Catalyst::Plugin::RequestToken;
require Catalyst::Controller::HTML::FormFu;
require Catalyst::Plugin::Unicode;
require Catalyst::Plugin::Email::Japanese;

pod2usage(2) unless @ARGV >= 1;

my $module = shift @ARGV;

# from pmsetup
# $module = "Foo::Bar"
# $dist   = "Foo-Bar"
# $path   = "Foo/Bar.pm"
# $pkg_dir   = "Foo/Bar"
my @pkg  = split /::/, $module;
my $dist = join "-", @pkg;
my $path = join( "/", @pkg ) . ".pm";
my $appprefix = Catalyst::Utils::appprefix($module);
my $pkg_dir = join( "/", @pkg );

my %templates;
my %chunks = grep { $_ } split /(?:^\s*)?=== (\w+) ===/sm, do { local $/; <DATA> };

while (my ($key, $chunk) = each %chunks) {

    for my $text (grep /\S+/, split /^---/m, $chunk) {
        my $obj;
        eval {
            $obj = YAML::Load( $text );
        };
        if ($@) {
            warn $@;
            next;
        }

        if ($obj->{file}) {
            $obj->{file} = eval "qq{$obj->{file}}"
                or die $@;
        }

        push @{ $templates{$key} }, $obj;
    }

}

sub render {
    my $name = shift;
    my $templates = $templates{$name};

    my $base_dir = dir( $dist );

    my $tt = Template->new;
    my $vars = {
        module    => $module,
        pkg       => \@pkg,
        dist      => $dist,
        path      => $path,
        appprefix => $appprefix,
	pkg_dir   => $pkg_dir
    };

    for my $t (@$templates) {
        my $file;
        if ($t->{file}) {
            $file = $base_dir->file( $t->{file} );
        }
        elsif ($t->{component}) {
            (my $p = $path) =~ s/\.pm$//;
            $file = $base_dir->file( 'lib', $p, $t->{component} );
        }
        next unless $file;

	my $content;
	if($t->{ignore}) {
	  $content = $t->{template}; 
        } else {
          $tt->process( \$t->{template}, $vars, \$content );
        }
        
	my $op = '>';
        if ( ($t->{type} || '') eq 'append' ) {
            $op = '>>';
        }

        print $op eq '>>' ? "Updating $file\n" : "Creating $file\n";
        if (!-d $file->parent) {
            $file->parent->mkpath or die $!;
        }

        open my $out, $op, $file or die "$file: $!";
        print $out $content;
        close $out;
    }
}

if ( $opt{new} ) {
    !system "catalyst.pl $module"   or die $?;
    !system "rm -rf $dist/root/*"    or die $?;
    !system "mkdir $dist/tmp"       or die $?;
    !system "mkdir -p $dist/root/static/css" or die $?;
    !system "mkdir -p $dist/root/static/images" or die $?;
    !system "mkdir -p $dist/root/static/js" or die $?;
    !system "mkdir -p $dist/root/templates" or die $?;
    !system "mkdir -p $dist/root/templates/common/site" or die $?;
    !system "mkdir -p $dist/root/templates/common/config" or die $?;
    !system "mkdir -p $dist/root/templates/errors" or die $?;
    !system "mkdir -p $dist/root/templates/email" or die $?;
    !system "mkdir -p $dist/root/forms" or die $?;
    !system "mkdir -p $dist/sql" or die $?;
    !system "mkdir -p $dist/lib/$pkg_dir/I18N/" or die $?;
    render('core');

    !system "./$dist/script/$appprefix\_create.pl view TT TT::ForceUTF8" or die $?;
}
elsif ( !-d $dist ) {
    warn qq{No such directory "$dist". Please run "catsetup.pl $dist -new" first.\n};
    exit;
}

for my $option (@render_options) {

    if ($option eq 'json') {
        !system "./$dist/script/$appprefix\_create.pl view JSON JSON" or die $?;
    }

    if ($option eq 'db') {
        !system "./$dist/script/$appprefix\_create.pl model DBIC DBIC::Schema $module\::Schema" or die $?;
        !system "mkdir -p ./$dist/schema/lib/$pkg_dir/Schema" or die $?;
    }
    render($option);
}

__DATA__

=== core ===
---
file: 'lib/$path'
template: |
  package [% module %];
  use strict;
  use warnings;
  
  use Catalyst::Runtime '5.70';
  use Catalyst qw/
      -Debug
      ConfigLoader
      FillInForm::ForceUTF8
      Authentication
      Session
      Session::Store::FastMmap
      Session::State::Cookie
      Email::Japanese
      Unicode
      I18N
      RequestToken
      Static::Simple
      /;
  our $VERSION = '0.01';
  
  __PACKAGE__->setup;
  
  1;
  
---
file: '$appprefix.yml'
template: |
  ---
  name: [% module %]
  page_title: 'Title' 
  default_view: TT
  View::TT:
    INCLUDE_PATH: __path_to(root/templates)__
    WRAPPER: 'common/site/wrapper.tt2'
  
---
component: Controller/Root.pm
template: |
  package [% module %]::Controller::Root;
  use strict;
  use warnings;
  use base 'Catalyst::Controller';
  __PACKAGE__->config->{namespace} = '';
  
  sub default :Private {
      my ($self, $c) = @_;
      $c->res->status(404);
      $c->stash->{template} = 'errors/404.tt2';
  }
  
  sub index :Private {
      my ($self, $c) = @_;
      $c->stash->{template} = 'index.tt2';
  }
  
  sub end :ActionClass('RenderView') {
      my ($self, $c) = @_;
      $c->res->header( 'Cache-Control' => 'private' );
  }
  
  1;

=== perllib ===
---
file: 'lib/$pkg_dir/Data.pm'
template: |
  package [% module %]::Date;
  
  use strict;
  use warnings;
  use base qw( DateTime );
  
  sub now {
      my($class, %opt) = @_;
      my $self = $class->SUPER::now();
      $self->set_time_zone('Asia/Tokyo');
      $self;
  }
  
  sub parse {
      my ( $class, $format, $date ) = @_;
  
      my $module;
      if (ref $format) {
          $module = $format;
      } else {
          $module = "DateTime::Format::$format";
          $module->require or die $@;
      }
  
      my $dt = $module->parse_datetime($date) or return;
      if ($dt->time_zone->is_floating) {
          $dt->set_time_zone( 'Asia/Tokyo' );
      }
      bless $dt, $class;
  }
  
  sub format {
      my ( $self, $format ) = @_;
      my $module;
      if (ref $format) {
          $module = $format;
      } else {
          $module = "DateTime::Format::$format";
          $module->require or die $@;
      }
  
      $module->format_datetime($self);
  }
  
  1;
  
=== json ===
---
file: '$appprefix.yml'
type: append
template: |+2
  
  View::JSON:
    expose_stash: 'json'
  #  json_driver: XS
    no_x_json_header: 1
  
---
component: Controller/API.pm
template: |
  package [% module %]::Controller::API;
  use strict;
  use warnings;
  use base 'Catalyst::Controller';
  
  sub end :Private {
      my ($self, $c) = @_;
  
      my $json = $c->stash->{json} ||= {};
      $json->{id} = $c->req->param('id');
      $json->{result} ||= '' unless defined $json->{result};
      $json->{error}  ||= '' unless defined $json->{error};
  
      $c->stash->{fillform} = 0;
  
      $c->forward( $c->view('JSON') );
  }
  
  1;

=== db ===
---
file: '$appprefix.yml'
type: append
template: |+2
  
  Model::DBIC:
    connect_info:
      - dbi:mysql:[% appprefix %]
      - root
      - on_connect_do:
          - SET NAMES utf8
  
=== email ===
---
file: '$appprefix.yml'
type: append
template: |+2
  
  email:
    TmplOptions:
      INCLUDE_PATH: __path_to(root/templates/email)__
  #  ForceUTF8: 1
    mailroute:
      via: sendmail
      command: /usr/sbin/sendmail
    From: noreply@example.com

=== authentication ===
  authentication:
    default_realm : 'members'
    realms:
      members:
        credential:
          class: Password
          password_field: password
          password_type: hashed
          password_hash_type: SHA-1
        store:
          class: 'DBIx::Class'
          user_class: 'DBIC::User'
          id_field: 'username'
	
=== cache ===
---
file: '$appprefix.yml'
type: append
template: |+2

  cache:
    backend:
      store: FastMmap
      share_file: __path_to(tmp/cache)__

=== i18n ===
---
file: 'script/update-po.sh'
template: |
  #!/bin/sh
  (find templates/ -name '*.tt2' ; find lib/[% pkg_dir %]/Controller -name '*.pm' )| xargs xgettext.pl -o lib/[% pkg_dir %]/I18N/ja.po

=== templates ===
---
file: 'root/templates/common/site/header.tt2'
template: |

---
file: 'root/templates/common/site/footer.tt2'
template: |
  <div id="copyright">&copy; dann. All rights reserved.</div>
 
---
file: 'root/templates/common/site/layout.tt2'
ignore: 1
template: |
  <div id="container">
  <div id="header">[% PROCESS common/site/header.tt2 %]</div>
  <div id="content">
  [% content %]
  </div>
  <div id="footer">[% PROCESS common/site/footer.tt2 %]</div>
  </div>
  
---
file: 'root/templates/common/site/html.tt2'
ignore: 1
template: |
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>[% c.config.page_title %][% IF title %] - [% title | html %][% ELSE %] - Example -[% END %]</title>
  <link rel="alternate" type="application/atom+xml" title="Atom" href="[% c.uri_for('/feed') %]" />
  <link rel="stylesheet" href="[% c.uri_for('/static/css/common/default.css') %]" type="text/css" />
  <link rel="stylesheet" href="[% c.uri_for('/static/css/common/common.css') %]" type="text/css" />
  <link rel="stylesheet" href="[% c.uri_for('/static/css/site.css') %]" type="text/css" />
  <script type="text/javascript" src="[% c.uri_for('/static/js/site.js') %]"></script>
  </head>
  <body>
  [% content %]
  </body>
  </html>

---
file: 'root/templates/common/site/wrapper.tt2'
ignore: 1
template: |
  [% IF template.name.match('\.(css|js|txt)');
       debug("Passing page through as text: $template.name");
       content;
     ELSE;
       debug("Applying HTML page layout wrappers to $template.name\n");
       content WRAPPER common/site/html.tt2 + common/site/layout.tt2;
     END;
  -%] 
  
---
file: 'root/templates/index.tt2'
template: |
  Hello World

=== helper ===
---
file: 'script/$appprefix\_schema\_dumper.pl'
template: |
  #!/usr/bin/env perl
  
  use strict;
  use warnings;
  
  use FindBin;
  use File::Spec;
  use lib File::Spec->catfile( $FindBin::Bin, qw/.. schema lib/ );
  
  use Path::Class qw/file dir/;
  use DBIx::Class::Schema::Loader qw/make_schema_at/;
  
  my @arg = defined @ARGV ? @ARGV : qw();
  
  die unless @arg;
  
  # do manual delete instead of really_erase_my_files option
  #    for keep MyApp/Schema.pm
  my $libdir = dir($FindBin::Bin, 'lib', 'OpenLine', 'Schema' );
  if (-d $libdir) {
      $libdir->rmtree;
  }
  
  make_schema_at(
      'OpenLine::Schema',
      {   components => [ 'ResultSetManager', 'UTF8Columns' ],
          dump_directory => File::Spec->catfile( $FindBin::Bin, '..', 'lib' ),
  	really_erase_my_files => 1,
          debug          => 1,
      },
      \@arg,
  );
  
  1;


=== css ===
---
file: 'root/static/css/common/default.css'
template: |
  @charset "utf-8";
  	
  /*======================================
  
  	1-1.Yahoo UI Library Fonts CSS ver.2.3.1
  	http://developer.yahoo.com/yui/fonts/
  	*Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  	*http://developer.yahoo.com/yui/license.txt
  
  	Font-size Adjustment
  	
  	77% = 10px	| 	122% = 16px	|	167% = 22px
  	85% = 11px	|	129% = 17px	|	174% = 23px
  	92% = 12px	|	136% = 18px	|	182% = 24px
  	100% = 13px	|	144% = 19px	|	189% = 25px
  	107% = 14px	|	152% = 20px	|	197% = 26px
  	114% = 15px	|	159% = 21px	|
  
  =======================================*/
  
  /*編集不要*/
  body {font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:99%;}
  
  /*======================================
  
  	1-2.Universal selector
  
  =======================================*/
  
  * {
  	line-height: 1.6;
  	font-size: 100%;
  	font-weight: normal;
  	font-style: normal;
  }
  
  /*======================================
  
  	1-3.Structure Module
  
  =======================================*/
  
  body {
  	color: #333;
  	background-color: #fff;
  	font-family: Arial, Helvetica, sans-serif;
  }
  
  /*======================================
  
  	1-4.Text Module
  
  =======================================*/
  
  p,
  pre,
  address,
  cite {
  	margin: 0.5em 20px;
  	font-size: 100%;
  }
  
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
  	margin: 0.5em 20px; 
  }
  
  h1 {
  	font-size: 189%;
  }
  
  h2 {
  	font-size: 159%;
  }
  
  h3 {
  	font-size: 144%;
  }
  
  h4 {
  	font-size: 122%;
  }
  
  h5 {
  	font-size: 107%;
  }
  
  h6 {
  	font-size: 107%;
  }
  
  pre {
  	padding: 0.5em 10px;
  	border: 1px dotted #aaa;
  	width: 90%;
  	overflow: scroll;
  	color: #333;
  	background-color: #f5f5f5;
  	font-family: "Osaka-等幅", monospace;
  }
  
  pre[title]::before {
  	margin-bottom: 0.8em;
  	padding: 0 10px;
  	display: block; 
  	content: attr(title);
  	color: #000;
  	background-color: #fff;
  }
  
  blockquote {
  	margin: 1.5em 20px;
  	padding: 1px 0; 
  	border: 3px solid #eee;
  	background-color: #fff;
  }
  
  blockquote * {
  	color: #666;
  }
  
  blockquote[title]:before {
  	margin: 2px 2px 1em 2px;
  	padding: 0.1em 16px;
  	display: block;
  	content: attr(title); 
  	background-color: #f5f5f5; 
  }
  
  blockquote[cite]:after {
  	padding: 0.8em 20px;
  	display: block; 
  	content: attr(cite);
  	color: #333;
  	text-align: right;
  }
  
  cite {
  	display: block;
  	color: #333;
  	text-align: right;
  }
  
  em {
  	font-weight: bold;
  }
  
  strong {
  	color: #ff4500;
  }
  
  code {
  	font-family: "Osaka-等幅", monospace;
  }
  
  abbr,
  acronym {
  	border-bottom: 1px dotted #aaa;
  	cursor: help;
  }
  
  kbd {
  	border: 1px solid #ccc;
  	padding: 0 0.3em; 
  	background-color: #f5f5f5;
  	font-family: "Osaka-等幅", monospace;
  	text-transform: uppercase;
  }
  
  /*======================================
  
  	1-5.Hypertext Module
  
  =======================================*/
  
  a:link {
  	color: #005585;
  }
  
  a:visited {
  	color: #818f98;
  }
  
  a:hover {
  	color: #80af00;
  }
  
  /*======================================
  
  	1-6.List Module
  
  =======================================*/
  
  ul,
  ol,
  dl {
  	margin: 1em 20px;
  	padding: 1px 0;
  	list-style-position: inside;
  }
  
  li,
  dt,
  dd {
  	margin: 0.1em 10px;
  }
  
  dt {
  	margin-top: 0.6em;
  }
  
  dd {
  	margin-bottom: 0.6em;
  	color: #666;
  }
  
  li li,
  li p,
  li pre,
  li dt,
  li dd,
  dd li,
  dd p,
  dd pre,
  dd dt,
  dd dd {
  	font-size: 100%;
  }
  
  li ul,
  li ol,
  li dl,
  li p,
  dd ul,
  dd ol,
  dd dl,
  dd p {
  	margin: 0.1em 10px;
  }
  
  /*======================================
  
  	1-7.Edit Module
  
  =======================================*/
  
  del {
  	color: #999;
  	text-decoration: line-through;
  }
  
  del[datetime]::before {
  	content: " ( "attr(datetime)"\00524a\009664) ";
  }
  
  ins {
  	border-bottom: 1px dotted #ccc;
  	text-decoration: none;
  }
  
  ins[datetime]::before {
  	content: " ( "attr(datetime)"\004fee\006b63) ";
  }
  
  /*======================================
  
  	1-8.Forms Module
  
  =======================================*/
  
  form {
  	margin: 0.5em 20px;
  	padding: 1px 0; 
  }
  
  form dl,
  form p {
  	margin: 0.5em 10px;
  }
  
  fieldset {
  	border: 1px solid #ddd;
  }
  
  legend {
  	margin: 0 1em;
  	padding: 0 10px;
  }
  
  input,
  textarea {
  	margin: 0.4em 10px;
  	padding: 0.1em 10px;
  	border: 1px solid #ddd;
  	font-family: Arial, Helvetica, "ヒラギノ角ゴ Pro W3",  sans-serif;
  	background-color: #f5f5f5;
  }
  
  input {
  	line-height: 1.2;
  }
  
  input:hover,
  textarea:hover {
  	border: 1px solid #aaa;
  }
  
  input:focus,
  textarea:focus {
  	border: 1px solid #000;
  }
  
  textarea {
  	padding: 0.4em 10px;
  }
  
  /*======================================
  
  	1-9.Tables Module
  
  =======================================*/
  
  table {
  	margin: 0 20px 0.5em 20px;
  	border-collapse: separate;
  	border-spacing: 1px;
  	border: 1px solid #cfd3d6;
  	background-color: #fff;
  }
  
  th,
  td {
  	padding: 0.1em 5px;
  	border: 1px solid #efefef;
  	border-color: #efefef #dce0e3 #dce0e3 #efefef;
  }
  
  th {
  	color: #000;
  	background-color: #eff0f1;
  }
  
  td {
  	border: 1px solid #ddd;
  	background-color: #fff;
  }
  
  /*======================================
  
  	1-10.Image Module
  
  =======================================*/
  
  img {
  	vertical-align: bottom;
  }
  
  a img {
  
  }
  
  a:hover img {
  
  }
  
  /*======================================
  
  	1-11.Object Module
  
  =======================================*/
  
  object,
  embed {
  	margin: 1em 20px;
  }

---
file: 'root/static/css/common/common.css'
template: |
  @charset "utf-8";

---
file: 'root/static/css/site.css'
template: |
  @charset "utf-8";


トラックバック - http://catalyst.g.hatena.ne.jp/dann/20080102