あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

MT::Object をワンライナーしやすくする拡張書いた

コマンドラインで、MT::Objectを取得して、加工して、出力してみたいなことすることが結構あるんですけど、MT::Objectは大変ワンライナーしにくい印象でした。

MTのシステム管理者のIDとパスワードがわからない時、コマンドで強制的に作ることがあるんですけど、MT::Objectがワンライナーしにくいので、下記のようなファイルを用意してから、実行するというまどろっこしさです。

試しにワンライナーしてみると下記のような感じでしょう。

$ perl -I{lib,extlib} -MMT -e 'my $taiju = MT->instance->model("author")->get_by_key({name=>"taiju"});$taiju->nickname("taiju");$taiju->email("higashi@taiju.info");$taiju->auth_type("MT");$taiju->status(1);$taiju->set_password("password");$taiju->is_superuser(1);$taiju->save or $taiju->errstr;printf "You can use taiju (id=%d) (password=password)! taiju is suupppeeeer user!!\n", $taiju->id;'

...セミコロン......

これがワンライナーと言えるなら、minifyされた jQueryですらワンライナーになってしまいますね。

MT::Object::Chaining

ということで作ったのが下記になります。

MT::Object::Chaining - Methods chaining for MT::Object

MT::Object::Chainingを使うと、下記のように書けます。

perlコマンドのように、Iオプションや、Mオプション指定して、インスタンス取得するのすらダルいので、tools/chainというスクリプトでその辺をカバーしてます。

mオプションでモデルを指定して、eオプションでeval文字列を指定します。

$ cd /path/to/mt && tools/chain -m author -e '$model->get_by_key({ name => "taiju" })->nickname("taiju")->email("higashi\@taiju.info")->auth_type("MT")->status(1)->set_password("password")->is_superuser(1)->save->tap(sub { printf "You can use taiju (id=%d) (password=password)! taiju is suupppeeeer user!!\n", shift->id })'

こんな感じでメソッドチェーンで書けるようにする機能を提供するような感じですね。

まとめ

これでMT::Objectをワンライナーしやすくなりました。

jsonで吐くAPIなども用意しているので、

$ tools/chain -m author -e '$model->load->json' | jq '.[] | {name, email}'

みたいな感じでjqに渡したりなど、捗るかもしれません。

また、mapメソッドで一括置換して保存とか、reduceで加工した結果を出力するとか、普段使いに個人的に便利だと思う機能も入れました。

まだ、v0.1なのと、統一感に欠ける気もしているので、インターフェイスの変更するかもしれません。

詳しい使い方は、README.pod を参照下さい。

https://github.com/taiju/MT-Object-Chaining/blob/master/README.pod

ほなの。

MT アプリケーションを Sinatra like に記述できる MT::App::Lite 作った

Movable Type には、MT::App という Web アプリケーションを記述するための基底クラスがありますが、結構慣れが必要なのと癖があることもあって、もっと手軽に Sinatra like にアプリケーションを記述できる、フレームワーク的なものが欲しいなーと常々思っていました。

とりあえずいろいろやりたいことは残っていますが、動くところまでできたので、紹介します。

現状では、PSGI でのみ動作します。

名前

概要

以下のような感じでMTアプリケーションを記述します。

package MyLiteApp;
use strict;

use MT::App::Lite;

setup Renderer => 'Xslate';

get '/' => sub {
  my $app = shift;
  $app->render('index.tt', {
    blog => MT->model('blog')->load(1),
    entries => [MT->model('entry')->load({blog_id => 1})],
  });
};

get '/entry/:id' => sub {
  my $app = shift;
  $app->render('entry.tt', {
    blog => MT->model('blog')->load(1),
    entry => [MT->model('entry')->load($app->param('id'))],
  });
};

1;

__DATA__

@@ index.tt
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title><: $blog.name :></title>
</head>
<body>
  <ul>
  : for $entries -> $entry {
    <li><a href="<: $entry.permalink :>"><: $entry.title :></a></li>
  : }
  </ul>
</body>
</html>

@@ entry.tt
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title><: $entry.title :> | <: $blog.name :></title>
</head>
<body>
  <h1><: $entry.title :></h1>
  <div>
    <: $entry.text | mark_raw :>
  </div>
</body>
</html>

1ファイル完結型です。(正確にはプラグインの config.yaml が必要)
多分、フォームとか作るのに便利だと思います。

仕様

MT アプリケーションのハンドラ内で、Router::Simple::Sinatraish でエクスポートされる、get や post などの関数を使って、ルートを記述します。

そのファイル中の __END__ セクションに、テンプレートを書きます。
__END__ セクションに記述したテンプレートは、Data::Section::Simple によって、MT::App::Lite に渡されます。

テンプレートは、Text::Xslate を標準のテンプレートエンジンにしています。申し訳程度に MTML 用のテンプレートエンジンも用意しましたが、ここでわざわざ使う必要はない気がします。

