Markdown の HTML 変換 - Ruby, PHP, Groovy, Scala, Node.js

Markdown 形式の文字列を HTML 変換する処理を複数のプログラム言語で試してみました。

処理としては、標準入力から UTF-8 の Markdown 形式の文字列を取得し HTML 変換した結果を標準出力へ UTF-8 で出力しています。

ちなみに、Markdown 文字列は LOGGiX プロジェクトの日本語版サンプル markdown-sample.text を使用しました。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120809/

Ruby の場合

Pure Ruby な kramdown モジュールを JRuby で実行してみました。

tohtml.rb
require 'kramdown'

Encoding.default_external = 'utf-8'

puts Kramdown::Document.new($stdin.read).to_html
実行例
 jruby tohtml.rb < markdown-sample.text > ruby_result.txt
結果

特に問題なく変換されました。

更に kramdown は以下のような表組みにも対応していました。

プログラム言語 | モジュール名
---------------|-------------
PHP            |   Markdown
Ruby           |   kramdown
Groovy         |   MarkdownJ
Scala          |   knockoff
Node.js        |   markdown

HTML 変換結果は以下の通りです。

<table>
  <thead>
    <tr>
      <th>プログラム言語</th>
      <th>モジュール名</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>PHP</td>
      <td>Markdown</td>
    </tr>
    ・・・
  </tbody>
</table>

PHP の場合

Markdown Extra をダウンロードし、markdown.php をカレントディレクトリに配置して、実行しました。

tohtml.php
<?php
include_once "markdown.php";

$mkStr = stream_get_contents(STDIN);

echo Markdown($mkStr);
?>
実行例
 php tohtml.php < markdown-sample.text > php_result.txt
結果

kramdown と同様、特に問題なく変換され表組みにも対応していました。
Extra の付いていない Markdown 1.0.1o の方は表組みに対応していないのでご注意ください。

Groovy の場合

Java 用のモジュール MarkdownJ を Groovy で使いました。

tohtml.groovy
@Grab('com.madgag:markdownj-core:0.4.1')
import com.petebevin.markdown.*

def mk = new MarkdownProcessor()
def enc = 'UTF-8'

System.out.withWriter enc, {w ->
    w.print mk.markdown(System.in.getText(enc))
}
実行例
 groovy tohtml.groovy < markdown-sample.text > groovy_result.txt
結果

以下のような問題が発生しました。

  • 画像の「リファレンス」スタイル箇所が a タグになった *1
・・・
<p>
「リファレンス」スタイル: !
<a title="Loggix" href="../../../theme/images/loggix-logo.png">Loggix</a>
</p>

Scala の場合

Scala は 2種類のモジュールを試してみました。Scala 2.9.2 用のモジュールが見当たらなかったため、2.9.1 用のモジュールを 2.9.2 でスクリプト実行しました。

tohtml.scala (Knockoff 版)
import scala.io._
import com.tristanhunt.knockoff.DefaultDiscounter._

val mkStr = new BufferedSource(System.in)(Codec.UTF8).mkString

val ps = new java.io.PrintStream(System.out, false, "UTF-8")

ps.println(toXHTML(knockoff(mkStr)))
実行例1 (Knockoff 版)
 scala -nc -cp knockoff_2.9.1-0.8.0-16.jar tohtml.scala < markdown-sample.text > scala_result.txt

ここで -nc オプションを指定している点にご注意ください。-nc オプションを付けないとコンパイラのプロセスがリダイレクト先のファイルを開いたままになり不都合が生じます。

結果

以下のような問題が発生しました。

  • '=' と '-' の見出しが正しく変換されなかった *2
  • 画像の「リファレンス」スタイル箇所が a タグになった
<p>Markdown Sample
=======================
</p><p>サンプルドキュメント日本語版
-----------------------
</p>
tohtml2.scala (Actuarius 版)
import scala.io._
import eu.henkelmann.actuarius.ActuariusApp

val mkStr = new BufferedSource(System.in)(Codec.UTF8).mkString

val ps = new java.io.PrintStream(System.out, false, "UTF-8")

ps.println(ActuariusApp(mkStr))
実行例2 (Actuarius 版)
 scala -nc -cp actuarius_2.9.1-0.2.3.jar tohtml2.scala < markdown-sample.text > scala_result2.txt
結果2 (Actuarius 版)

Knockoff とは別の問題が発生しました。

  • '+' と '-' のリストが正しく変換されなかった
