2010/06/26


まず、ちょっとタイトルがおかしいので、最後まで読んで下さい。

WindowsのVimを使っている方ならば、KaoriYaのVimを使っておられる方が多いと思います。
KaoriYa.net

Vim 7.2 Vim 7.2を公開します。vim.org等で配布されるオリジナルに、日本語を扱う上で便利な設定やスクリプトが追加されています。Vimはユーザーの方々からの意見などの支援によって育てら...

http://www.kaoriya.net/
KaoriYaが配布しているVimには複数のpatchが当たっていて、migemo検索やフルスクリーンといった便利な機能が入っています。
その中の一つに、modelineの中でfileencodingが指定出来るという物があります。
Vimでは、ファイルのエンコーディングを調べるのにfileencodingsで指定された複数のエンコーディングを失敗しなくなるまで試すという、若干きな粉臭い手法が用いられているのです。ですので例えばファイル内書かれているバイトコードとエンコーディングがサポートする領域によっては誤認識する事もあります。例えばWindowsのCP932等は、サポートする範囲が広いので文字数が少ないとEUC-JPと誤爆する事もあります。
これを解決する為に、ファイル単位で設定を指定出来るmodelineという設定行にfileencoding(fenc)を指定出来るというpatchをKoRoNさんが書きました。
実はこれ、KaoriYaにこのパッチが入る数年前に別のpatchとしてvim-devに送った事があり、その時は「うーん」とBram氏に言われて終わってしまった機能です。

KaoriYaでないVimを使っている場合、modelineにfenc指定があると読み込んだファイルに対してfileencodingを変更する...という動きになってしまい、ファイルが更新されたという扱いになってしまいます。もちろんそのエンコーディングでファイルが開かれる訳でもありません。
このKaoriYa仕様に慣れてしまった方が、modeline内fencを指定したファイルを世に配ってしまう事があります。
そしてそのファイルをKaoriYa仕様でないVimを使っている人が開くと、開いた途端にファイルが更新される...というとても悲しい結末になってしまいます。

今日はその問題を解決するプラグインを書きました。

mattn's modelinefenc-vim at master - GitHub

enable to specify 'fenc' in modeline.

http://github.com/mattn/modelinefenc-vim
これをpluginフォルダにインストールすると、KaoriYa仕様のVimと同じように(modeline内fencに関してだけ)動作します。ファイルを書いた人がmodelineに指定したエンコーディングの通りファイルが表示されます。

KaoriYa仕様でないVimmerには便利かなと思います。ただし、1点守って下さい。

このプラグインでmodeline内にfencを指定出来る様になったはいいのですが、それが指定されているファイルはバラまかないで下さい。
Vimは世界中で使われているテキストエディタです。KaoriYa版でない人、modelinefenc-vimを入れてない人は星の数ほどいます。
あくまで、そんなファイルを開いた時に気持ち悪くなりたくない...という人向けの機能です。

ですのでタイトルは「modeline内でfencを指定されたファイルを開いてイヤーンとならないVimプラグイン書いた。」が正解です。
Posted at by



2010/06/25


追記
os0xさんにjson2.jsで使われている手法である事を教えてもらいました
正しくはエスケープが必要。

以下フォーラムで議論されている内容から拝借
validating json unnecessarily is killing firefox - jQuery Forum
// Try to use the native JSON parser first
if (window.JSON && window.JSON.parse) {
    try {
           return window.JSON.parse( data );
    } catch (err) {
            jQuery.error( "Invalid JSON: " + data );
    }
} else {
    if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]")
        .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) {
        return  (new Function("return " + data))();
    } else {
        jQuery.error( "Invalid JSON: " + data );
    }
http://forum.jquery.com/topic/validating-json-unnecessarily-is-killing-firefox
この検証手順は
  • 使用可能な制御文字および改行を文字参照を"@"に置き換え
  • 文字列と定数を"["に置き換え
  • 全ての空白を削除
すると、その結果は ^[\],:{}\s]*$
という正規表現で表す事が出来るという物。
何個か試してみましたが、100%完璧な検証というよりも簡易的な物みたい。でも不正なスクリプト除けには使えそうな感じ。要検証

最近私が書くvimscriptではeval()を使ってJSONをパースさせてる物があるので、使えないかと思いサクっとvimscriptに移植してみた。
function! s:ValidateJSON(str)
  let str = a:str
  let str = substitute(str, '\\\%(["\\\/bfnrt]\|u[0-9a-fA-F]\{4}\)', '\@', 'g')
  let str = substitute(str, '"[^\"\\\n\r]*\"\|true\|false\|null\|-\?\d\+\%(\.\d*\)\?\%([eE][+\-]\?\d\+\)\?', ']', 'g')
  let str = substitute(str, '\%(^\|:\|,\)\%(\s*\[\)\+', '', 'g')
  return str =~ '^[\],:{} \t\n]*$'
