Kokudoriing

技術系与太話ブログ

Grunt0.4.xでのGruntプラグインの作り方

結構前にGrunt.jsがバージョン0.4.xになりました。
・・・、マイナーバージョンアップというか実質メジャーバージョンアップというか。
かなり変わったのでプラグイン作成とGrunt自体の使い方について備忘録までに。

かなり大きな変更として、Grunt.js本体とCUIインタフェースが分離されました。
CUIインタフェースをグローバルインストールし、Grunt.js本体はプロジェクト毎にローカルインストールする方針らしいです。
ということで、まずはCUIインターフェイスをグローバルインストールします。

npm install -g grunt-cli

次にプロジェクトディレクトリに入ってからGruntをローカルインストールします。

npm install grunt

そしてGruntファイル書くだけですね。
ただ、デフォルト名が前までのgrunt.jsではなくGruntfile.jsに変わりました。
Windows環境だとgruntよりgrunt.jsが優先されるため、grunt.cmd打たないといけないからでしょうね。
http://qiita.com/items/a96b3812e93c0a3c7ae0

そして一番の変更点として、今まで標準で入ってたconcatやらwatchやらが全部外部モジュール化されました。
(例えばgrunt-contrib-concatとかgrunt-contrib-watcjとか)
なので、Gruntファイル内でconcatが使いたい場合は

grunt.loadNpmTasks('grunt-contrib-concat')  

する必要があります。
もちろんgrunt-contrib-concatはnpmでローカルインストールしておく必要があります。
grunt-contrib-xxx系のインストールをするのはめんどいので、package.jsonに書いておくと楽です。
package.jsonのdevDependenciesに依存npmモジュール名を書いておくと、プロジェクトディレクトリで

npm install

すると、勝手に依存モジュールをインストールしてくれます。便利!

それ以外のGruntファイルの書き方はバージョン0.3の時と大体一緒です。 Grunt自体のわかりやすい解説はこちらにまとまってます。

さて本題。
Gruntプラグインの作り方については日本語情報さっぱりなんですよねー。
英語なら、公式ドキュメントが0.4から頑張りだしてるので、英語読める方はそちらで。

手前味噌ですが、Gruntプラグイン「grunt-encase」を作ってみたりしたので、
以下の説明はコードを追いながら見ていただくとわかりやすいかもしれません。

さて、本来grunt-initとかでgruntpluginようテンプレートを自動生成できるらしいんですが、Windows環境だとうまくいかない。
基本的にはGrunt0.3の時と同じで、Gruntファイルに

grunt.loadTasks('tasks')  

を書いて、tasksディレクトリにタスクを書くだけ。
(いや、tasksじゃなくても好きな名前でいいんだけども。)

タスクファイル内では以下の様に書きます。

module.exports = function (grunt) { };  

あとはGruntが呼んでくれて、gruntオブジェクトを渡してくれるわけです。

grunt.registerMultiTask('task名', 'taskの説明', function() {
    // taskの処理
})

でタスクを登録すれば出来上がり。
な、ワケですが。
前述の通り、concatとかがgrunt-contrib-concatモジュールに分離されました。
0.3だとgrunt組み込みだったので、grunt.helperからconcatをプラグイン内で使えたんですよね・・。
0.4からgrunt.helperが廃止されたようで、concatとか自前で作るしかないのかなー・・。

ちなみに、作ったGruntプラグインを公開するのなら、package.jsonのkeywordsに"gruntplugin"を加えて公開するだけです。

npm publish

ただ、公開するのであれば、最低限テストくらいは書いておいたほうがいいかもですね。

grunt.loadNpmTasks('grunt-contrib-nodeunit')  

でnodeunitが使えます。

grunt nodeunit

で動くので、package.jsonで以下のように書いておくと便利です。

"scripts": { "test": "grunt nodeunit" }  

これで、

npm test

でテストが実行されるようになります。
あとはjshintとかも付けておくと良いかもですね。

grunt.loadNpmTasks('grunt-contrib-jshint')

あとはGruntファイルにjshint用のタスクを書けばOK。

grunt jshint

で動きます。
JSHintや、オプションについては本家サイトをご参照ください。

watchとかjshintとかconcat、minifyを簡単に自分のコードに適用できるので、Gruntは便利ですね。

C#でPEGパーサー

PEGって何?って方はWikipediaを御覧ください。
(正直ボクもよくわかってない・・)
ざっくりと、BNFよりも曖昧性のないものを扱うのが得意みたいで、Shift/Reduceコンフリクトが発生しないとのこと。
Perl6がPerl6 rulesとかで言語レベルに組み込んでるアレです。

PEG自体の解説はkmizushimaさんのPEG基礎文法最速マスターを見ていただくとして、
以下はC#で使えるPEGパーサー、peg-sharpについてのお話し。

昨日辺りから触りだして、ようやく簡単なJSONパーサー作ってみました。
https://gist.github.com/kokudori/5252810

Xxx.pegにPEGを書いて、peg-sharpでパーサーをジェネレートして使う感じです。
ただ、PEGファイル内でバッククォート(`)で囲まれた部分がC#として解析されます。
この時、非終端記号、終端記号毎に解析結果がResult型として保持され、非終端記号毎にList型のresults変数が割り当てられます。

Result型は以下の様な構造体です。
struct Result
{
/ 解析されたテキスト。
public string Text { get; }

// 1から始まる行番号。
public int Line { get; }

// 1から始まる列番号。
public int Col { get; }

// セマンティックアクションの結果
public Expression Value { get; }
}

セマンティックアクションっていうのはバッククォートで囲んだC#の振る舞いのことです。
また、セマンティックアクション内で使える変数が存在します。
expected 非終端記号の文字列表現。パーサーエラーの時のエラーメッセージで役立つ。
fatal パーサーエラーを起こす。また、その時のエラーメッセージを設定する。
results 上記で説明した非終端記号、終端記号毎の解析結果のリスト。
text よくわかんない。使ったことない。
value Result型のValueプロパティに対応。基本的にはここに解析結果を渡していく。

また、独自のルールが存在し、ルールはPEGルールの前に書かないとダメです。
ルールは色々あるので詳しくはドキュメントを見ていただくとして、重要なのはstartとvalue。
startは非終端記号のエントリポイントを指定します。
valueは解析結果を意味する値の型を指定します。

パースの流れとしては、非終端記号毎に意味のある値をvalueに渡して、
1レイヤー上の非終端記号でresult.Valueがnullじゃないものを補足して云々みたいな感じですかね。
因みにパーサーの部分クラスを定義できるのでそこでvalueに渡す時に使うヘルパメソッドとか書いとくといいんじゃないですかね。
ただあっというまにメンテきつくなりそうですけども。
startで指定した非終端記号のvalueがパーサーのParseメソッドの戻り値になります。

使ってみた感想として、まずvalueの型が1つのみっていうのは辛いです。
ドキュメントではUnion的な型作ればOKとか書いてますけど、やっぱりきついです。
あと公式のサンプルだとExpression抽象クラスがEval持って、それを継承していくインタプリタパターン使ってました。
なのでJSONパーサー作ったときは全部dynamicに突っ込んでます。
これ、本当は木構造にして各ノードがChildren持つとかの方がいいんでしょうが、それをこれで作るの面倒だなー。

あ、そういえばdynamicって、内部が構造体の時にnullチェックすると例外出るんですね。(当たり前か)
なので if(dynamicobj is ValueType || dynamicobj != null) some() とかしてたんですが、スマートな方法ないんですかね。
以上、備忘録。