2009/10/31

クエリストリングの取得と cl-who(ABLC で Google app engine)

前回 ABCL で Google app engine が動いたので、今回はクエリスティングの取得と cl-who の使用をやってみた。

Sevlet クラスで currentThread.execute するときに req と resp を JavaObject でくるんでわたしてやる。

package abcl_ae;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.armedbear.lisp.ConditionThrowable;
import org.armedbear.lisp.JavaObject;
import org.armedbear.lisp.Lisp;
import org.armedbear.lisp.LispThread;
import org.armedbear.lisp.SpecialBinding;
import org.armedbear.lisp.Symbol;

@SuppressWarnings("serial")
public class NextServlet extends HttpServlet {

static private Symbol doGet2 = null;

public void init() throws ServletException {
AbclInit.init();
try {
doGet2 = Lisp.internInPackage("DO-GET2", "FIRST-SERVLET");
} catch (ConditionThrowable ct) {
}
}

public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {

LispThread currentThread = LispThread.currentThread();

SpecialBinding lastSpecialBinding = currentThread.lastSpecialBinding;
currentThread.bindSpecial(Symbol.STANDARD_OUTPUT,
new org.armedbear.lisp.Stream(resp.getOutputStream(),
Symbol.CHARACTER, Symbol.internKeyword("UTF-8")));

resp.setContentType("text/html; charset=utf-8");

try {
currentThread.execute(doGet2, new JavaObject(req), new JavaObject(resp));
} catch (ConditionThrowable condition) {
resp.setContentType("text/plain");
resp.getWriter().println(condition.toString());
} finally {
currentThread.lastSpecialBinding = lastSpecialBinding;
}
}
}

lisp ファイルの方は関数(go-get2)に引数を2つ追加。 java:jcall で getParameter メソッドを呼び出せばクエリストリングが取得できる。