MT::App::Lite::Renderer::Foo という形で、render メソッドと render_string メソッドを用意すれば、任意のテンプレートエンジンを利用することは可能です。

また、MT::App::Lite を use すると、自動的に MT::App を継承することになるので、MT::App っぽい書き方もできるのかもしれません。

基本的には、MT::App に Web アプリケーションを記述するための機能はひと通り揃っているので、便利っぽいモジュールを糊付けしたような実装です。

MT アプリケーションのプラグインを作成する

このモジュールを使った、MTアプリケーションのプラグインの作り方です。

最小限の config.yaml と、ハンドラを用意します。

config.yaml
name: MyLiteApp
id:   myliteapp

applications:
  lite_app:
    handler: MyLiteApp
    script: sub { 'app' }
    cgi_path: sub { '/' }
MyLiteApp (handler)
package MyLiteApp;
use strict;

use MT::App::Lite;

setup Renderer => 'Xslate';

get '/' => sub {
  my $app = shift;
  $app->render('index.tt', {
    blog => MT->model('blog')->load(1),
    entries => [MT->model('entry')->load({blog_id => 1})],
  });
};

get '/entry/:id' => sub {
  my $app = shift;
  $app->render('entry.tt', {
    blog => MT->model('blog')->load(1),
    entry => [MT->model('entry')->load($app->param('id'))],
  });
};

1;

__DATA__

@@ index.tt
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title><: $blog.name :></title>
</head>
<body>
  <ul>
  : for $entries -> $entry {
    <li><a href="<: $entry.permalink :>"><: $entry.title :></a></li>
  : }
  </ul>
</body>
</html>

@@ entry.tt
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title><: $entry.title :> | <: $blog.name :></title>
</head>
<body>
  <h1><: $entry.title :></h1>
  <div>
    <: $entry.text | mark_raw :>
  </div>
</body>
</html>

これで、http://yourdomain/app/ や、http://yourdomain/app/entry/:id にアクセスするとテンプレートの内容が描画されます。

まとめ

本当は、Movable Type Advent Calendar 2013 の自分担当日のネタとして作ろうと考えていたんですけど、実装する暇がなく、今日に至ります。

便利な気がするので、メンテしていこうと思います。まだいろいろと実装したいことや、実装すべきことがあります。

現時点ではちょっとした Viewer やフォームを作るのには便利な気がしています。

Ruby の method_missing 的な tag_missing コールバックを MT に追加してみる

Movable Type Advent Calendar 2013 の8日目です。

Rubymethod_missing だったり、PerlAUTOLOAD 的なものが、MT のタグを利用する時に使えると便利かなと思って、TagMissingプラグインなるものを作ってみました。存在しないタグを利用しようとした時に、tag_missing というコールバックを呼ぶ単純なものです。ダイナミックは未対応です。

ただ、これを実現する方法は、MT の処理をモンキーパッチするしかなさそうだったのと、作ってみたものの、インターフェイスも微妙な感じがしますが、せっかくなので公開します。

TagMissing プラグイン

タグが存在しない時のエラー処理は、MT::Builder::build を見る限り、下記のあたりにあって、

            my $hdlr = $ctx->handler_for( $t->tag );
            my ( $h, $type, $orig ) = $hdlr->values;
            my $conditional = defined $type && $type == 2;

            if ($h) {
# ... 省略 ...
            }
            else {
                if ( $t->tag !~ m/^_/ ) {    # placeholder tag. just ignore
                    return $build->error(
                        MT->translate( "Unknown tag found: [_1]", $t->tag ) );
                }
            }

https://github.com/movabletype/movabletype/blob/de8fc5aba805a113075aa7e58882855554dd5d5a/lib/MT/Builder.pm#L475-L591

MT::Template::Context::handler_for メソッドの結果次第で、分岐の経路が変わる感じなので、MT::Template::Context::handler_for の中にコールバックを仕込むのが確実かなと思ったので、TagMissing プラグインでは、handler_for をパッチする方針にしました。

モンキーパッチする時は、本体のバージョンアップの影響をなるべく受けないようにした方が良いので、無駄が多い感じですが、handler_forをコールバック前後で2回呼び出す形にしました。

package TagMissing::Patch;

use strict;
use warnings;

sub init {}

use MT::Template::Context;

my $orig_handler_for = \&MT::Template::Context::handler_for;

{
  no warnings 'redefine';
  *MT::Template::Context::handler_for = sub {
    my $ctx = shift;
    my $tag = lc $_[0];
  
    my $hdlr = $orig_handler_for->( $ctx, $tag );
    my ( $h ) = $hdlr->values;
  
    MT->run_callbacks( 'tag_missing', $ctx, $tag ) unless $h;

    $orig_handler_for->( $ctx, $tag )
  
  };
}