<p>(プラス記号で記述)</p>
<p>+   モツァレラチーズ
+   パスタ
+   ワイン</p>
<p>(ハイフン(マイナス記号)で記述)</p>
<p>-   モツァレラチーズ
-   パスタ
-   ワイン</p>

なお、Scala IO を使うと標準入出力まわりの処理が以下のようになります。

ToHtml.scala
package fits.sample

import scalax.io.JavaConverters._
import com.tristanhunt.knockoff.DefaultDiscounter._

object ToHtml extends App {
    val mkStr = System.in.asUnmanagedInput.slurpString

    val html = toXHTML(knockoff(mkStr)).mkString

    System.out.asUnmanagedOutput.write(html)
}

Node.js (CoffeeScript) の場合

Markdown-js を npm install markdown でインストールし、CoffeeScript で実行しました。

tohtml.coffee
mk = require 'markdown'

process.stdin.resume()

process.stdin.on 'data', (data) ->
    console.log mk.markdown.toHTML data.toString()
実行例
 coffee tohtml.coffee < markdown-sample.text > coffee_result.txt
結果

以下のような問題が発生しました。

  • '=' と '-' の見出しが正しく変換されなかった
  • '<' '>' '&' などの不要なエスケープが実施されてしまった
<pre><code>&lt;blockquote&gt;</code></pre>

<p>
        &lt;p&gt;For example.&lt;/p&gt;
    &lt;/blockquote&gt;
</p>

<h3>HTMLとの共存</h3>
・・・
<p>例:April 1&lt;sup&gt;st&lt;/sup&gt;

まとめ

今回の結果をまとめると以下のようになります。

言語 モジュール 見出し = - リファレンススタイル(画像) リスト + - エスケープ 表組み
Ruby kramdown 0.13.7
PHP Markdown Extra 1.2.5
Groovy MarkdownJ Core 0.4.1 × ×
Scala Knockoff 0.8.0 × × ×
Scala Actuarius 0.2.3 × ×
Node.js (CoffeeScript) Markdown-js 0.4.0 × × ×

*1:img タグになるのが正しい

*2:それぞれ h1 と h2 タグに変換されるのが正しい

非同期処理でWebコンテンツをダウンロードする方法5 - IcedCoffeeScript

IcedCoffeeScript は TameJS の await/defer をサポートした CoffeeScript の拡張版です。

前回(id:fits:20120415)、TameJS + CoffeeScript で実装した Web コンテンツのダウンロード処理を IcedCoffeeScript で実装すると以下のようになります。(Embedded JavaScript を使う必要がなくなり、直接 await/defer を使えます)

download_web.coffee (IcedCoffeeScript 版)
http = require 'http'
url = require 'url'
fs = require 'fs'
path = require 'path'

dir = process.argv[2]

printError = (urlString, error) ->
    console.log "failed: #{urlString}, #{error.message}"

process.stdin.resume()

await process.stdin.on 'data', defer(urls)

urls.toString().trim().split('\n').forEach (u) ->
    trgUrl = url.parse u

    await http.get(trgUrl, defer(res)).on 'error', (err) -> printError u, err

    res.setEncoding 'binary'
    buf = ''

    await
        res.on 'data', (chunk) -> buf += chunk
        res.on 'end', defer()
        res.on 'close', (err) -> printError trgUrl.href, err if err

    filePath = path.join dir, path.basename(trgUrl.pathname)

    await fs.writeFile filePath, buf, 'binary', defer(err)

    if err
        printError trgUrl.href, err
    else
        console.log "downloaded: #{trgUrl.href} => #{filePath}"

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120716/


インストール方法は以下の通りです。

IcedCoffeeScript インストール
 npm install -g iced-coffee-script

実行は iced コマンドで直接実行できます。(TameJS + CoffeeScript 版のような変換処理が必要ありません)

実行例
 iced download_web.coffee destdir < urls.txt

Node.js の非同期処理を関数合成で繋げてみる - 連続コールバック対策

Node.js でプログラミングする際の課題は、非同期処理のコールバックが多段になって分かり難くなってしまう点だと思います。(下記は CoffeeScript の例)

func1 引数・・・, (err, 処理結果1) ->
    if err?
        エラー処理
    else
        ・・・
        func2 引数・・・, (err, 処理結果2) ->
            if err?
                エラー処理
            else
                ・・・
                func3 引数・・・, (err, 処理結果3) ->
                    if err?
                        エラー処理
                    else
                        ・・・

