ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

Windows Script Host Framework(wshf)見て、ラッパライブラリとはなんぞやと考えた。

前々からWSHを便利に使えたらいいよなーと思いつつ、
作ったスクリプトのコピペで大体の用は足りてたので、
共通フレームワークを作るというところまでは行ってませんでした。


が、たまたまTwitter上でWSHの話題になって、教えてもらったのが、
wshfというライブラリでした。

プロジェクトサイト:wshf - Project Hosting on Google Code

ファイルをコピーするプログラムを書いてみる

ライブラリの効用を確かめるために、
簡単なプログラム(ファイルをコピーするプログラム)
を書いて、試してみる。

ライブラリ等を使わないで、素のJScript on WSHで書いた場合
<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<package>
  <comment>CScript //Job:A1 test.wsf
  see: wshf - Project Hosting on Google Code
  http://code.google.com/p/wshf/
  </comment>
  <job id="A1">
    <script language="JScript">
    <![CDATA[
      var ts;
      try {
        // ファイルを開く
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        ts = fso.OpenTextFile("test.java");
        ws = fso.CreateTextFile("test.txt");

        // 各行を読み取って出力
        while(!ts.AtEndOfStream) {
          ws.WriteLine(ts.ReadLine());
        }
      } catch(e) {
        // エラー時の処理
        WScript.Echo("Error Occured!" + e);
      } finally {
        // ファイルクローズ
        if(ws) {
          ws.Close();
        }
        if(ts) {
          ts.Close();
        }
      }
    ]]>
    </script>
  </job>
</package>

これはこれで良いような気もしますが、
new ActiveXObject("Scripting.FileSystemObject");
の辺りで、文字列を指定してオブジェクトを作っているのが、
若干気持ち悪い気がしないでもないです。

wshfを使った場合
<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<package>
  <comment>CScript //Job:A1 test.wsf
  see: wshf - Project Hosting on Google Code
  http://code.google.com/p/wshf/
  </comment>
  <job id="A1">
    <script language="JScript" src="http://wshf.googlecode.com/svn/trunk/lib/WSHF.js"></script>
    <script language="JScript">
    <![CDATA[
      $.fileCreate("test.txt", function (writeStream) {
        var FILE_IOMODE_FOR_READING = 1;
        $.fileOpen("test.java", FILE_IOMODE_FOR_READING, function (textStream){
          while(!textStream.AtEndOfStream) {
            writeStream.WriteLine(textStream.ReadLine());
          }
        });
      });
    ]]>
    </script>
  </job>
</package>

rubyっぽく、すっきり書けますね!


ただ、このコードで気になったのは、
new ActiveXObject("Scripting.FileSystemObject");
はうまくラップできたものの、
AtEndOfStreamプロパティや、ReadLineメソッドは、
FileSystemObjectのままです。


ここで、ふと、ラッパライブラリに求められるものはなんだろう?
と考えました。
どこまでをラップして、どこまでをラップしないのか。


以前、rubeusというライブラリの開発に携わっていたときに、
JRubyで、JavaのライブラリをRubyっぽく書けるようにする
というのが目的だったのを思い出し、
Javaっぽく書けるようにしてみたらいいんじゃないか。
と思いました。
(そういやrubeusは、うやむやのうちに放置状態になってしまいましたね。。)


で書いたのがこんなの。

Javaっぽく書けるようなラッパライブラリを使った場合
<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<package>
  <comment>CScript //Job:A1 test.wsf
  see: wshf - Project Hosting on Google Code
  http://code.google.com/p/wshf/
  </comment>
  <job id="A1">
    <script language="JScript" src="WSHF-0.0.3a.js"></script>
    <script language="JScript">
    <![CDATA[
      try {
        // ファイルを開く
        var fr = new WSHF.io.FileReader("test.java");
        var fw = new WSHF.io.FileWriter("test.txt");

        // 各行を読み取って出力
        var buf;
        while((buf = fr.readLine()) != undefined) {
          fw.writeLine(buf);
        }
      } catch(e) {
        // エラー時の処理
        WScript.Echo("Error Occured!" + e);
      } finally {
        // ファイルクローズ
        if(fw) {
          fw.close();
        }
        if(fr) {
          fr.close();
        }
      }
    ]]>
    </script>
  </job>
</package>

Javaプログラマにとっては馴染みのある形なのではないでしょうか?
また、上でwshfの問題とした、FileSystemObjectのプロパティやメソッドも出てきていません。
まあ、Javaが嫌いな人にとってはクソくらえなわけで、
Rubyが好きな人は、現状のwshfのRubyっぽさを残しつつ、完全にラップしてしまうかも知れません。
要は人それぞれってことですね。


なお、ライブラリのソースは以下のようになってます。

// The only global object.
var WSHF = {};
WSHF.VERSION = '0.0.3a';

// Add io namespace to WSHF
WSHF.io = {};

// Create File System Object
WSHF.io.FSO = new ActiveXObject("Scripting.FileSystemObject");

// Constructor of WSHF.io.FileReader
WSHF.io.FileReader = function(fileName) {
  this.ts = WSHF.io.FSO.OpenTextFile(fileName);
};

// Prototypes of WSHF.io.FileReader
WSHF.io.FileReader.prototype = {
  readLine: function() {
    if(this.ts.AtEndOfStream) {
      return undefined;
    } else {
      return this.ts.ReadLine();
    }
  },
  close: function() {
    this.ts.Close();
  }
};

// Constructor of WSHF.io.FileWriter
WSHF.io.FileWriter = function(fileName) {
  this.ts = WSHF.io.FSO.CreateTextFile(fileName);
};

// Prototypes of WSHF.io.FileWriter
WSHF.io.FileWriter.prototype = {
  writeLine: function(str) {
    this.ts.WriteLine(str);
  },
  close: function() {
    this.ts.Close();
  }
};

ラッパライブラリ道は険しい。。

ラッパライブラリを作るには、
ラップするオブジェクトが持つ公開インタフェースの分だけ、
デリゲートを作らなければならないので、非常に面倒です。。


上のFileWriterの例だと、

// Prototypes of WSHF.io.FileWriter
WSHF.io.FileWriter.prototype = {
  writeLine: function(str) {
    this.ts.WriteLine(str);
  },
  close: function() {
    this.ts.Close();
  }
};

writeLine、closeの二つのメソッドが定義されており、
それぞれ、ラップしているオブジェクトの対応するメソッドを呼んでいるわけですが、
ラップしているオブジェクトの公開インタフェースが多いほど、
このデリゲートを作成するという単純作業が増えることになります。。


というわけで、やっぱりコピペでいいかな。。などと思ってしまうこの頃でした。