MTオブジェクトの複製を作るツールスクリプトを作った
Movable Type使ってる時に、たまに、ページングのテストや負荷テスト等をしたい時に、記事を大量にコピーしたい時があって、これまで下記のようなスクリプトを叩いたりしてコピーを作ってました。
これをMTオブジェクト全般に広げても良いかなと思ったので、ObjectCloneというプラグインを作りました。
ObjectClone プラグイン
usage のままですが、下記のように使います。
USAGE: perl tools/object-clone --model=NAME --orig_id=NUM [OPTION] Requires: -m, --model=NAME Model name (object datasource) of object to make clone. -o, --orig_id=NUM Original object id clone object. Options: -a, --amount=NUM Amount of clones to make. Default 1. -d, --debug Output debug info to STDERR. -h, --help Show help. -r, --redefine=PAIR Redefine column value with column key and column value pair. -u, --usage Show usage. Examples: # Make clones of 100 objects of MT::Entry from original entry that entry_id is 1. $ tools/object-clone --model=entry --orig_id=1 --amount=100 # Make a clone of MT::Blog, and redefine name, site_path and site_url. $ tools/object-clone -m blog -o 2 -r 'name=Clone Blog' -r 'site_path=/path/to/site-path' -r 'site_url=/::/clone-blog/'
基本は、モデル名とコピー元のオブジェクトのIDを、それぞれ、--model
オプションと--orig_id
オプションで指定してクローンを作ります。
クローンを複数作る場合は、作る数を--amount
オプションで指定します。
他には、単に丸々コピーするだけではなくて、一部、値を変更する場合は、--redefine
オプションで、フィールド名とその値を=
で区切って指定します。
具体的には、Aというブログの記事を、Bというブログにコピーしたいような時などに、下記のように指定します。
$ perl tools/object-clone --model entry --orig_id 1 --amount 10 --redefine blog_id=3
コマンドラインオプションの単語が適切かどうかは微妙な感じもしますが、とりあえず必要最低限なオプションだけ用意しました。
値の変更が必要なケース
DB上の制約はないにも関わらず、実際にはMT::Entryのベースネームだとか、CustomFields::Fieldのタグ名は、値が重複しないようにCMS上で制限がかかっていたり、バックグラウンドでの値の生成の実装があったりします。
その辺の対応はオブジェクト毎に必要な感じなので、ObjectClone::Patcherとそのサブクラスに切り出しています。
単体で使うことはほぼないと思いますが、下記のような感じで使います。
use strict; use warnings; use lib qw(lib extlib plugins/ObjectClone/lib); use MT; use ObjectClone::Patcher; use ObjectClone::Patcher::MT::Entry; my $mt = MT->instance; my $orig_obj = MT::Entry->load(1) or die MT::Entry->errstr; my $new_obj = $orig_obj->clone; my $patcher = ObjectClone::Patcher->model('entry')->new($new_obj, $orig_obj); $patcher->remove_patch(basename => \&ObjectClone::Patcher::MT::Entry::basename) ->add_patch(basename => sub { 'new-basename' }) ->add_patch(convert_breaks => sub { 'markdown' }) ->apply_patch; $new_obj->save or die $new_obj->errstr;
デフォルトで下記のPatcherクラスを用意しています。
- ObjectClone::Patcher::MT::Entry
- ObjectClone::Patcher::MT::Category
- ObjectClone::Patcher::MT::Author
- ObjectClone::Patcher::CustomFields::Field
とは言っても実際に実装があるのは、ベースネームをユニークにする処理くらいです。
独自オブジェクトをtools/clone-objectで生成したいときなどは、上記のPatcherクラスを追加すれば対応ができます。( ObjectClone::Patcher::{オブジェクト名} )
後は、apply_patchメソッドで、パッチを適用する前に、before_apply_patchというコールバックが呼ばれるので、デフォルトのパッチ処理を無効にしたり、それに加えて何かの処理を実行したりする時は、コールバックプラグインで拡張できます。
コールバックプラグインのサンプル
下記のようにコールバックを使ったプラグインが書けます。
id: ObjectCloneCallback key: object-clone-callback name: ObjectCloneCallback callbacks: ObjectClone::Patcher::MT::Entry::before_apply_patch: | sub { my ($cb, $patcher) = @_; $patcher->add_patch('basename', sub { my ($new_obj, $orig_obj) = @_; 'new-basename'; }); }
まとめ
主に開発ツールとして作ったので、開発時には役に立つ気がしています。
シェルスクリプトと組み合わせたりするのも良いかもしれません。
ほなの。
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をワンライナーしやすくなりました。
$ 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日目です。
Ruby の method_missing
だったり、Perl の AUTOLOAD
的なものが、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 ) ); } }
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;
インターフェイス的にかなり微妙な感じですが、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 コールバックは便利に使えそうです。
まとめ
あると便利な気はするんですけど、アイデアありきで作ってみたものの、実装は正直微妙な感じになってしまいました。
今年はコードを書くより、読む時間が圧倒的に多い年でした。来年は、書く時間を増やせたらいいなと思います。
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ブランチの話なので確定ではありませんが。