1;

https://github.com/taiju/mt-plugin-TagMissing/blob/8b69c4f3c6f372de7de032b68260c042a4b29eea/plugins/TagMissing/lib/TagMissing/Patch.pm

インターフェイス的にかなり微妙な感じですが、tag_missing コールバックの中で、$ctx->{__handlers}{tag_name_foo}に MT::Templage::Handler 形式でハンドラを代入するなどして、タグの動作を動的に指定することができます。

AutoUnless プラグイン

そんな感じで、tag_missing コールバックを使ったタグのリファレンス実装として、AutoUnless プラグインというのも作ってみました。

mt:IfWebsite みたいな、If という単語の含まれたタグがいろいろありますが、mt:UnlessWebsite みたいな逆の意味を持つタグがあったりなかったりするので、mt:*unless*がなければ自動的に定義するプラグインです。

tag_missing が呼ばれた時に、動的に定義する感じの実装で、tag_missing コールバックのサンプルプラグインにもなっています。

MT の t ディレクトリにある、35-tags.t を丸々移して、mt:*If*mt:*Unless* に書き換えた上で、テスト結果を逆にしたテストを書いてみましたが、一部を除いてうまく動いているようです。

https://github.com/taiju/mt-plugin-AutoUnless/blob/master/plugins/AutoUnless/t/tags.t

動的にいくつかのタグを定義するような時には、tag_missing コールバックは便利に使えそうです。

まとめ

あると便利な気はするんですけど、アイデアありきで作ってみたものの、実装は正直微妙な感じになってしまいました。

今年はコードを書くより、読む時間が圧倒的に多い年でした。来年は、書く時間を増やせたらいいなと思います。

Perlでメソッドチェーンのメソッドを動的に決定する

Perl でメソッドを呼び出す時、

$class->$method;

みたいな呼び出し方できるのは知ってたけど、

$class->${ \'method' }

みたいな感じで、文字列のスカラーリファレンスをデリファレンスすることでも同じことができることを知った。

これを用いることでメソッドチェーンのメソッドを動的に決定することができる。

上記のように pass とかいうメソッドを用意しておけば、不要なメソッドはスキップするということもできるので、そのチェインを実行するかどうかも動的に決定できる。

コードは結構キモい感じだけど、それなりに実用性はありそう。

MTDDC 2013の感想文

感想文書くの遅くなりましたが、先日、MTDDC 2013に参加しました。

LTについて

Data APIをさわるキッカケを作りたいなと思って、さわる前にLT応募したんですけど、実際にさわってみるのはギリギリになってしまって、焦ってたんですが、5分だったのでなんとかなりました。

とりあえず、できることを確認して、サンプルアプリ作ってみて、ファーストインプレッションをまとめた感じです。

下記にLTのスライドを置いておきました。

LT用に作ったサンプルアプリケーション

Movable Type Data APIは、結構ポテンシャルが高いなと思っていて、スライドにも書きましたが、MTの管理画面を作るだけじゃなくて、JSONを使ったフロントエンドアプリが普通に作りやすくなりますねってことを紹介するために、ToDoアプリケーションを作りました。

とは言え、作ったというと大げさで、下記の記事にあるチュートリアルそのままで、CoffeeScript + Data APIで少し書き直しただけです。

作ったサンプルは下記に置いておきました。

Data APIはURLの設計キレイだし、JSON/REST APIなので、Backbone.jsとの親和性がとても高いと思いました。バックエンドはMTをセットアップしただけで利用できるので、Backbone.jsでフロントエンドのアプリを書くだけで済みます。アプリのプロトタイプ作るのに活用するのもいいなと思いました。

後、フロントエンドの人達も活用しやすいと思います。フロントエンド技術だけでも、Data APIを使って、Webアプリを作れるとおもいますし、DBのデータの管理アプリケーションにMTが使えます。

MT6について

やはりData APIに注目が集まっているようです。

自分もData APIは、MTの静的ファイル出力やMTML、Perlなど、MTのアイデンティティになる要素を一切排除できる仕組みなので、かなり大胆な機能追加だなと思いました。

今後Data APIがどのような使い方をされていくのか楽しみではあります。

あと、長谷川恭久さんを中心にMTがどのようにリデザインされていくのかも注目です。

新しいMTはSimple, Smart, Speedyがキーワードとのことですが、今のMTは残念ながらどのキーワードもしっくりとマッチしていないように思うので、これからそのキーワード通りに変化していくことを期待しています。

MTがStyleDoccoに対応したっぽい

developブランチのcommitログ見てたら下記のコミットを見かけた。

StyleDocco 形式のStyleGuideを整備したようで、StyleDoccoを使ってスタイルガイドが出力できるようになってた。これによって既存のスタイル探すのにソース見て調べなくても良くなりそう。developブランチの話なので確定ではありませんが。

