このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

CatyScriptと環境変数とテストのこと

CatyScript2.0が完成」で述べたように、予定していたCatyScriptの機能は揃い、今は細かいところを調整しています。CatyScriptの主たる用途は次の2つです。

  1. リクエスト処理を書く。
  2. テストスクリプトを書く。

最近(ここ半年くらい)に実装した機能はテストスクリプト用のものであり、リクエスト処理部分は1年前と同じです。テスト用途だと、「try-catchの双対となる構造」で述べた、環境変数を操作する機能「unclose構文」が必須です。今朝も、unclose構文を手直ししていました。

というわけで、日記らしく今日の午前中やっていた事と、関連する話題を書きます。

内容:

  1. Catyの環境変数
  2. 環境変数に影響されるコマンドをテストする
  3. 簡易シェルを使う
  4. テストのためのスクリプト言語

Catyの環境変数

CatyScriptは、構文も使い方もOSのシェルとよく似ています。Catyの環境変数という概念も、OSのそれとほぼ同じです。実際にCatyの環境変数を表示してみると:


caty:root> env
{
"CONTENT_LENGTH": "-1",
"APP_PATH": "",
"CATY_PROJECT": "org.chimaira.caty.dev",
"LANGUAGE": "ja",
"SYSTEM_ENCODING": "cp932",
"CATY_APP": {
"path": "",
"group": "main",
"description": "root",
"name": "root"
},
"APP_ENCODING": "utf-8",
"CATY_VERSION": "pp-2.0.0",
"SERVER_MODULE": "caty.front.web.simple",
"REQUEST_METHOD": "POST",
"CATY_EXEC_MODE": [
"console",
"test"
],
"DEBUG": false,
"CATY_HOME": "c:\\Users\\hiyama\\Work\\ProjectCaty\\dev",
"HOST_URL": "http://localhost:8000",
"PATH_INFO": "/"
}
caty:root>

こんな感じです。LANGUAGEという環境変数をもう一度確認すると:


caty:root> %LANGUAGE
"ja"
caty:root>

環境変数もローカル変数も、パーセント記号を前置して参照できます。

unclose構文は、Unixのenvコマンドと類似のもので、一時的に環境変数を変更してからスクリプトブロックを実行します。


caty:root> [{"LANGUAGE":"en"}] | unclose{%LANGUAGE}
"en"
caty:root> [{"LANGUAGE":"fr"}] | unclose{%LANGUAGE}
"fr"
caty:root> %LANGUAGE
"ja"
caty:root>

一番目はLANGUAGEを"en"に、二番目はLANGUAGEを"fr"に変更してからブロック内を実行してます。この変更は一時的なもので、外側(トップレベル)の環境は変わりません。

環境変数に影響されるコマンドをテストする

環境変数の値を単に表示するだけではつまらないので、環境変数LANGUAGEの値により挙動が変わるコマンドを作ってみます。

# -*- coding: utf-8 -*- 
# sample.py
#
from caty.command import Command

class Hello(Command):
    def execute(self):
        lang = self.env.get(u'LANGUAGE')
        if lang == u'ja':
            return u'こんにちは'
        else:
            return u'Hello'

Pythonで書かれたコマンドをCatyScriptから使うには宣言が必要です。

/** CatyScript2.0 のサンプル
*/
module sample;

/** 多言語で挨拶 
 * つっても、日本語と英語だけ。
 */
@[register-public]
command hello :: void -> string
 reads env
 refers python:sample.Hello;

試してみます。


caty:root> %LANGUAGE
"ja"
caty:root> hello
"こんにちは"
caty:root>

環境変数LANGUAGEが"ja"なので、出力は "こんにちは" です。unclose構文で環境変数の値を変えて実行してみます。


caty:root> [{"LANGUAGE":"en"}] | unclose{hello}
"Hello"
caty:root> [{"LANGUAGE":"fr"}] | unclose{hello}
"Hello"
caty:root>

残念ながらフランス語はでませんねー。次のようなこともできます。


caty:root> ["en", "fr", "ja"] | each { [{"LANGUAGE": pass}] | unclose{hello} }
[
"Hello",
"Hello",
"こんにちは"
]
caty:root>

eachはリスト(配列)に関するマップ関数です。環境変数LANGUAGEの値を、"en", "fr", "ja" の順で変えながら hello を実行したことになります。

簡易シェルを使う

Catyの環境変数はイミュータブルで、Catyを再起動しないと本物の環境変数の変更はできません。uncloseで設定できるのは、あくまで一時的な局所環境だけです。でも、いちいち unclose を付けるのは面倒です。LANGUAGE = en の環境でしばらく操作をしたいときはどうすればいいでしょう。

サブシェルを起動すればいいのです。サブシェル? そんなものあったっけ? -- ないです。Catyにサブシェル機能は付いていません。なければ作ればいいのです。

/** quitコマンドであるかどうか判定する */
command quit-p :: string -> @True string | @False string {
 pass > in |
 text:regmatch "^\\s*quit\\s*$" |
 when {
   OK => @True  %in,
   NG => @False %in,
 }
};

/** シェルを実行する */
@[register-public]
command shell :: void -> void {
  begin {
    "ShellWithinShell >> " | cout --no-nl;
    cin | quit-p |
    when {
      True  => void,
      False => eval | xjson:pretty | cout; repeat
    }
  }
};

これでも最低限のシェルの機能はあります。この自家製簡易シェルを、uncloseブロック内で起動すればいいのです。


caty:root> [{"LANGUAGE":"en"}] | unclose{shell}
ShellWithinShell >> %LANGUAGE
"en"
ShellWithinShell >> hello
"Hello"

...(なんやかんや)...

ShellWithinShell >> quit
caty:root>

テストのためのスクリプト言語

CatyScriptは汎用言語ではないので、特定の目的を達成する以上の機能は不要です。特定目的のひとつ(というか、ほとんどすべて)はテストの記述です。その特定目的のために、「自分で自分を記述できる」能力が必須だったのです。その事情は、上の例でだいたい察しが付くかと思います。

自分で自分をテストする都合上、自分の実行環境のなかに自分のモデルを作る事が、さまざまな形で要求されます。“テストする自分”と“テストされる自分”がスノーグローブ現象として登場するのです。スノーグローブ現象は、テスト以外での利用価値もあります

テストはやらなくちゃいけないけど、メンドクサくてイヤなものです。テストを書くことを(ひとつの)目的としたスクリプト言語があれば、状況は改善するんじゃないかな、と思っています。