まずは、Excellent!! と言いたい。
「文字列で指定したハンドラを実行する」を書いたところ、ppmweb さんと Script factry さんから有益な情報とスクリプトを送って頂きました。
正直なところ、私の方法は無意味に osacompile を使っていたり、その上 run script 命令まで使ってなんだかな、と言った趣きがありました。その後、Script factry さんがシンプルな方法を発表されていて、実用として使うならこちらかなと思っていたのですが、ppmweb さんにご教授いただいた方法もまた、シンプルなのでした。
この方法の出典は、AppleScript-Users ML です。
ppmweb さんは、この方法でレコードのラベル、スクリプトオブジェクト内のハンドラ、スクリプトオブジェクトのプロパティも文字列で取得可能な事を調べてくださり、また、load script 命令で読み込んだスクリプトのハンドラなども文字列で取得可能だという事を証明して下さいました。まさに、Great, Excellent, Beautiful and Lovely!!
今宵は気分良く酔えそうです。送って頂いたスクリプトは 2 つあって、一方がメインとなるスクリプトで、他方がメインから読み込まれるスクリプトになっています。まず、読み込まれる方のスクリプト。
on externalShow(theText)
display dialog theText
end externalShow
このスクリプトを externalTextShower.scpt として保存しておきます。メインとなる方のスクリプトは以下。
(* ==== Demo ==== *)
-- Demo 1
callHandler(me, "showText", {"I'm a top level handler."})
on showText(theText)
display dialog theText
end showText
-- Demo 2
callHandler(TextShower, "show", {"I'm a handler of the script object."})
script TextShower
on show(theText)
display dialog theText
end show
end script
-- Demo 3
-- 以下のコードの実行には externalTextShower.scpt が必要
(*set thePath to join(":", items 1 thru -2 of split(":", path to me as text)) & ":externalTextShower.scpt"
set externalTextShower to load script file thePath
callHandler(externalTextShower, "externalShow", {"I'm a handler of the loaded script file."})*)
(* ==== Call Handler Core ==== *)
on callHandler(targetScript, handlerName, handlerParams)
set handlerParamLabels to {}
set numberOfHandlerParams to count handlerParams
if numberOfHandlerParams > 0 then
repeat with i from 1 to numberOfHandlerParams
set the end of handlerParamLabels to "handlerParam" & (i as text)
end repeat
end if
set runCommandParamLabels to {"targetScript", "theHandler"} & handlerParamLabels
set runCommandParams to {targetScript, extract_usrf(targetScript, handlerName)} & handlerParams
run script "on run{" & join(",", runCommandParamLabels) & "}
tell targetScript to " & handlerName & "(" & join(",", handlerParamLabels) & ")
end" with parameters runCommandParams
end callHandler
-- Taken from <http://lists.apple.com/archives/applescript-users/2006/Sep/msg00446.html>.
-- Usage Examples:
-- extract_usrf({label1:"value1", label2:"value2"}, "label1") -- "value1"
-- extract_usrf(ScriptObjectA, "propertyName") -- "value of ScriptObjectA's property propertyName"
-- extract_usrf(me, "handlerName") -- «handler handlerName»
to extract_usrf(theRecord, fieldName)
run script "on run{r}
return " & fieldName & " of r
end" with parameters {theRecord}
end extract_usrf
(* ==== Text Manupilation ==== *)
on split(separator, sourceText)
tell AppleScript
set oldDelimiters to text item delimiters
set text item delimiters to {separator}
end tell
set listOfText to text items of sourceText
tell AppleScript to set text item delimiters to oldDelimiters
return listOfText
end split
on join(separator, listOfText)
tell AppleScript
set oldDelimiters to text item delimiters
set text item delimiters to {separator}
end tell
set theText to listOfText as text
tell AppleScript to set text item delimiters to oldDelimiters
return theText
end join
こちらも名前を付け、先の読み込まれるスクリプトと同階層の場所に保存しておきます(スクリプト内のコメントにある Demo 3 でスクリプトを読み込んで実行するのですが、このとき同一階層からスクリプトファイルを指定しているので)。
では、Great で Excellent で Beautiful で Lovely なこのスクリプトがなぜ動くのかを推論とともにお送りしたいと思います(スクリプトは分かりやすいように単純化しています)。
script MyObject
on x()
display dialog "MyObject"
end x
end script
run script "on run {target}
tell target to x()
end run" with parameters {MyObject}
run script "on run {target}
tell target to x()
end run" with parameters {me}
on x()
display dialog "me"
end x
with parameters とは気がつかなかった(馬鹿をさらけだすようですが、本当に気がつかなかった)。
「文字列で指定したハンドラを実行する」で指摘したと思うのですが、run script 命令は新しいスクリプトオブジェクトを生成します(生成する...これは勝手な推論で根拠はありません)。現在実行されているスクリプトとは別の新しいスクリプトオブジェクトなので、以下のスクリプトは動かないのでした。
set x to 10
run script "x"
--> x変数は定義されていません。
定義されていないなら定義されているオブジェクトを渡せばいいのです。もし、新しいスクリプトオブジェクトが生成されるのならば、そのスクリプトオブジェクトの run ハンドラの引数に渡せばいいのです(run script 命令は run ハンドラを実行するから)。このシンプルな発想。
- 文字列で run ハンドラとその引数を作る
- run script 命令に文字列を渡す
- run script 命令は文字列をコンパイルする
- コンパイルした時点では構文エラーにならない
- run script 命令がコンパイルされたスクリプト(スクリプトオブジェクト)の run ハンドラを実行
- run ハンドラの引数が実際のオブジェクトの参照に置き換わる
- MISSION COMPLETE
4 の時点で構文エラーにならないのは以下のスクリプトがエラーにならないのと同じ理由です。
on run {target}
tell target to x()
end run
ようするにこのスクリプトを保存し、run script 命令で保存したファイルを引数を指定して実行しているのと同じことなのです。
ppmweb さん、本当にありがとうございました。
そして、Script factory さんからはいくつかの間違いを指摘いただきました。
script Obje end script on x(num) return num end x --set x of Obje to x tell Obje to x(10)
スクリプトオブジェクトにハンドラをインストールしている部分をコメントアウトしました。それでもこのスクリプトは動きます。つまり、同一スクリプト内でのハンドラの有効範囲(可視性、スコープ)です。以下のようにしても動く事からハンドラは同一スクリプト内にある限り、ほとんどの場所から参照できるのです。
ちゃらんぽらん : 文字列で指定したハンドラを実行する
まず、このスクリプトが動くのがなぜか?
引用にもあるようにスコープが原因だと書いていますが、スクリプトオブジェクト Obje が上位のスクリプトを継承しているから、という事のようでした。以下のスクリプトを動かしてみると分かります。
script Obje
end script
parent of Obje
--> «script»
name of parent of Obje
--> "Script Editor"
ほんとだ。継承している...。だから動いたのか、と納得。って、AppleScript Language Guide に書かれていたような(うろ覚え)。そうか、継承か。
次の指摘。
読み込まれた方でハンドラが共有されます(されてしまいます)。この事を利用したのが最初に紹介したスクリプトです(蛇足ですが実際はハンドラだけでなく変数でもスクリプトオブジェクトでも文字列で指定する事ができます)。
ちゃらんぽらん : 文字列で指定したハンドラを実行する
ハンドラが共有されると書きましたが、これも間違い。
osacompile で変数名だけのファイルを書き出した場合、グローバル変数だけを持ったスクリプトを作っているのと同じになり、load script したとき、トップレベルのスクリプトのプロパティと値が共有されるから動くのだそうです。この辺りの事は、Script factory さんの侵略の global 変数 に詳しいです。参考までにいくつかのスクリプトを。
set num to 10
x()
on x()
set num to 20
end x
num
--> 10
次にハンドラ内でグローバル変数を定義。
set num to 10
x()
on x()
global num
set num to 20
end x
num
--> 20
num 変数は共有されます。自分自身を表す my を使うと、global を利用しなくても変更できます。
set num to 10
x()
on x()
set my num to 20
end x
num
--> 20
しかし、num 変数をローカル変数にすると動きません。
local num
set num to 10
x()
on x()
set my num to 20
--> num のタイプを reference に変換できません。
end x
num
--> 10
そして、最後の指摘。handler はローカル変数におさめる事はできなくて、property にならおさめる事ができる。だから、以下のスクリプトは動かないのでした。
x()
on x()
set theResult to handlerWithName("greeting")
theResult("Mac")
--> «script» は theResult メッセージを認識できません。
end x
on getNum()
return 81
end getNum
on greeting(yourName)
display dialog "Hello, " & yourName
end greeting
on handlerWithName(str)
set dir to path to temporary items folder from user domain as Unicode text
set fileName to "tmp.scpt"
set scptFile to dir & fileName
set posixFile to POSIX path of scptFile
set str to quoted form of str
do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
set theReult to run (load script file scptFile)
end handlerWithName
...まだまだ分からない事も多いけど、納得。
ppmweb さん。Script factory さん。本当にありがとうございました。「ちゃらんぽらん」を続けていてよかったよ。また、このサイトをご覧になっている皆様もありがとうございます。「ちゃらんぽらん」はあなたに支えられています。
えっと...また、引用。
素敵な回答をいただいた方には、感謝の気持ちを込めて個人的にほっぺにチュッてします。おでこでも可。
ちゃらんぽらん : 文字列で指定したハンドラを実行する
ほっぺにチュッ。
おでこでも可。
えっと...。
どういたしましょう?