対策はいろいろあると思いますが(id:fits:20120415 で使った TameJS もその一つ)、今回は id:fits:20101213 で扱ったような関数合成を適用する方法を模索してみました。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120617/

コールバックを繋げて関数合成

なんとなく、コールバック結果を繋げて関数合成すれば使い道ありそうな気がしました。

つまり、"関数1" のコールバック結果を引数にして "関数2" を呼び出し、そのコールバック結果を引数にして "関数3" を呼び出し・・・というような処理を関数合成で作り出せればと。

そこで、試しに CoffeeScript を使って関数合成する処理を実装してみました。

compose.coffee
# 関数をコールバックで繋げて処理する無名関数を返す
exports.compose = (funcs...) ->
    (args, callback) ->
        cb = genCallback callback

        if funcs?.length > 0
            for i in [(funcs.length - 1)..0]
                cb = genCallback cb, funcs[i]

        cb null, args

genCallback = (callback, func) ->
    (err, res) ->
        if err?
            callback err
        else if func?
            func res, callback
        else
            callback null, res

compose 関数は、渡された関数を入れ子で繋げて処理する無名関数(args と callback が引数)を返します。
ただし、compose に渡す関数の引数も (args, callback) で統一しておく必要があります。


処理としては、compose f1, f2 で以下のような無名関数が作られるイメージです。(cb の部分は関数実行時に毎回構築される点に注意)

compose f1, f2 で作成される無名関数のイメージ
(args, callback) ->
    cb = (err, res0) ->
        if err?
            callback err
        else
            f1 res0, (err, res1) ->
                if err?
                    callback err
                else
                    f2 res1, (err, res2) ->
                        if err?
                            callback err
                        else
                            callback null, res2

    cb null, args

動作確認

動作確認のため、上記で作成した compose 関数を使ったサンプルを作成・実行してみます。

sample.coffee
c = require './compose'

# 関数1
f1 = (args, cb) ->
    console.log "f1 : #{args}"
    cb null, [args, "aaa"]

# 関数2
f2 = (args, cb) ->
    console.log "f2 : #{args}"
    cb null, 10

# 関数3
f3 = (args, cb) ->
    console.log "f3 : #{args}"
    if args is 10
        cb null, "done"
    else
        cb 'error3'

# 関数4
f4 = (args, cb) ->
    console.log "f4 : #{args}"
    cb 'error4'

# 関数1, 2, 3 を合成
cf1 = c.compose f1, f2, f3
# 関数1, 2, 3 の合成関数を実行
cf1 'test', (err, res) ->
    console.log "result1 : #{err}, #{res}"

console.log '-----'

cf2 = c.compose f1, f4, f2, f3
cf2 'test', (err, res) ->
    console.log "result2 : #{err}, #{res}"

console.log '-----'

cf3 = c.compose f2, f1, f3
cf3 'test', (err, res) ->
    console.log "result3 : #{err}, #{res}"

console.log '-----'

cf4 = c.compose f4, f2
cf4 'test', (err, res) ->
    console.log "result4 : #{err}, #{res}"

console.log '-----'

cf5 = c.compose f2, f1
cf5 'test', (err, res) ->
    console.log "result5 : #{err}, #{res}"

console.log '-----'

cf6 = c.compose()
cf6 'test', (err, res) ->
    console.log "result6 : #{err}, #{res}"

console.log '-----'

cf7 = c.compose f1
cf7 'test', (err, res) ->
    console.log "result7 : #{err}, #{res}"

console.log '-----'

# 合成関数 cf1, cf5, cf7 を合成
cf8 = c.compose cf1, cf5, cf7
cf8 'test8', (err, res) ->
    console.log "result8 : #{err}, #{res}"

実行結果は以下の通り。一応、動作しているようです。

実行結果
> coffee sample

f1 : test
f2 : test,aaa
f3 : 10
result1 : null, done
-----
f1 : test
f4 : test,aaa
result2 : error4, undefined
-----
f2 : test
f1 : 10
f3 : 10,aaa
result3 : error3, undefined
-----
f4 : test
result4 : error4, undefined
-----
f2 : test
f1 : 10
result5 : null, 10,aaa
-----
result6 : null, test
-----
f1 : test
result7 : null, test,aaa
-----
f1 : test8
f2 : test8,aaa
f3 : 10
f2 : done
f1 : 10
f1 : 10,aaa
result8 : null, 10,aaa,aaa

