« ^ »

restclientのテンプレートエンジンの仕組みを調べる

所要時間: 約 3分

restclientでは独自の簡易なテンプレートエンジンの機能を提供している。もしかしたら、テンプレートエンジンと呼ぶには機能不足かもしれない。単純に変数を定義し、その変数を展開する。変数定義には文字列とEmacs Lispを利用できる。本当に簡易な機能なのだが、restclient以外でもこの機能を利用したくなる。そこで今回はrestclientの提供するテンプレートエンジンについて調べる事にする。

この機能には特に名前が付いている訳ではない。restclient内で正規表現を定義し、それを解析する事で機能を実現している。Emacs Lispでの正規表現は、エスケープお化けになりがちで、パッと見では良くわからない事が多い。re-builderを用いて正規表現がマッチするかどうかを確認できる。

restclientの話に戻そう。文字列用の変数とEmacs Lisp用の変数の違いは、それを評価するかどうかだ。文字列用の変数として定義された値は単純に文字列として扱われる。この変数定義を解析する正規表現は restclient-svar-regexp で定義されている。 s はstringのsだろうか。

:STRING_VAR = Hello world
文字列用の変数の例

一方Emacs Lisp用の変数として定義された値は評価されるため、S式である必要がある。この定義方法で嬉しいのは、変数に設定する値(つまり右辺)のS式は直接評価できるため、 eval-last-sexp (大抵の場合 C-x C-eにバインドされている) を使って、どのような値が設定されるのかを確認できる事だ。

:LISP_VAR := (concat "Hello" " " "world") ;; この式をC-x C-eすればminiバッファに値が表示される。
文字列用の変数の例

また文字列も以下のようにすれば利用できる。

:VAR := "xxxxx"

この正規表現は restclient-evar-regexp で定義されている。 e はevalのeだろうか。emacs-lispのeだろうか。

変数は << 記号を用いる事で、複数行の定義ができる。この正規表現は restclient-mvar-regexp で定義されている。 m はマルチラインのmだろうか。

これらの正規表現を使い、変数を読み取っている。

(defconst restclient-use-var-regexp
  "^\\(:[^: \n]+\\)$")

(defconst restclient-var-regexp
  (concat "^\\(:[^:= ]+\\)[ \t]*\\(:?\\)=[ \t]*\\(<<[ \t]*\n\\(\\(.*\n\\)*?\\)" restclient-comment-separator "\\|\\([^<].*\\)$\\)"))

(defconst restclient-svar-regexp
  "^\\(:[^:= ]+\\)[ \t]*=[ \t]*\\(.+?\\)$")

(defconst restclient-evar-regexp
  "^\\(:[^: ]+\\)[ \t]*:=[ \t]*\\(.+?\\)$")

(defconst restclient-mvar-regexp
  "^\\(:[^: ]+\\)[ \t]*:?=[ \t]*\\(<<\\)[ \t]*$")
restclient.el抜粋

restclientはこれらの解析の為の関数 restclient-find-vars-before-point を実装している。この関数は名前のとおり、現在のポイントより前に定義された変数を解析し、連想リストにして返す。例えば、次のように変数が定義されていたとする。

:STRING_VAR = hello world
:LISP_VAR := (concat "Hello" " " "world")
:MULTI_LINE_STRING_VAR = <<
Hello world
#

:MULTI_LINE_LISP_VAR := <<
(concat
  "Hello"
  "world")
#

これらの末尾で (restclient-find-vars-before-point) を評価すると、次の連想リストが返される。

((":MULTI_LINE_LISP_VAR" . "Helloworld") (":MULTI_LINE_STRING_VAR" . "Hello world") (":LISP_VAR" . "Hello world") (":STRING_VAR" . "hello world"))

restclientは、この連想リストを元に変数を展開する。変数展開は restclient-replace-all-in-string 関数で行なわれる。その内部では replace-regexp-in-string を呼び出して変数を置換してしているだけだ。

ここまで来ると、この機能だけ抜き出して利用できるように思える。今回は時間の都合で調査までに留めるが、時間が作れたらこの簡易テンプレートエンジンの抜き出し実装をしようと思う。