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 で実行してみました。
- JRuby 1.7.0 preview1
- kramdown 0.13.7
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 をカレントディレクトリに配置して、実行しました。
- PHP 5.4.4
- Markdown Extra 1.2.5
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 で使いました。
- Groovy 2.0.0
- MarkdownJ Core 0.4.1
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 で実行しました。
- Node.js 0.8.0
- CoffeeScript 1.3.3
- Markdown-js 0.4.0
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><blockquote></code></pre> <p> <p>For example.</p> </blockquote> </p> <h3>HTMLとの共存</h3> ・・・ <p>例:April 1<sup>st</sup>
非同期処理で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>
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/
Maven + CoffeeScript のサンプル Web アプリケーション
それでは Maven で CoffeeScript を使った簡単なサンプルを作成してみます。
Java の Web アプリケーションを想定していますが、web.xml やサーブレット等の作成は省きました。
- Maven 3.0.4
ソースは 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 ファイルに格納されます。