Express 用のテンプレートエンジンを自作する方法

Node.js 用 Web フレームワーク Express のテンプレートエンジンを自作する方法をご紹介します。

といっても自作テンプレートエンジン側で以下のような処理を用意するだけなので、非常に簡単です。

exports.__express = function(path, options. fn) {
    /*
     * 処理に成功すれば        fn(null, テンプレート処理結果)
     * 何かエラーが発生すれば  fn(err)
     * をそれぞれ実行すればよい
     */
     ・・・
};

path にはテンプレートファイルのパス、options にはテンプレート処理用のパラメータが渡ってきます。fn は Node.js ではおなじみのエラーと処理結果を引数にとるコールバックです。


それでは、CoffeeScript を使って Express 用の自作テンプレートエンジン "sample" を作成してみます。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120616/

Express プロジェクトの準備

まず、Express の実行環境を用意します。
今回は、以下のようなパッケージファイルをカレントディレクトリに用意して npm install を実行しました。

package.json
{
  "name": "express_template_sample",
  "version": "1.0.0",
  "dependencies": {
    "express": "3.0",
    "coffee-script": "*",
    "underscore": "*"
  }
}
npm install 実行
> npm install

npm http GET https://registry.npmjs.org/underscore
npm http GET https://registry.npmjs.org/express
npm http GET https://registry.npmjs.org/coffee-script
・・・

なお、環境変数 PATH に node_modules/.bin を追加しておけば、カレントディレクトリで coffee コマンドを使用できます。

ちなみに、Express を使うだけなら CoffeeScript や Underscore.js は不要です。

自作テンプレートエンジン sample の作成

node_modules ディレクトリに自作テンプレートエンジン名のディレクトリ (今回は sample) を作成し、index.coffee (Node.js なら index.js) に exports.__express の処理を実装します。*1

node_modules/sample/index.coffee
fs = require 'fs'
_ = require 'underscore'

exports.__express = (path, options, fn) ->
    try
        res = _.template fs.readFileSync(path).toString(), options
        fn null, res
    catch err
        fn err

テンプレートファイル(path)をテンプレート処理し処理結果をコールバックに渡します。

今回は Underscore.js の template 関数を使ってテンプレート処理を行いました。

テンプレートとメインプログラム作成

まず、テンプレートエンジン名を拡張子に使ったテンプレートファイルを views ディレクトリに用意します。
Underscore.js の template 関数では値を埋め込む箇所に <%= ・・・ %> が使えます。(EJS と同様)

views/index.sample
<html>
<body>
<p><%= title %></p>
</body>
</html>

次に、Express のメインプログラムを作成します。

app.coffee
express = require 'express'

app = express()

app.configure ->
    # デフォルトの view engine を sample に設定
    app.set 'view engine', 'sample'

app.get '/', (req, res) ->
    # index.sample をテンプレート処理
    res.render 'index', {title: 'test'}

app.listen 3000, -> console.log "server started"

/ へのアクセスで index.sample をテンプレート処理するように実装しています。

なお、app.set 'view engine', ・・・ でデフォルトの view engine を設定しておくと、res.render の際に拡張子の指定を省略する事ができます。

動作確認

coffee コマンドで app.coffee を実行した後、http://localhost:3000/ にアクセスすると "<%= title %>" の箇所が "test" に置換されたコンテンツが返ってきます。

実行
> coffee app
server started
処理結果
$ curl http://localhost:3000/

<html>
<body>
<p>test</p>
</body>
</html>

*1:Linux などであれば node_moudules に sample のシンボリックリンクを作成しても可です

TameJS による継続と CoffeeScript 上での利用 - 非同期処理でWebコンテンツをダウンロードする方法4

TameJS は JavaScript で継続(Continuation)を実現するためのツールです。

TameJS によって Scala の限定継続(id:fits:20111016 参照)のような事ができるので、コールバックを多用する Node.js の処理を比較的シンプルに実装できるようになります。

そこで今回は、id:fits:20111030 で実装した Web コンテンツダウンロード処理(Node.js 版)を TameJS と TameJS + CoffeeScript で実装してみました。

  • Node.js v0.6.14
  • CoffeeScript 1.3.1
  • TameJS

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120415/

事前準備

まずは npm を使って tamejs モジュールをインストールしておきます。(Node.js と CoffeeScript はインストールされているものとします)

> npm install tamejs -g