endfunction

let json = substitute(join(readfile(expand('<sfile>')), "\n"), '.*\nfinish\n', '', '')
if s:ValidateJSON(json)
  echo "is valid json"
else
  echo "is invalid json"
endif

finish
{
    "testID": "2",
    
    "testResults": [
        
            {
                "testResultID": "2",
                "contactID": "1",
                "answers": [
                    
                            {
                                "questionID": "6",
                                "answer": "A",
                                "num_attempts": "1",
                            },      
                        
                            {
                                "questionID": "7",
                                "answer": "I do not know.",
                                "num_attempts": "0",
                            },      
                        
                            {
                                "questionID": "9",
                                "answer": "blah",
                                "num_attempts": "0",
                            }      
                            
                ]
            },
        
            {
                "testResultID": "4",
                "contactID": "231",
                "answers": [
                        
                ]
            },
        
            {
                "testResultID": "5",
                "contactID": "231",
                "answers": [
                    
                            {
                                "questionID": "6",
                                "answer": "d",
                                "num_attempts": "1",
                            },      
                        
                            {
                                "questionID": "7",
                                "answer": "hello.",
                                "num_attempts": "1",
                            },      
                        
                            {
                                "questionID": "9",
                                "answer": "This is just test posted text.",
                                "num_attempts": "1",
                            }      
                            
                ]
            }
        
    ]
}
サンプルでもvalidが出てる。
明日にでもeval()使ってるvimscriptに入れていこう。

こういう記事書くと、勝手に検証してくれる人がいて、最近はありがたい世の中になったもんだ...と勝手に期待。
Posted at by




GoはJavascriptに似た書き方が出来るのだけど、それを利用して何か書けないかなーと思って、cho45さんのJSDeferred書いてみた。
これを使うと、JSDeferredに似た事が出来る。
cho45.stfuawsc.com

JSDeferredSimple and clean asynchronous processing.SampleJSDeferred SamplesDownloadjsdeferred.jsNo c...

http://cho45.stfuawsc.com/jsdeferred/
サンプルだとこんな感じ。 package main

import . "deferred"
import "syscall"
import "http"
import "xml"
import "os"

type feed struct {
    Entry []struct {
        Title string
        Link []struct {
            Rel  string "attr"
            Href string "attr"
        }
        Summary string
    }
}

func main() {
    Deferred().
        Next(func() string {
            return "「この戻り値が...」"
        }).
        Next(func(v string) {
            println("ここの引数にくる!:" + v);
        }).
        Next(func() {
            println("かけっこすものよっといで!");
        }).
        Loop(3, func(i int) {
            println(string("ABC"[i]) + ":はい!");
        }).
        Next(func() {
            println("位置についてよーいどん!");
        }).
        Parallel([]interface{} {
            func() {
                println("A:一番手だしちょっと昼寝してから行くか");
                syscall.Sleep(1000*1000*300);
                println("A:ちょwww");
            },
            func() {
                syscall.Sleep(1000*1000*200);
                println("B:たぶん2位かな?");
            },
            func() {
                syscall.Sleep(1000*1000*100);
                println("C:俺いっちばーん!");
            },
        }).
        Next(func() {
            println("しゅーりょー!");
        }).
        Next(func() {
            println("otsune:ネットウォッチでもするか!");
        }).
        HttpGet("http://b.hatena.ne.jp/otsune/atomfeed").
        Next(func(res *http.Response) *feed {
            var f feed;
            xml.Unmarshal(res.Body, &f);
            return &f
        }).
        Next(func(f *feed) {
            for _, entry := range f.Entry {
                println(entry.Title + "\n\t" + entry.Link[0].Href);
            }
        }).
        HttpGet("http://b.hatena.ne.otsune/"). // this make error
        Next(func(res *http.Response) {
            println("ここにはこないよ");
        }).
        Error(func(err *os.Error) {
            println("これはひどい");
        })
}
Goでは例外がcatch出来ない(そもそもthrowがない)のだけど、Goの風習で戻り値の最後にos.Errorを返す風習があるので、その場合はErrorに飛ばす様にしてある。
mattn's godeferred at master - GitHub

port of jsdeferred: http://cho45.stfuawsc.com/jsdeferred

http://github.com/mattn/godeferred
使い道あるか分かんないけど、とりあえず...
※あんましJSDeferredの仕様詳しく無いので正しい動きじゃないかも苦笑
Posted at by