出力結果

f:id:jdg:20130729235752p:plain

あと、話は変わりますが、MTDDCにてLTをすることになったので、今週は頑張ってスライド作ります。

Perlのデータ構造を意識しつつ、常にmt:loopタグを使う話

案件で試したわけではなく、かと言って試せる機会もないので、あくまで一つの提案です。

mt:loopタグの良い文書がGithubのmovabletype/Documentation Wikiにあります。

特筆すべき箇所が、以下のように書かれた説明です。

Movable Typeの再構築の処理を行う場合、データベースへのアクセスが多くなります。

例えばブログ記事のタイトル一覧をページ内で複数個所で利用するケースの場合、 タグを何箇所にも書くと、その個数分データベースへアクセスしに行く事になり再構築時にオーバーヘッドが多くなります。

そこで、一旦データを配列やハッシュに格納し、ループを各所でまわす事でデータベースへのアクセスを減らす事が可能となります。

データを配列やハッシュに格納すれば、あらゆるコンテナタグと呼ばれる類のタグは、mt:loopの共通のインターフェイスによって利用でき、非常にわかりやすくなると感じました。

mt:loopのインターフェイスを利用することで、もれなく、__first__, __last__, __odd__, __even__, __index__, __counter__などの特殊変数が利用できるのも大きいです。

極端に言えば、mt:entriesやmt:pages等は、「mt:loopに渡すためのデータの構築のみに使うようにする」みたいな話です。

mt:entriesを例に考えます。

mt:entriesは、通常、以下のように使うでしょう。

<mt:entries>
title: <mt:entrytitle>
text: <mt:entrytext>
more: <mt:entrymore>
</mt:entries>

これをPerlのデータ構造で表すと以下のような形で考えるのが自然ですね。

my $entries = [
  {
    title => 'title1',
    text => 'text1',
    more => 'more1',
  },
  {
    title => 'title2',
    text => 'text2',
    more => 'more2',
  },
  {
    title => 'title3',
    text => 'text3',
    more => 'more3',
  },
];

これをそのままmt:loopで使えば、かなり楽にmt:loopを活用できそうです。mt:varでアクセスできる値は、実際にはPerlのデータ構造ですからね。

結果、以下のようにテンプレートを書くことで、先ほどのデータ構造を自然に作れることを確認しました。無名ハッシュリファレンスをどうやって表現するかが悩ましいところでしたが、割とスムーズにできました。

<mt:entries>
  <mt:sethashvar name="entry">
    <mt:entrytitle setvar="title">
    <mt:entrybody setvar="text">
    <mt:entrymore setvar="more">
  </mt:sethashvar>
  <mt:var name="push(entries)" value="$entry">
</mt:entries>

以下のようにmt:loopで利用します。

<mt:loop var="entries">
  <mt:if name="__first__">
    <div class="entries">
  </mt:if>
      <article class="entry<mt:if name='__first__'> first</mt:if><mt:if name='__even__'> even</mt:if><mt:if name='__last__'> last</mt:if>">
        <h1><mt:var name="title"></h1>
        <div class="entry-text">
          <mt:var name="text">
        </div>
  <mt:if name="more">
        <div class="entry-more">
          <mt:var name="more">
        </div>
  </mt:if>
      </article>
  <mt:if name="__last__">
    </div>
  </mt:if>
</mt:loop>

これであらゆるコンテナタグを、常にmt:loopで回せそうな気がして参りました。試してませんが、元のデータ構造がもう少し複雑でも、mt:loopのネストでうまくデータにアクセスできるように思います。

個人的には、上記のmt:ifの分岐すらお腹いっぱい感があるので、もう少しテンプレートを簡潔に書けるインターフェイスを作りたいなーと思ってはいます。

まとめ

共通のインターフェイスを使えて、DBアクセスも減らせるので、メリットが大きい気がしています。やたらめったらグローバルに変数を定義しているわけでもないので、お行儀も良いです。また、基本的に<mt:if name="foo">...</mt:if><mt:var name="foo">しか使わないので、シンプルにもなります。落とし穴あるかもしれませんが...。

おまけ

データ構造を逐一調べるために、下記のような単純なMTタグを定義して、あちこちにmt:dumpを散りばめながら動作を確認しました。

tags:
  function:
    Dump: >
      sub {
        use Data::Dumper;
        Dumper shift->stash('vars');
      }

config.yamlのみでプラグインのプロトタイプをサクッと作れるのは良いですね。

追記

コメント欄で同僚さんにコメントいただいたのですが、コレが自然に動くようになったのは、MT 5.2から だそうです。どおりで、今までサンプル見なかったわけだ。あと、「落とし穴あるかもしれませんが」の回答として、ダイナミックパブリッシングで挙動が異なるケースがあるそうです。ありそうですね...。