なお、-g オプションを付けなかった場合、カレントディレクトリの node_modules ディレクトリ内にインストールされますので、この場合は ./node_modules/.bin を環境変数 PATH に追加して使用します。

TameJS の場合

TameJS では、await ブロック内に非同期処理を実装しコールバックに defer を指定する事で、コールバックが実行されると await ブロックの終わりから処理が継続され、defer に指定した変数*1が使えるようになります。

Scala 限定継続の shift が TameJS では await に、継続の呼び出しが defer に該当するようなイメージです。

download_web_tamejs.tjs
var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');

var dir = process.argv[2];

var printError = function(urlString, error) {
    console.log('failed: ' + urlString + ', ' + error.message);
}

process.stdin.resume();

await {
    //コールバック(defer)が実行されると (1) へ
    process.stdin.on('data', defer(urls));
}
//(1)

urls.toString().trim().split('\n').forEach(function(u) {
    var trgUrl = url.parse(u);

    await {
        //URL接続成功後にコールバック(defer)が実行されると (2) へ
        http.get(trgUrl, defer(var res)).on('error', function(err) {
            printError(u, err);
        });
    }
    //(2)

    res.setEncoding('binary');
    var buf = '';

    await {
        //データ取得
        res.on('data', function(chunk) {
            buf += chunk;
        });

        //データ取得が完了しコールバック(defer)が実行されると (3) へ
        res.on('end', defer());

        res.on('close', function(err) {
            if (err) {
                printError(u, err);
            }
        });
    }
    //(3)

    var filePath = path.join(dir, path.basename(trgUrl.pathname));

    await {
        //ファイルへの出力が完了しコールバック(defer)が実行されると (4) へ
        fs.writeFile(filePath, buf, 'binary', defer(var err));
    }
    //(4)

    if (err) {
        printError(u, err);
    }
    else {
        console.log('downloaded: ' + u + ' => ' + filePath);
    }
});

上記の .tjs ファイルを tamejs コマンドで .js に変換した後、node コマンドで実行します。

実行
> tamejs download_web_tamejs.tjs
> node download_web_tamejs.js destdir < urls.txt

TameJS + CoffeeScript の場合