あと cl-who を使うために asdf を rquire し、fasls/cl-who/ を asdf:*central-registry* に追加。 (asdf:oos 'asdf:load-op :cl-who) で cl-who が使えるようになる。

(require 'asdf)
(pushnew "fasls/cl-who/" asdf:*central-registry* :test #'equal)
(asdf:oos 'asdf:load-op :cl-who)

(defun do-get2 (request response)
(declare (ignore response))
(let ((foo (java:jcall "getParameter" request "foo"))
(bar (java:jcall "getParameter" request "bar")))
(cl-who:with-html-output (*standard-output*)
(:html (:head (:title "cl-who"))
(:body (:h1 "CL-WHO")
(:form :method :get :action "next"
(:div "foo " (:input :type :text :name "foo"))
(:div "bar " (:input :type :text :name "bar"))
(:input :type :submit :value "クリック"))
(:div (cl-who:esc (format nil "foo => ~a" foo)))
(:div (cl-who:esc (format nil "bar => ~a" bar))))))
(force-output)))

cl-who はあらかじめ ABCL でコンパイルしておいて、*.asd と *.abcl を war/fasls/cl-who にコピーしておく必要がある。これがうまい方法かどうかわまだ分からないけど。そのための build.xml を書いた(コピペした)。

<project basedir="." default="all">

<property name="sdk.dir" location="/home/ancient/local/opt/appengine-java-sdk" />
<property name="clbuild.dir" location="/home/ancient/letter/lisp/clbuild/source" />

<import file="${sdk.dir}/config/user/ant-macros.xml" />

<path id="project.classpath">
<pathelement path="war/WEB-INF/classes" />
<fileset dir="war/WEB-INF/lib">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="shared/**/*.jar" />
</fileset>
</path>

<target name="all" depends="clean,copy-files" />

<target name="clean">
<delete dir="war/fasls" />
</target>

<target name="copy-files">
<copy todir="war/fasls">
<fileset dir="src/lisp">
<include name="**/*.abcl"/>
</fileset>
</copy>
<copy todir="war/fasls/cl-who">
<fileset dir="${clbuild.dir}/cl-who">
<exclude name="**/_darcs/**"/>
<include name="**/*.abcl"/>
<include name="**/*.asd"/>
</fileset>
</copy>
</target>

<target name="devserver" description="run local dev appserver" depends="all">
<dev_appserver war="war" />
</target>

<target name="deploy" description="deploy to appspot" depends="all">
<appcfg action="update" war="war" />
</target>
</project>

けっこういける気がしてきた。まだ Servlet とのつながり(レスポンスやポストとか)とかいろいろ問題ありそうだけど。あと開発サイクルとかも。ソース修正のたびに開発サーバ再起動はきつい。でも解決方法はある気がする。

MOP で Google app engine のデータストアうまく扱えないかしらん。どうせならちゃんと Common Lisp っぽくいきたいよね。

ABCL で Google app engine

前回は Clojure だったので今回は ABCL。 ABCL on Google App Engine を頼りにやってみる。

まずは Eclipse でプロジェクトを作る(手抜き)。

abcl.jar を war/WEB-INF/lib に ln -s して Eclipse の build pass に abcl.jar を追加。

ABCL のリポジトリにあるサンプルから AbclInit.java, HelloWorldServlet.java, first-servlet.lisp をプロジェクトのソースディレクトリにコピー。

first-servlet.lisp は Slime で C-c C-k してコンパイル。 war/fasls ディレクトリを作ってコンパイルした first-servlet.abcl をコピー。

war/WEB-INF/appengine-web.xml の application タグにアプリケーション名(ID?)を設定。

/war/WEB-INF/web.xml の servlet と servlet-mapping を設定(ABCL のリポジトリにあるサンプルそのまんま)。

だいたいこんなもんで(説明も手抜きだ)、Eclipse からプロジェクトを右クリックして Run As -> Web Application する。 http://localhost:8080/hello にアクセス。動いた。

Eclipse のエンジンボタン(?)をクリックしてデプロイしてみる。パスワード入力すればファイルがアップロードされて完了。 http://hello-abcl.appspot.com/hello 動いてる♪

日本語(というよりむしろ UTF-8)を表示したい場合は、 HelloWorldServlet.java の doGet メソッド中で、 org.armedbear.lisp.Stream を new するときに Symbol.internKeyword("UTF-8") を渡してやり、ついでに resp.setContentType("text/html; charset=utf-8"); もしておく。

public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {

LispThread currentThread = LispThread.currentThread();

SpecialBinding lastSpecialBinding = currentThread.lastSpecialBinding;
currentThread.bindSpecial(Symbol.STANDARD_OUTPUT,
new org.armedbear.lisp.Stream(resp.getOutputStream(),
Symbol.CHARACTER, Symbol.internKeyword("UTF-8")));

resp.setContentType("text/html; charset=utf-8");

try {
currentThread.execute(doGet);
} catch (ConditionThrowable condition) {
resp.setContentType("text/plain");
resp.getWriter().println(condition.toString());
} finally {
currentThread.lastSpecialBinding = lastSpecialBinding;
}
}

この例では resp.getOutputStream() を *standard-output* にバインドしているだけだが、 currentThread.execute(doGet); のことで req と resp を渡すようすれば、クエリパラメータとかもいじれるようになるよね。きっと。また今度やってみよう。

なにはともあれ Common Lisp で Google app engine できそうだ。 ABCL ありがとう。

追記。 lisp では最後に ~% とかで改行を出力するか force-output しとく必要があるような気がする。

2009/10/29

Clojure で Google app engine

基本的に Clojure on Google AppEngine - El Humidor のとおりやれば動いた。

ちょっとひっかかったところがいくつか

  • clojure.jar, clojure-contrib.jar は Compojure の ant deps のものではなく、ちゃんとそれぞれの github から持ってきたものを使う。
  • commons-codec-1.3.jar, commons-fileupload-1.2.1.jar, commons-io-1.4.jar も war/WEB-INF/lib にコピー(or ln -s)する。
  • (GET "/" だと何故がうまく動かなかったので (GET "/foo" とかした。

2009/10/28

「悲しいけど、これで間にあっちゃったのよね…」とのことですが

悲しいけど、これで間にあっちゃったのよね…」とのことですが、 with-output-to-string 使うとなんだか綺麗に書けないですね。

なので根本的にズルをしてリストを string に coerce する方向で

(loop for i across "0123456789"
for pred = (evenp (char-code i))
if pred
collect i into a
else
collect i into b
finally (return (list (coerce a 'string) (coerce b 'string))))

で、無理矢理 SERIES

(require :series)
(let* ((series (series:scan "0123456789"))
(bools (series:map-fn t (lambda (x) (evenp (char-code x))) series)))
(mapcar (lambda (x) (series:collect 'string x))
(multiple-value-list (series:split series bools))))

2009/10/25

Parenscript で jQuery を使う場合は chain を

Parenscript で jQuery を使うのは のエントリで Parenscript のメンテナ Vladimir Sedach さんからコメントをもらい、既に chain というのがあると教えてもらった。Vladimir Sedach さんありがとうございます。

(ps:ps (ps:chain ($ "#f1") (attr "action" "edit")))
;; => "$('#f1').attr('action', 'edit');"

(ps:ps (defun foo (x)
(bar x (ps:chain x bano (bino) (baha 1 2 3)))))
;; => "function foo(x) {
;; bar(x, x.bano.bino().baha(1, 2, 3));
;; };"

素晴しい!!

2009/10/17

Parenscript で jQuery を使うのは

chain を使いましょう。

ということで以下は不要。

Common Lisp で JavaScript が書ける Parenscript はすばらしい。でも Parenscript で jQuery を使うのはとてもめんどくさい。例えば $('#f1').attr('action', 'edit') は

(ps:ps (ps:@ ((ps:@ ($ "#f1") attr) "action" "edit")))
のようになる。メソッドをつなげていくのが不得手なの。 Lisp らしいシンタックスではあるが、これでは書く気にならない。

というわけで定番の「マクロを使えば」となる。

(js (-> $ ("#f1") attr ("action" "edit")))
これなら Parenscript で書く気になるよね。
(defmacro js (&body form)
`(ps:ps ,@(mapcar #'parse-js form)))

(defun ->p (x)
(and (symbolp x)
(string= "->" (symbol-name x))))

(defun parse-js (form)
(cond ((atom form)
form)
((->p (car form))
(-> (cddr form) (cadr form)))
(t
(mapcar #'parse-js form))))

(defun -> (form acc)
(cond ((null form)
acc)
((listp (car form))
(-> (cdr form) (if (->p (caar form))
`(,acc ,(parse-js (car form)))
`(,acc ,@(parse-js (car form))))))
(t
(-> (cdr form) `(ps:@ ,acc ,(car form))))))

2009/10/12

三鷹の森ジブリ美術館に行ってきた

義妹一家と一緒に三鷹の森ジブリ美術館に行ってきた。よかった。きっと猫も楽しめると思う。

2009/10/03

StumpWM

最近(?) StumpWM が人気のようなので、ようやくセットアップして使ってみた。

結果、Emacs 23 が爆速。 Gnome 環境でアンチエイリアスを効かせた Emacs が 22 に戻そうかと思うくらい遅かったけど、ウィンドウマネージャが原因だったのかねぇ。軽い、速いってのはいいことだ、と改めて実感した。

参考サイト

~/letter/lisp/clbuild/my-projects に次の追加。

# stumpwm
stumpwm get_git git://git.savannah.nongnu.org/stumpwm.git

StumpWM 持ってきてビルドする。

cd ~/letter/lisp/clbuild
./clbuild install stumpwm
cd ~/letter/lisp/clbuild/source/stumpwm
autoconf
./configure
make

xmodmap は stumpwm の前に実行する。 ~/bin/stumpwm

#!/bin/sh

xmodmap ~/.xmodmaprc
xset b off
~/bin/xsands&
exec ~/letter/lisp/clbuild/source/stumpwm/stumpwm

Emacs と Opera と ターミナルがあがってればそれでいい。 ~/.stumpwmrc

;; -*- mode: common-lisp; -*-
;; debian=sbcl

(in-package :stumpwm-user)

;;(asdf:oos 'asdf:load-op 'swank)
;;(swank:create-server :dont-close t)

;; 以下は ~/bin/stumpwm の中で実行する。
;;(run-shell-command "xset b off" t)
;;(run-shell-command "xmodmap ~/.xmodmaprc" t)
;;(run-shell-command "xsands")

(set-prefix-key (kbd "C-)"))

(setq *shell-program* "/bin/zsh")

(set-font "-*-gothic-medium-r-normal-*-*-*-*-*-p-*-iso10646-1")

(define-key *root-map* (kbd "C-p") "emacs")

(setq *mouse-focus-policy* :sloppy)

(defcommand opera () ()
"Start opera unless it is already running, in which case focus it."
(run-or-raise "opera" '(:class "Opera")))
(define-key *root-map* (kbd "C-(") "opera")

(defcommand gnome-terminal () ()
"Start gnome-terminal unless it is already running, in which case focus it."
(run-or-raise "gnome-terminal" '(:class "Gnome-terminal")))
(define-key *root-map* (kbd "C-y") "gnome-terminal")

(opera)
;;(emacs) .zshrc の環境変数が設定されないのでターミナルから手動起動するよ。
(gnome-terminal)

GDM からログインするために /usr/share/xsessions/clbuild-stumpwm.desktop

[Desktop Entry]
Encoding=UTF-8
Type=XSession
Exec=/home/ancient/bin/stumpwm
TryExec=stumpwm
Name=StumpWM
Comment=Stump window manager

さて、Gnome から移行するかどうかはまだ未定w 環境まわりはいじりだすと時間がいくらあっても足りないので、なるべく悩みたくない。その点では Gnome は素晴らしい。何も設定する必要がない(することができないとも)。しかし、軽くて速いのは善だ。