テンプレートエンジン
gnu-Smalltalkをインストールすると、CGIから利用できるHTMLテンプレートエンジンらしき物もおまけで付いてくる。 smalltalk/net/httpd/STT.st がそれです。ちゃんとサンプルもある。↓
test | sttTest | sttTest := ' <html> <head> <title>{%= self class %}</title> </head> <body> <table> {% self to: 10 do: [ :each | %} <tr> <td>{%= each printString %}</td> <td>{%= (each * 2) printString %}</td> </tr> {% ] %} </table> </body> </html>'. ^(STTTemplate on: sttTest) evaluateOn: 1.
evaluateOn: に渡すオブジェクトがテンプレート内の self になります。 evaluateOn: の中では perform: してて、渡ってきたオブジェクトよって色々な仕事をしてくれてます。
このサンプルプログラムの処理結果を見た感じでは、 to:do:[ 〜 ] で囲まれた部分がその回数分出力されて、それとは別で特別に出力が必要な処理は {%= 〜 %} で囲み、必要ない処理は {% 〜 %} で囲むようです。気を付けないといけないのは、この例だと一つの処理({%= 〜 %}と{% 〜 %})の終わりに"."は必要無いらしいです。"."が有るとエラーになります。(追記:"."を付けてエラーになるのは {%= 〜 %} の場合)なんだか不明な点が幾つかあるんですが、とりあえずテストCGIを作ってみた。
#!/usr/bin/env gst -Q FileStream fileIn: 'GST_PATH/smalltalk/net/httpd/STT.st'. ! | max fileName sttTest path | stdout nextPutAll: 'Content-Type: text/html; charset=UTF-8';nl;nl. Diaryarray := OrderedCollection new. path := 'DIARY_PATH/diary'. max := 5. sttTest := ' <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>てすと</title> <style type="text/css"> <!-- div.diary { width: 450px; border-style: solid; margin-bottom: 30px; } --> </style> </head> <body> <!-- gst --> {% 1 to: self do: [:each | %} <div class="diary"> {%= (Diaryarray at: each) contents %} </div> {% ] %} <!-- End Of gst --> </body> </html>'. Directory working: path. fileName := (Smalltalk getenv: 'QUERY_STRING'). (fileName = '') ifTrue: [ Directory allFilesMatching: '*' do: [:each | Diaryarray add: each]. ] ifFalse: [ Directory allFilesMatching: fileName do: [:each | Diaryarray add: each] ]. (Diaryarray size < 1) ifTrue: [ stdout nextPutAll: ((STTTemplate on: sttTest) evaluateOn: 0). ] ifFalse: [ (Diaryarray size < max) ifTrue: [ stdout nextPutAll: ((STTTemplate on: sttTest) evaluateOn: Diaryarray size). ] ifFalse: [ stdout nextPutAll: ((STTTemplate on: sttTest) evaluateOn: max). ]. ]. ObjectMemory quit. !
しょぼいテストっすね、ごめんなさい。
このファイル内とテンプレート内({%= 〜 %}と{% 〜 %})ではスコープが違うので、 Diaryarray とかいう大域変数を用意してます。このCGIは何をするかというと、保存されている日記ファイルをディレクトリから検索したりして拾ってきてその内容を表示するという単純な物です。表示の仕方も検索もだいぶテキトーです。表示する件数は max の初期値や QUERY_STRING の値で微妙に変わってきます。
今の所、ぜんぜん便利に使えてないんですが、便利に使おうとすると大域変数が沢山増えそうな感じもするし・・・ むぅ、もうちょっといじってみる。
追記
テンプレートの中では ifTrue:ifFalse: が使えない? 使うとエラーになる。けど、ifTrue: と ifFalse:だけなら使える。
<!-- gst --> {% (self = 0) ifTrue: [ %} {%= ''コンテンツが見付かりませんでしたTT'' %} {% ] %} {% (self ~= 0) ifTrue: [ %} {% 1 to: self do: [:each | %} <div class="diary"> {%= (Diaryarray at: each) contents %} </div> {% ] %} {% ] %} <!-- End Of gst -->
さらに追記
こうすると ifTrue:ifFalse: が使えるようだ。
<!-- gst --> {% (self = 0) ifTrue: [^''コンテンツが見付かりませんでしたTT'' readStream] ifFalse: [ %} {% 1 to: self do: [:each | %} <div class="diary"> {%= (Diaryarray at: each) contents %} </div> {% ] %} {% ] %} <!-- End Of gst -->
テンプレート内で"^"を使って返す値はストリーム以外だとエラーになります。この例だと True の場合、出力されるのは返した文字列だけでHTMLの部分は出力されません。
さらにさらに追記
テンプレートの書き方を勘違いしてました。こうするとちゃんと ifTrue:ifFalse: が使える。
<!-- gst --> {% (self = 0) ifTrue: [ %} {%= ''コンテンツが見付かりませんでしたTT'' %} {% ] ifFalse: [ %} {% 1 to: self do: [:each | %} <div class="diary"> {%= (Diaryarray at: each) contents %} </div> {% ] %} {% ] %} <!-- End Of gst -->
勘違いな書き方でエラーになったのはこんな感じ。
<!-- gst --> {% (self = 0) ifTrue: [ %} {%= ''コンテンツが見付かりませんでしたTT'' %} {% ] %} {% ifFalse: [ %} {% 1 to: self do: [:each | %} <div class="diary"> {%= (Diaryarray at: each) contents %} </div> {% ] %} {% ] %} <!-- End Of gst -->
ごちゃごちゃしてて解かりづらいですが、まぁそう言うことらしいです。
さらにさらにさらに追記
大域変数ではなくて、 Dictionary を使ったほうがいいかも。