[[SCALAの記事一覧]]

#areaedit
*RubyでScalaスクリプトをコンパイル補助するツールを作成する。 [#vd683dab]
**目的 [#t615c205]
 HelloWorld程度ならば、コンパイルツールをつくることは必要ないのでしないのですが、
 使えるプログラムというものは、はたくさんのjarをインクルードしたものになりがちです。

以下の問題点を解決
-標準のコンパイル方法ではクラスパスにJARファイルを追加するクラスパスやJARファイルが増えるにしたがって作業が困難になってくる。

-かといって、Mavenをつかってみたんですが無駄な動作をしている.
--大量のJARファイルを読み込む
--更新していないファイルもコンパイルしだす。

-かといってEclipseはコンパイル結果にちょっと癖があって使いづらい。

 
となれば、自分で簡潔に動作する最速のツールをつくってしまおうとおもったわけです。
#areaedit(end)

**目次 [#k5be5712]
#contents



#areaedit
*仕様 [#df26398e]
**ツール1 タイムスタンプスナップ機能 [#ac9ea477]
フォルダ内の.scalaファイルのタイプスタンプのスナップを取得する。
**ツール2 更新ファイル抽出機能 [#g2830135]
既存のタイムスタンプと現在のファイルのタイムスタンプを比較し更新されたファイルを取得しコンパイル可能なバッチファイルを生成する

#areaedit(end)

*scalacc.batの例 [#n0b705be]
-説明 このバッチファイルはSCALAファイルをコンパイルする際、ライブラリを自動的に取り込んでからコンパイルする

BASEが2回出てきているが、どちらも自動的にJarファイルを読み込みたいフォルダである。下記の例のように追加したいフォルダの数だけ繰り返し記述するとよいでしょう。 
 @echo off
 setlocal ENABLEDELAYEDEXPANSION
 set CP=.
 set BASE=C:\scala\lib
 for %%f in (%BASE%\*) do set CP=!CP!;%%f
 set BASE=C:\scala\lib\user
 for %%f in (%BASE%\*) do set CP=!CP!;%%f
 SET SCALA_DOC_HOME="C:\scala\doc"
 ECHO %CP%
 scalac -classpath %CP% %1


*ツール1 タイムスタンプスナップ機能 [#la5cc9ff]

**rubyのディレクトリ関連の処理 [#q1973441]
***ディレクトリ中のファイル一覧を取得する [#ic08cdb2]
-コード例
 p Dir::entries("/home/take")
--結果
 [".", "..", "WorkbookScala.scala"]

***ワイルドカードにマッチしたファイル全てに処理を行う [#nda0b89a]
-コード例
 p Dir::entries("/home/take")
--結果
 [".", "..", "WorkbookScala.scala"]
---注意点
 フォルダの区切り文字は\ではなく、/を使うこと

***ファイルのタイムスタンプを取得する [#ud2674ec]
-例
 s = File.stat("c:/scala/user/src/WorkbookScala.scala")
 puts File.mtime(fname).strftime("%Y/%m/%d %H:%M:%S") # 最終更新時刻
-結果
 c:/scala/user/src/WorkbookScala.scala
 2009/12/05 22:16:11

***snap_timestamp.rb [#hffed54a]
 path=ARGV[0].gsub("\\","/")
 ext="scala"
 outfile="timestamp.txt"
 
 buffer = ""
 # **は再帰的検索。\0は複数パターンの区切り。.*は隠しファイル取得
 Dir.glob("#{path}/**/*\0#{path}/**/.*" + ext).each{|name|
    puts name
    buffer = buffer + name + "\n"
    
	s = File.stat(name)
	timestamp = File.mtime(fname).strftime("%Y/%m/%d %H:%M:%S") # 最終更新時刻
	puts timestamp
	buffer = buffer + timestamp + "\n"
	
 } 
 
 # タイムスタンプスナップを上書き更新します。
 file = File.open(path + "/" + outfile, "w");
 # 書き込む
 file.write(buffer);
 file.close
 

-使い方
   ruby c:\scala\bin\snap_timestamp.rb "c:\scala\user\src"
--指定したフォルダにtimestamp.txtが出力されファイルのタイムスタンプのスナップショットがとれます。

**snap_timestamp_diff.rb [#a7a5adf5]
更新したファイルを出力する
 #第一引数はフォルダ名
 path=ARGV[0].gsub("\\","/")
 ext=/.*\.scala$/
 outfile="timestamp.txt"
 
 current={}
 
 # **は再帰的検索。\0は複数パターンの区切り。.*は隠しファイル取得
 Dir.glob("#{path}/**/*\0#{path}/**/.*" ).each{|name|
	if ext =~ name
		s = File.stat(name)
		current.store(name,File.mtime(name).strftime("%Y/%m/%d %H:%M:%S"))
	end
 }
 
 # タイムスタンプ保存用ファイル読み込み
 before={}
 file = open(path+"/"+outfile)
 buffer = file.read().split("\n");
 file.close
 
 #2行を1つの配列にまとめた配列を作成
 newItem=""
 newItem = nil
 buffer.each{|line|
	if newItem == nil
		newItem=line
	else
		if ext =~ newItem
			before.store(newItem,line)
		end
		newItem = nil
 	end
 } 
 
 
 #タイムスタンプの比較 更新・新規追加したファイルを出力
 current.each{|filepath, timestamp|
	if timestamp != before.fetch(filepath)
		puts filepath
	end
 }
-使い方
 ruby snap_timestamp_diff.rb "c:\scala\user\src" > updatelist.txt


***備考 [#w8c99c3c]
 RubyをWindowsのDOSで動作させるとRubyからバッチファイルを動作させる際に使用する
 システムのShellが使用している
-ruby\lib\ruby\1.8\shell\command-processor.rb
 のPathの分割処理がバグっているというかcygwinしか想定していないので、
 使えません。
 動かしてもwindows版rubyにはfork関数が最初から実装されていないっぽい。
 つまり複数プロセスの同時実行には対応していない。


*Scala でコマンドの実行結果を文字列として取得 [#m4deff0b]
別途prototype.jarをつくって、簡単に取得できるようにつくっておきました。
-コマンド結果を取得する例
"dir".exeresult

簡潔でしょ?

下記の例は簡潔にかけているんだけど、実際にはInputStreamを閉じないとそのうち不具合がでるので、しっかりとcloseする処理が必要です。

その点上のprototype.jarを使えば、その点が考慮されているばかりか、文字列、配列の基本クラスの拡張までやってくれるので、

開発効率がアップしますよん。


**「java -version」を実行する例 [#ddc5fad5]
 	Runtime r = Runtime.getRuntime();
	Process p = r.exec("java -version");

**複数の引数の指定方法 [#dfda1e31]
	Runtime r = Runtime.getRuntime();
	Process p = r.exec(new String[] {
		"C:\\Program Files\\Java\\j2re1.4.2_13\\bin\\java",
		"-version" });

**「echo zzz」を実行する例 [#sc8628a2]
	Runtime r = Runtime.getRuntime();
	Process p = r.exec("cmd /c echo zzz");
 //	Process p = r.exec(new String[]{ "cmd", "/c", "echo", "zzz" });


**環境変数の指定 [#jab4f667]
	String[] env = new String[2];
	env[0] = "TEST=サンプル";
	env[1] = "PATH=" + System.getProperty("java.library.path");

	Runtime r = Runtime.getRuntime();
	Process p = r.exec("cmd /c echo %TEST%", env);


***参考URL 外部プロセス起動 [#qeb4d144]
http://www.ne.jp/asahi/hishidama/home/tech/java/process.html#Runtime
***Javaで,子プロセスを使うときの注意点 [#f56f123d]
http://isolinear.info/wiki/index.php/Java/Tips/Java%A4%C7%A1%A4%BB%D2%A5%D7%A5%ED%A5%BB%A5%B9%A4%F2%BB%C8%A4%A6%A4%C8%A4%AD%A4%CE%C3%ED%B0%D5%C5%C0.html

*バッチファイルからだと連続して2つめのコンパイル以降が無視される。 [#z9001eca]
バッチファイルからだと、どういうわけかわかりませんが、1つめのコンパイルが終わるとバッチファイルが終了してしまいます。

startコマンドをつかうと、非同期にコンパイルされてしまい、コンパイルが正しくできない場合があります。
そこで、対策として、SCALAでコンパイルごとにプロセスをたて、なおかつ同期をとりながらコンパイルするコードを作成しました。



-scala
 import prototype.Prototype._
 args(0).read{line :String =>
    ("scalacc " + line).exe
 }

**説明 [#z100501f]
--prototype.Prototypeは自作のprototype.jarファイルをクラスパスにいれることで使える拡張です。
--readは文字列をファイル名と見なし、該当するファイルから1行ずつブロック変数として取得します。
--exe は文字列をコマンドラインの文字列とみなし実行し結果を出力します。


**余談 コマンドラインの実行結果を環境変数に格納する方法での怪しい現象 [#j3f16ff5]
コマンドラインの実行結果を環境変数に格納する方法に
 FOR /F %t IN ('TIME /T') DO SET NOWTIME=%t
というサンプルがある。これをDOS窓から実行すると,DOS窓が閉じてしまうのだ。ところが、DOS窓からさらに
 cmd
と打ち込んで、もう一段DOS窓をかませて実行すると、DOS窓は閉じなくなるのだ。不思議だ。
バッチファイル実行は、どうなるんだろうか。。。

-結果
 コマンドの構文が誤っています。

と出てきました。OSはWindowsXPです。
なんでやねん。って言いたいです。

色々サイトをみていたときに、%tではなく%%t表記の表現があることを思い出してバッチファイルを下記のように修正してみました。
 FOR /F %%t IN ('TIME /T') DO SET NOWTIME=%%t 

-結果

画面が閉じる

ただし
 cmd
でコマンド画面を1段かませた場合には画面は閉じない

-考察
--FOR文をつかうとDOSの起動オプション/cと同様の状態になり、コマンドが完了するとDOS画面を閉じる処理を走らせてしまう。

-対策
--FOR文はBATファイルからの起動専用と認識し、%%t表記を使い、さらにそのバッチファイルの起動には
 cmd /c バッチファイル
という起動方法が良い

-結論
--基本的にDOSコマンドから実行パスを得るスクリプトを書くくらいならscaleで書いた方が良い

--基本的にカレントディレクトリは「.」ドットで済ませる。
 Explorer .

さもないと次に示すバッチファイルを2段構えにしたファイルが必要です。
--e_for.bat
 FOR /F %%t IN ('cd') DO SET CDIR=%%t 
 echo %CDIR%
 Explorer "%CDIR%"
--e.bat
 e_for.bat


---使い方:
上記のバッチファイルをパスが通った場所に配置しておく

---   任意のコマンドラインで
 e (リターン)

*DOSからエディタを簡単に開くバッチ [#w038ce95]
DOS画面からファイルを編集したい場合がちょくちょくある。

そこで次のバッチファイルをパスが通ったところに置くと改善する。
-ed.bat もしくは edit.bat
 start cmd /c "c:\Program Files\sakura\sakura.exe" %1
エディタは自分の好みに合わせて変えるといい。

***cygwinのシェルから呼び出す場合 [#we3e7a62]
上記のバッチファイルを呼び出す例
フォルダの位置は参考まで
 #! /usr/bin/sh
 /cygdrive/c/userbin/edit.bat "$1"


*というか、DOSのよりもCYGWINのシェル環境のほうが少なくても10000倍は優れている [#q07a0131]
DOSはなぜかXPになっても石器時代のままだ。できればcygwin環境を標準でつかいたいものだ、zsh、すくなくともbashである。

しかしながらcygwin上からscalaを実行しようとすると下記の問題点があることが判明したので、泣く泣くDOS環境に戻ってつかっているという経緯があったのだ。
**cygwin上からscalaをつかう際の問題点 [#y18a835d]
-連続キー入力を受け付けない。
-矢印キーの入力がおかしい
-時々ハングアップっぽくなる(ctrl+z リターンで8割は回避できる)
インタプリタでつかうには、使いづらいので困りものである。

**対策 [#qc6715fd]
cygwin上からscalaを呼び出す際には下記のコマンドで開く
 cmd /c start scala
つまり、DOS上かつ別プロセスで実行してやれば問題ないのである。 

**.zshrcの設定 [#z4cd6ead]
以上のことから、cygwinのシェルの設定を修正するため .zshrcファイルもしくは.bash_profileに下記の設定を追加しておく
 alias scala=cmd /c start scala

#areaedit
*バッチファイルの例 [#i2203c7c]
 copy "C:\Documents and Settings\Administrator\workspace\prjPrototype\src\prototype\Prototype.scala" .
 
 :CHECK_UPDATE
 ruby "c:\scala\bin\snap_timestamp_diff.rb" . >updatelist.txt
 
 FOR /F %%t IN ('cd') DO SET CURRENTDIR=%%t 
 REM 

 scalac "c:\scala\bin\scalacc.scala"

-説明
コンパイルしたい対象をコピーしてカレントディレクトリにもってきています。
更新したファイルがないか確かめています。

カレントディレクトリを取得して
コマンドに渡しています。

*scalacc.scala [#l867c1b0]
**目的 [#gbda6f56]
付属のscalacでコンパイルを行うと、連続複数ファイルのコンパイルができないため、これを実現可能なバッチファイルを用意する。

**仕様 [#t54ec363]
拡張子が.scalaであれば、単体ファイルのコンパイル、そうでなければファイル指定であると
認識する

**実行方法 [#e5e5c6f6]
-例1
-- scalaa "c:\scala\bin\scalacc.scala" コンパイルしたいファイル.scala
-例2
-- scalaa "c:\scala\bin\scalacc.scala" コンパイルしたいファイルリスト.txt

ただ、これだと書き方が助長なので下記のバッチファイルを作成
**scalaccc.bat [#tbf494e6]
 scalaa "c:\scala\bin\scalacc.scala" %*

-例3
-- scalaccc コンパイルしたいファイル.scala
-例4
-- scalaccc コンパイルしたいファイルリスト.txt
#areaedit(end)
トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS