(「Perl で Exif を扱う」のつづき)
Xcode を使って、 Perl スクリプトに AppleScript の GUI を付けてみて、そこそこうまくいったので忘れないように、いや、忘れるので、忘れないうちにメモ。
AppleScript から do shell script という命令を使うと、 UNIX コマンドを起動する事ができる。コマンドが起動できるということはつまり、なんでもできるということになるかしら? それはさておき、その仕掛けを使えば、無愛想なコマンドライン・ツールであった Perl スクリプトに GUI をかぶせて Mac OS X 用のアプリケーションに仕立てる事もできる。 Objective-C を使わなくとも(いえ、知らないんです) Perl (や他の言語でもいいけどそれら)と、少しの AppleScript の知識があれば、それっぽいものが「手軽に」できるのだ。
──ということは前から知ってはいて、実際やってみたいとたびたび思っていたのだけれども、 AppleScript の壁がなかなか超えられず、興味はあれども、これまでの Xcode は結局ハードディスクの肥やし以外の何ものにもならなかった。
Xcode で AppleScript のアプリケーションを作るとき、その環境の事を AppleScript Studio と言うらしい。言語としての AppleScript はもとよりその環境、たとえば Interface Builder とかの使い方とかそれらとコードの連携の仕方、といった環境の中のことも、手探りではなかなか難しかった。
でもありがたいことに、簡単な手順を説明してそれを公開しているページなども、ひと昔に比べて見つけやすくなったので、このたび改めて、少々力をいれて取り組んでみることにした。
AppleScript Studio のことは、じぶんが参照したそれらのようにいろいろ散見されるので、ここで改めて繰り返さなくてもいいとして、ここでは、 Perl に GUI を付ける、ということに注目して、じぶんが試行錯誤して得たひとつの道筋、それをメモしておくことにする。そういう(ある種限定的な問題を扱うような) TIPS が、ほかではうまく見つけられなかったのだ。
インタフェース
さて、 AppleScript から呼び出すにあたっては、 Perl スクリプトのほうには制限がいくつか生じてしまうので、そこを常にケアしておかなくてはいけない。とはいっても、落ち着いて考えれば、あたりまえといえばあたりまえなことばかりではある。
ここでいうインタフェースは、 AppleScript と Perl とのつなぎの部分。彼らがどうやって互いの入出力を行っているか、双方を連携させる場合は、そこが大きなポイントになる。
AppleScript から Perl を呼び出す時、それは do shell script という命令を発する。これに渡すパラメータが、シェルに渡すコマンドラインそのものになる感じだ。従って、 Perl スクリプトを作るときには、そういう利用のされ方をするものだと考えておけばいい。これはなんら特別な事ではないので、問題もないだろうけども。
ポイントは、コマンド( Perl スクリプト)の処理の途中では、処理が終わるまでは、 AppleScript 側には戻れないというところだ。言うなれば Perl スクリプトは対話型にはできない。つまり処理が始まったら、最後まで走り抜けなくてはいけない。また処理が始まったら途中でキャンセルできない。
これは、高度で複雑な事をすればできるのかもしれないけれど、 AppleScript ビギナーのじぶんには思いつかなかった。
ただし、もし、何らかの原因で Perl スクリプトが失敗、つまり die などで abort されるべき事態に陥ったらば、それは do shell script が例外を出すので、それを補足することはできる。だから、常に try 構文で括っておくようにしたい。
try
do shell script "/usr/bin/perl script.pl"
on error msg number val
display dialog msg buttons {"OK"} default button 1
end try
do shell script の戻り値は、実行したコマンドの標準出力となる。なので、コマンドの処理内容によって結果の出し方はいろいろだけれども、簡単なメッセージを得るだけなら、戻り値を AppleScript のほうで、その出力を再利用すればよい。
また、たとえば何かのファイルに結果を書き出すならば、それはコマンドのほうで世話をしておけばいい。たとえば、コマンドライン引数に、出力ファイル名を受け取るような作りにしておくなどして、 do shell script に渡すコマンドラインのその引数に、オプションを追加する。
try
set OUT to do shell script "/usr/bin/perl script.pl -o output.txt"
display dialog OUT buttons {"OK"} default button 1
on error msg number val
display dialog msg buttons {"OK"} default button 1
end try
しかしこのやりかたの場合、出力するファイルが既に存在していたら? という手続きは、じつは悩ましいものかもしれない。じぶんの場合は、 AppleScript のファイル選択ダイアログを使い、 AppleScript 側で上書きしてもよいかをユーザに訊ねるようにした。
try
set OUTPUT_FILE to POSIX path of (choose file name with prompt "output file" default name "Untitled.txt")
on error
return
end try
こうして得られたファイルのエイリアス(パス)をコマンドに渡して処理させる。ただし、もし、ファイル選択ダイアログのときには存在しないファイルを指定したのだとしても、コマンド実行のイベントまでの間にその場所にファイルが作成されたら、結果はどうなるだろうか。できることなら、ファイルをオープンする直前に、上書きしてもよいかともう一度、内部で判断させてケアしたいところだけれども、じぶんの手際では妥協せざるをえなかった。
カレント・ディレクトリ
Perl のコードをパッケージに分割したりして、メインの Perl スクリプト( AppleScript から呼ばれるコマンド)が、 use lib などでディレクトリを指定したがっていたらどうしよう。これも大きな悩みだった。
これは Apple のドキュメントにヒントがあった。
AppleScript では AppleScript 自身の動作しているカレント・ディレクトリを知る事はできる。そこでまず、そこへ cd してから、コマンドを実行するようにすればよい、というアイデアだ。
set PERL_COMMAND to "script.pl" set CWD to resource path of main bundle as string do shell script "cd '" & CWD & "'; /usr/bin/perl " & PERL_COMMAND
このとき、ディレクトリに空白文字が入ったりすることは十分想定できるので、シェル・クォートを忘れずに。
こうしたうえで、メインの Perl スクリプトと同じディレクトリに、任意のライブラリフォルダを置き、それを Perl スクリプト内部から指定するようにする。つまり、プログラムの冒頭では、次のように宣言する。
use lib qw(./Library);
もしくは、 perl コマンドにオプション -I でライブラリのパスを渡すような方法でもいいかもしれない。
ともかく、実行される Perl スクリプトは、具体的なディレクトリがどこだかには頓着はないけれど、カレントディレクトリに居る事だけは知っている。そういう作りがこの場合は求められる。
Add to Project
以上の事を踏まえた Perl スクリプトが完成したら、それを Xcode の、プロジェクトに組み込む事になる。
組み込むときには──ここから本題なのでスクリーンショットで示していこう。
具体的な例を示しながら説明した方が説明もしやすいので、以下は WhereIsPhoto というプロジェクトを作成しているときの風景を見ながら、その流れを順番に説明して行く事にする。
まず、この例のプロジェクトのプロジェクト・フォルダはある任意のフォルダの中の、 "1.02" というフォルダになっている(名前はなんでもいいけれど、この例では "1.02" 。ちなみにこれはバージョン番号にあたる)。以下の説明の中では気にする事はないのだけれども、スクリーンショットを見る中で、この名前のフォルダが写っていたときに、 "1.02" だなんてなんだろう、と後で思っても、これは名前にすぎない、ということを註釈する。ただそれだけで、ほかに意味はない。
それから、材料となる、これから GUI を付けようとしている Perl スクリプトは where_is_photo.pl という名前で、それが参照する Perl パッケージのライブラリが Library フォルダにしてまとめられている。そしてこれらを、 AppleScript Studio で作成中の AppleScript アプリケーションの中に組み込もう、というのが、ここでの内容になる。
1. プロジェクトにメインの Perl スクリプトを追加する

Xcode のメニュー "Project" から "Add to Project... " を選ぶ。 AppleScript アプリケーションのプロジェクトに、 Perl を参加させるのだ。

ファイル選択ダイアログが開くので、作成したメインの Perl スクリプトを選択する。

すると、続けてなにか設定画面がでる。 "Copy items into destination groups's folder (if needed)" にチェックした。たぶん、プロジェクトには追加するもとのファイルのコピーを追加するのだろう。たぶん。
すべてを Xcode の中でまとめて持っておきたい場合は(おそらくふつうはそうだろう)、コピーしなくてもいいかもしれないけれど Xcode の中のエディタはなんだか落ち着かなくて、日本語を扱う時? 不可解な現象が起こってファイルが壊れる(文字が化けて治らない)事があったので、バックアップの意味も含めて、追加するものはコピーのほうがいいかもしれない。
それから "Recursively create groups for any added folders" と "Create Folder References for any added folders" とラベルのついたラジオボタン。どちらも追加しようとしているもののフォルダについて言及している様子なので、いま追加したそれにはあまり関係がない? とはいうものの、プロジェクトの中では、グループとフォルダという概念があるようで、それが肝要な要素になっているらしく、ここではグループの方を選択する意味でとりあえず Recursively... のほうを選択させている。でも、どっちでもいいかもしれない。

追加されると、左のリスト Group & Files に、図のように追加されている様子が確認できる。 Resouces グループ内に入っている。

追加された Perl スクリプトの情報を見てみる。先ほど指定した値が反映されているかチェックする。
Path Type が "Relative to Enclosing Group" になっているところがポイントで、 Path もパスのついていないファイル名になっている。
このことで、このプロジェクトを Build したとき、 AppleScript とこの Perl スクリプトは、じぶんが何処に居るか、そのフォルダ位置を Build したアプリケーション自身から得る事になる(のだと思う)。理屈は想像だけれど、こうするとうまくいったという結果から、そう思われる。ので、そうしておこう。

実際のファイルは、このように、プロジェクト・フォルダにぺろんと置かれていた。もちろん、ただファイルをここに置いただけではだめで、 Add to Project で上記のとおりの手順を踏まないといけない。
2. プロジェクトに Perl ライブラリを追加する

つぎに、プロジェクトに追加した Perl スクリプトが要求する、 Perl ライブラリを追加する。
Perl ライブラリ、これは通常はフォルダに纏まっていて、かつ、その階層構造にも意味がある。なので、それに注意しておきたい。
とりあえずは、メニューからふたたび、 "Add to Project..." 。

フォルダを選択する。

そしてふたたび、さきほど説明があやうかった、この画面。 Add to Project しようとしているそれを、どのような形で追加するかを指示する。
フォルダ階層が重要な意味をもつため、今度はどっちでもいいとはいえなくて、 "Create Folder References..." のほうのラジオボタンを選択しなければいけない。

そうしてプロジェクトに追加されたフォルダは、左のリスト Group & Files の、やはり Resouces の中にぶらさがる。このとき、フォルダのアイコンの色が、 Resources などのそれと異なっている(青になっている)ことに注目。それは、今後がうまくいくことのサインだ。

追加されたフォルダの情報を見ると、先ほどと同様に、 "Relative to Enclosing Group" になっている。
3. Build する

以上で追加はできた。これを Build して、どうなっているかを確かめよう。
ちなみに Build して起動したアプリケーションはこんな画面( Figure 12 )。各フォームに値を入れて、 Execute ボタンを押すと、そのイベントで do shell script 経由で Perl スクリプトを実行する。各フォームの値はすべて、コマンドライン・オプションに渡す値の素になっていて、 Execute したときに読み取られ、 do shell script に渡す文字列、つまり Perl コマンドのオプションを構成する要素になる。

問題なく Build が完了したアプリケーションは、プロジェクト・フォルダの中の build フォルダ内にある。この中を、覗いてみよう。アプリケーションを選択してそのコンテクスト・メニューからアクセスできる。

アプリケーション・パッケージの中では、 Resouces フォルダの中に、プロジェクトのリソース(部品)が収められている。
プロジェクトに追加したリソース、 Perl スクリプトとそのライブラリは、このように収まった。ちゃんとフォルダ階層も保たれている。
4. テストする

ためしに実行してみる。
Build したアプリケーションの GUI には Execute ボタンがついていて、これをクリックするとイベントが発生して、そのイベントに対応させた AppleScript のコードが走り始める。そしてその中で do shell script が実行される。
そして do shell script を実行するとき、わざわざカレント・ディレクトリを移動してその位置から Perl スクリプトを実行するようにしたのは、このアプリケーションが、その使用者によって何処に置かれるものか、それを特定させる事ができないからにほかならない。なので、プロジェクトを作成する時に、プロジェクトに追加した Perl スクリプトなどが、アプリケーションの位置から相対的な位置を基点とするようにと意識しながら、セッティングしていくことがポイントとなる。
したがって、ここでテストするときも、その動作に従って、まずこのディレクトリに移動してから、 Perl スクリプトをパスなしで実行するようにする。
さて、いざ実行してみたらば──アラートがでてしまった。こういう実行時の問題は、アプリケーションのほうから実行した時には気がつけないので、あらかじめのテストが、とても大事なのだ(いうまでもないけれども)。
感想
以上でいちおうひととおりなのだけれども、Xcode を使えば、これくらいのことが、わりと簡単にできる。ただ、 GUI のツールはコマンドラインとは違った気の配り方をしないといけないので、簡単に作れるとはいえ、たいへんはたいへんだ。
たとえばこの例でいえば、 Perl スクリプトの進捗具合などがテキストでも表示できればいいのだけれども、先にも述べたようにじぶんの手際ではかなわない(そもそも、できるのかな?)。でも GUI のアプリケーションでは、アクションに対する反応はけっこう大事なことにちがいない。なので Execute ボタンを押したら、プログレス・スピンをくるくるさせるように、がんばってみたりした。そして、その、ほんの小さな部分かもしれないけれど、これが回りだしたら、なんとなくでも Mac 用アプリを作っているのだなという実感が持てて、ちょっと嬉しかったりした。
それに味をしめたら、じぶんは Objective-C をいじりはじめる事になるんだろうか。 Hello World から。でもまあ、とりあえず Perl で。中身は Perl でも、こんなこともできる、ということで。
(「画像を地図で開く」と「WhereIsPhoto」につづく)