CoffeeScript で TameJS を使うには Embedded JavaScript を使います。
具体的には、await { と } の箇所を ` で囲み、以下のように await ブロック内の処理は字下げしないようにします。

正しい記述
`await {`
process.stdin.on 'data', defer(urls)
`}`
誤った記述 (以下のように字下げすると coffee コマンド実行時にエラーが発生します)
`await {`
    process.stdin.on 'data', defer(urls)
`}`

CoffeeScript 上で TameJS を使って実装したダウンロード処理は以下のようになります。

download_web_tamejs2.coffee
http = require 'http'
url = require 'url'
fs = require 'fs'
path = require 'path'

dir = process.argv[2]

printError = (urlString, error) ->
    console.log "failed: #{urlString}, #{error.message}"

process.stdin.resume()

`await {`
process.stdin.on 'data', defer(urls)
`}`

urls.toString().trim().split('\n').forEach (u) ->
    trgUrl = url.parse u

    `await {`
    # URL 接続
    http.get(trgUrl, defer(res)).on 'error', (err) -> printError u, err
    `}`

    res.setEncoding 'binary'
    buf = ''

    `await {`
    # データ取得
    res.on 'data', (chunk) -> buf += chunk
    # データ取得完了
    res.on 'end', defer()
    # 接続後のエラー処理
    res.on 'close', (err) -> printError trgUrl.href, err if err
    `}`

    filePath = path.join dir, path.basename(trgUrl.pathname)

    `await {`
    # ファイル出力
    fs.writeFile filePath, buf, 'binary', defer(err)
    `}`

    if err
        printError trgUrl.href, err
    else
        console.log "downloaded: #{trgUrl.href} => #{filePath}"

実行するには、まず coffee コマンドで .js に変換し(実際には .tjs の内容)、tamejs コマンドで .js に変換して node コマンドで実行します。

なお、coffee コマンドで -b オプションを指定しておかないと tamejs コマンド実行時にエラーが発生する点にご注意下さい。

実行
> coffee -b -c download_web_tamejs2.coffee
> tamejs -o download_web_tamejs2.js download_web_tamejs2.js
> node download_web_tamejs2.js destdir < urls.txt

ちなみに、tamejs は入出力に同じファイル名を指定できるので、上記では coffee コマンドで出力されたファイルを tamejs コマンドの処理結果で上書きしています。

CoffeeScript の場合

最後に、TameJS を使わずに CoffeeScript だけで実装したものは以下の通りです。

download_web.coffee
http = require 'http'
url = require 'url'
fs = require 'fs'
path = require 'path'

dir = process.argv[2]

printError = (urlString, error) ->
    console.log "failed: #{urlString}, #{error.message}"

process.stdin.resume()

process.stdin.on 'data', (urls) ->
    urls.toString().trim().split('\n').forEach (u) ->
        trgUrl = url.parse u

        # URL 接続
        req = http.get trgUrl, (res) ->
            res.setEncoding 'binary'
            buf = ''

            # データ取得
            res.on 'data', (chunk) -> buf += chunk

            # データ取得完了
            res.on 'end', ->
                filePath = path.join dir, path.basename(trgUrl.pathname)

                # ファイル出力
                fs.writeFile filePath, buf, 'binary', (err) ->
                    if err
                        printError trgUrl.href, err
                    else
                        console.log "downloaded: #{trgUrl.href} => #{filePath}"

            # 接続後のエラー処理
            res.on 'close', (err) -> printError trgUrl.href, err if err

        req.on 'error', (err) -> printError trgUrl.href, err
実行
> coffee download_web.coffee destdir < urls.txt

*1:コールバックの引数

Maven で CoffeeScript を使用する

Maven を使った Web アプリケーションで CoffeeScript を使うためのプラグイン coffee-maven-plugin をご紹介します。

coffee-maven-plugin を利用するには pom.xml ファイルに以下のようなプラグイン設定を追加します。

coffee-maven-plugin の設定例
<plugin>
  <groupId>com.theoryinpractise</groupId>
  <artifactId>coffee-maven-plugin</artifactId>
  <version>1.4.0</version>
  <configuration>
    <coffeeOutputDirectory>
      ${project.build.directory}/${project.build.finalName}/js
    </coffeeOutputDirectory>
  </configuration>
  <executions>
    <execution>
      <id>coffee</id>
      <goals>
        <goal>coffee</goal>
      </goals>
    </execution>
  </executions>
</plugin>

coffee-maven-plugin はデフォルトで src/main/coffee ディレクトリ内の .coffee ファイルをビルドして target/coffee ディレクトリに .js ファイルを出力するようになっており、このままでは war ファイルに .coffee から生成された .js が含まれません。

そのため、上記例では coffeeOutputDirectory で target//js に .js ファイルを出力するように変更しています。

Maven + CoffeeScript のサンプル Web アプリケーション

それでは Maven で CoffeeScript を使った簡単なサンプルを作成してみます。

Java の Web アプリケーションを想定していますが、web.xmlサーブレット等の作成は省きました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20120216/


pom.xml ファイルは以下の通りです。
今回は web.xml ファイルを省いたので failOnMissingWebXml に false を設定しています。

pom.xml
<project
  xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>fits.sample</groupId>
  <artifactId>mvn_coffee</artifactId>
  <packaging>war</packaging>
  <version>1.0.0</version>
  <name>mvn_coffee</name>
  <url></url>
  <build>
    <finalName>mvn_coffee</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
        <configuration>
          <!-- web.xml が無くても war ファイル化を実行するための設定  -->
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <!-- CoffeeScript -->
      <plugin>
        <groupId>com.theoryinpractise</groupId>
        <artifactId>coffee-maven-plugin</artifactId>
        <version>1.4.0</version>
        <configuration>
          <!-- war ファイルに .coffee 変換後の .js ファイルを含めるために
               出力先を変更 -->
          <coffeeOutputDirectory>
            ${project.build.directory}/${project.build.finalName}/js
          </coffeeOutputDirectory>
        </configuration>
        <executions>
          <execution>
            <id>coffee</id>
            <goals>
              <goal>coffee</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

CoffeeScript のソースファイルを src/main/coffee 内に作成します。

src/main/coffee/sample.coffee
$(->
    message = "sample"
    $("#message").text "#{message} !!"
)

最後に sample.coffee から生成される sample.js を使用する HTML ファイルを用意します。

src/main/webapp/index.html
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="js/sample.js"></script>
<body>
<h2 id="message">Hello World!</h2>
</body>
</html>

以下のように Maven でビルドすると sample.coffee から target/mvn_coffee/js/sample.js が生成され、mvn_coffee.war ファイルに格納されます。

ビルド例
> mvn package

あとは Tomcat 7.0 等にデプロイすれば動作確認できます。