load script 命令はもしかすると難しいものなのかもしれません。なぜかというと、スクリプトオブジェクトの存在が理解を阻むからです。しかし、load script 命令はスクリプトオブジェクトを返します。「load script 命令におけるスクリプトの再利用」では触れませんでしたが、今回はその辺りのことを。
AppleScript ではオブジェクトが全てです。値という言葉を使うことはありますが、「値 = オブジェクト」とほぼ同意です。AppleScript はオブジェクト単位で考えると理解しやすいかもしれません。
もちろん、スクリプトファイルに保存されたスクリプトもオブジェクトです。
set documents_folder to path to documents folder
tell application "Finder"
activate
set the_window to make new Finder window
set target of the_window to documents_folder
end tell
このようなスクリプトでもオブジェクトです。AppleScript では Java や Objective-C、Python、PHP などの他のプログラム言語と異なり「クラス」を作りません。AppleScript でクラスを定義できるのは、操作対象となるアプリケーションや AppleScript だけです。AppleScript でユーザーが作ることができるのは、「オブジェクト」だけです。
ユーザーが定義したオブジェクトのことを「スクリプトオブジェクト(Script Object)」といいます。スクリプトオブジェクトはデータ(属性)とアクション(メソッド。AppleScript ではハンドラ)を持つことができます。
通常、スクリプトオブジェクトは予約語 script を用いて定義します。
script valiable_name
[ property parent: reference_of_parent ]
[ property property_label: initial_value ]...
[ handler_definition ]...
[ statement ]...
end script
script から end script の間までがスクリプトオブジェクトの定義になります。valiablename はスクリプトオブジェクトの名前になります。parent 属性で指定する referenceof_parent は、スクリプトオブジェクトの親の参照です。他のスクリプトオブジェクトから属性やハンドラを継承したいときに利用します。
propertylabel は 属性の識別子で、initialvalue に初期値を記述します。handlerdefinition はハンドラ(アクション)の定義で、statement は run ハンドラに含まれるものになります。valiablename 以外は全て任意で、あってもなくてもかまいません。
だから、一番シンプルなスクリプトオブジェクトは、valiable_name だけを指定したものになります。
script ScriptObject
end script
しかし、このままでは何の役にも立ちません。他の言語のように後から属性を追加してデータを加えるということはできません。通常は属性とハンドラを定義し、それなりの体裁を整えておきます。
script FirstScriptObject -- スクリプトオブジェクトの名前
-- スクリプトオブジェクトの属性。初期値は空のリスト
property the_list : {}
on add_data(x) -- スクリプトオブジェクトのハンドラ
set end of the_list to x
end add_data
-- スクリプトオブジェクトの任意の文(暗黙の run ハンドラに含まれる)
add_data(10)
add_data(20)
add_data(30)
the_list
end script
run FirstScriptObject
--> {10, 20, 30}
AppleScript では、トップレベルのスクリプトオブジェクト(Top-Level Script Object)から処理が開始されます。どのスクリプトにもトップレベルのスクリプトオブジェクトというものが存在していますが、それはユーザーの目には触れません。
トップレベルのスクリプトオブジェクトは、ユーザーが定義するものではありません。AppleScript のスクリプトに既に存在しているものです。トップレベルのスクリプトオブジェクトは決して明示されませんが、全てのスクリプトはトップレベルのスクリプトオブジェクトに含まれたものになります。
set documents_folder to path to documents folder
tell application "Finder"
activate
set the_window to make new Finder window
set target of the_window to documents_folder
end tell
このスクリプトならトップレベルのスクリプトオブジェクトに属する run ハンドラの中のにあるスクリプトになります。言い換えるなら、ユーザーは意識していなくてもスクリプトオブジェクトを作成しているのです。上記のスクリプトは分かりやすく書くと次のようになっています。
(* script *) -- トップレベルのスクリプトオブジェクト
(* on run *) -- トップレベルのスクリプトオブジェクトの run ハンドラ
set documents_folder to path to documents folder
tell application "Finder"
activate
set the_window to make new Finder window
set target of the_window to documents_folder
end tell
(* end run *)
(* end script *)
擬似的なスクリプトですが、先のスクリプトはコメントアウトしている部分が暗黙のうちに追加されているのです。
トップレベルのスクリプトオブジェクトは普段は意識しないので感覚がつかみにくいかもしれませんが、トップレベルのスクリプトオブジェクトは全てのスクリプトオブジェクトの親になります(AppleScript では継承の関係を「親」と「子」で表現します)。また、トップレベルのスクリプトオブジェクトも親を持っています。
script FirstObject
end script
me -- トップレベルのスクリプトオブジェクト
--> «script»
-- FirstObject の親は?
parent of FirstObject
--> «script», トップレベルのスクリプトオブジェクト
-- トップレベルのスクリプトオブジェクトの親は?
set parent_object to parent of me
--> «script AppleScript», AppleScript 自身(AppleScript Component )
--> AppleScript の親は?
parent of parent_object
--> current application, スクリプトを実行しているアプリケーション
current application(スクリプトを実行しているアプリケーション)が最上位の親になります。AppleScript では下図のような継承関係が定義されています。
AppleScript でなんらかの命令をオブジェクトに送るとき、通常は「ターゲット(命令を処理するオブジェクト)」に対して命令が送られます。ターゲットで命令を処理できない(命令が定義されていない)場合、この継承関係をたどりながら命令が定義されているオブジェクトを探索します。
次のスクリプトがエラーにならないのは、最終的に Script Editor に問い合わせを行っているからです。
-- トップレベルのスクリプトオブジェクトで定義された x ハンドラ
on x()
-- 2. document という用語はスクリプトオブジェクトで定義されていない
-- 3. AppleScript Component を探すが、見つからない
-- 4. current application(Script Editor)で探し、見つかる
get name of front document
end x
script FirstObject
on run
-- ハンドラ x は、ThirdObject では定義されていない
-- 1. トップレベルのスクリプトオブジェクトを探しにいく
x()
end run
end script
run FirstObject
-- 5. "名称未設定" という結果が得られる
Script Editor 上でスクリプトを実行しているとき、current application(スクリプトを実行しているアプリケーション)は Script Editor になります。Script Editor では document という用語が定義されています。結果、スクリプトが記述されているドキュメントの名称が返されるのです。ちなみにこのスクリプトをアプリケーションとして保存し、実行するとエラーになります。
ユーザーが定義したスクリプトオブジェクトは、他のスクリプトオブジェクトを親に指定し、親の持っている属性やハンドラを継承することができます。親が指定されていない場合、先にも書いたようにトップレベルのスクリプトオブジェクトが自動的に親になります。
script FirstObject
on greeting(your_name)
log "FirstObject's greeting"
return "Hello, " & your_name
end greeting
end script
script SecondObject
property parent : FirstObject -- 親を指定
end script
greeting("Mac OS X") of FirstObject
--> "Hello, Mac OS X"
greeting("Mac OS 9") of SecondObject -- 親のハンドラを利用
--> "Hello, Mac OS 9"
FirstObject で greeting を定義せず、トップレベルのスクリプトオブジェクトで定義してみましょう。
on greeting(your_name)
log "Top-level Script Object's greeting"
display dialog "Hello, " & your_name
end greeting
script FirstObject
-- greeting は定義されていないのでトップレベルのスクリプトオブジェクトを探索
end script
script SecondObject
property parent : FirstObject -- 親を指定
end script
greeting("Mac OS X") of FirstObject
--> "Hello, Mac OS X"
greeting("Mac OS 9") of SecondObject -- 親のハンドラを利用
--> "Hello, Mac OS 9"
parent で親を指定しないスクリプトオブジェクトでは自身にハンドラが定義されていない時、トップレベルのスクリプトオブジェクトで定義されているかどうかを調べます。トップレベルのスクリプトオブジェクトで定義されていない場合、AppleScript Component で定義されていないかどうか調べます。AppleScript Component でも定義されていない場合、current application で定義されているかどうか調べます。current application でも定義されていない場合、エラーになります。
スクリプトオブジェクトで親を指定している場合、親のスクリプトオブジェクトを経由しながらハンドラを探索しますが、最終的には current application まで辿っていきます。
このように全てのスクリプトオブジェクトがトップレベルのスクリプトオブジェクトを経由するということが分かっているなら、エラーなどをトップレベルのスクリプトオブジェクトに集約することができます。
on _error(m, n)
display dialog {n, return, return, m} as text
end _error
on run
try
-- 何らかの処理
error number 0 -- わざとエラーを起こしてみる
on error m number n
_error("トップレベルのスクリプトオブジェクトでエラー発生", n)
run FirstObject
x()
end try
end run
script FirstObject
script SecondObject
on run
try
-- 何らかの処理
error number 2 -- わざとエラーを起こしてみる
on error m number n
_error("SecondObject でエラー発生", n)
end try
end run
end script
on run
try
-- 何らかの処理
error number 1 -- わざとエラーを起こしてみる
on error m number n
_error("FirstObject でエラー発生", n)
run SecondObject
end try
end run
end script
on x()
try
-- 何らかの処理
error number 3 -- わざとエラーを起こしてみる
on error m number n
_error("x ハンドラでエラー発生", n)
end try
end x
まぁ、あまり利用しないテクニックですが。
スクリプトオブジェクトで親を指定するときに大事なのは、それが誰のものかを明示することです。
script FileFilter
property file_extensions : {}
on filter(this_item)
tell application "Finder"
if (class of this_item) is not document file then return false
-- of me(もしくは my)で誰の file_extensions か明示している
-- of me(もしくは my)がないとこの判定は失敗する
return name extension of this_item is in file_extensions of me
end tell
end filter
end script
script ImageFileFilter
property parent : FileFilter
property file_extensions : {"jpg", "jpeg", "png", "gif", "pict", "tiff", "tif"}
end script
tell application "Finder"
set selected_items to selection
if selected_items is {} then return
set image_files to {}
repeat with this_item in selected_items
if ImageFileFilter's filter(this_item) then
set end of image_files to this_item as alias
end if
end repeat
image_files
end tell
AppleScript には me(もしくは my)や it といったオブジェクトを指し示す予約語があります。これらの違いが分からない、と時々耳にします。me は「現在実行されているスクリプトオブジェクト」を指し、it は「現在のターゲット」を指します。
set the_list to {10, 20, 30}
tell the_list
it -- {10, 20, 30}
me -- «script»
class of it -- list
class of me -- script
-- 命令は現在のターゲットに送られる
count -- 3
end tell
it -- «script»
me -- «script»
tell application "Finder"
it -- application "Finder"
me -- «script»
end tell
script FirstObject
me
end script
script SecondObject
property parent : FirstObject
end script
run FirstObject
--> «script FirstObject»
run SecondObject
--> «script SecondObject»
それぞれ、文脈によって何を指し示しているかが変わってきます。
親を指定したスクリプトオブジェクトでは親と同じハンドラを持つことができます。
script DebugLog
on debug_message(msg)
tell me
activate
display dialog msg buttons {"OK"} default button 1 with icon 1
end tell
end debug_message
end script
script CustomDebugLog
property parent : DebugLog
on debug_message(msg)
set msg to ((current date) as text) & ": " & msg
log (msg)
end debug_message
end script
DebugLog's debug_message("DebugLog's logging")
CustomDebugLog's debug_message("CustomDebugLog's logging")
DebugLog ではダイアログでメッセージを表示しますが、子(CustomDebugLog)の方は現在の日時を追加し、Script Editor のイベントログに書き出すようにしています。親の(DebugLog)が持っているハンドラを上書き(オーバーライド)して、機能を修正/拡張したのです。
子は親の機能を拡張することができますが、continue を使って処理をそのまま親に任せてしまうこともできます(委譲といいます)。
script DebugLog
on debug_message(msg)
tell me
activate
display dialog msg buttons {"OK"} default button 1 with icon 1
end tell
end debug_message
end script
script CustomDebugLog
property parent : DebugLog
on debug_message(msg)
set msg to ((current date) as text) & return & return & msg
continue debug_message(msg) -- 親に処理を丸投げ
end debug_message
end script
DebugLog's debug_message("DebugLog's logging")
CustomDebugLog's debug_message("CustomDebugLog's logging")
AppleScript ではハンドラの上書きというより、横取りといった方があっていると思うのですが...。例えば、警告音を鳴らす beep 命令をカスタマイズ(横取り)することができます。
on beep
log "beep"
end beep
beep
このスクリプトを実行しても警告音は鳴りません。警告音を鳴らすには continue で処理を委譲する必要があります。横取りしたものを元に返すのです。
on beep
log "beep"
continue beep
end beep
beep
quit ハンドラで continue quit
とするのは終了命令を横取りしたままになり、終了しないアプリケーションになるのでアプリケーションに終了命令を返しているのです。
on quit -- 終了命令を横取り
-- アプリケーションに終了命令を渡さないと終了しないアプリケーションになる
end quit
このスクリプトを「実行後、自動的に終了しない」アプリケーションで保存して、実行すると終了させても終了できないアプリケーションになります。quit ハンドラがあるときは次のように記述しておきます。
on quit -- 終了命令を横取り
-- アプリケーションに終了命令を渡さないと終了しないアプリケーションになる
(* 終了前の処理が入る *)
continue quit
end quit
スクリプトオブジェクトはスクリプト実行時に初期化され、作成されます。トップレベルのスクリプトオブジェクトは run ハンドラ実行時に初期化されます。スクリプトオブジェクトの初期化のタイミングは命令を受け取る直前です。この性質を利用すると異なる初期値を持つスクリプトオブジェクトを作成することができます。
on FileFilter(extension_list)
script FileFilter
property file_extensions : extension_list
on filter(this_item)
return name extension of this_item is in file_extensions of me
end filter
end script
end FileFilter
set ImageFileFilter to FileFilter({"jpg", "jpeg", "png", "gif", "pict", "tiff", "tif"})
tell application "Finder"
set selected_items to selection
if selected_items is {} then return
set image_files to {}
repeat with this_item in selected_items
if (class of this_item) is document file then
if ImageFileFilter's filter(this_item) then
set end of image_files to this_item as alias
end if
end if
end repeat
image_files
end tell
このようにして作成されたスクリプトオブジェクトは、たとえ初期値が同じものであっても異なるスクリプトオブジェクトになります。
on objectMaker()
script
end script
end objectMaker
set obje_A to objectMaker()
set obje_B to objectMaker()
obje_A is obje_B
--> false
ちなみに set 命令ではオブジェクトは共有されます。
set the_list to {1, 2, 3}
set new_list to the_list
set end of new_list to 4
the_list
--> {1, 2, 3, 4}
これは対象がスクリプトオブジェクトでも同じで、オブジェクトのコピーが欲しい場合は copy 命令を使います。
on objectMaker()
script
end script
end objectMaker
set obje_A to objectMaker()
-- set ではオブジェクトは共有される
set obje_B to obje_A
obje_A is obje_B
--> true
-- 複製が欲しいときは copy 命令を使う
copy obje_A to obje_B
obje_A is obje_B
--> false
駆け足ですが、AppleScript やスクリプトオブジェクトがどのような性質を持っているか理解できた...でしょうか?
できねえよ。
...分かっています。全ての責は私の理解不足/文章力足らずに帰します。分からない、理解できない、もっとここを説明してほしい...という部分があればコメント(設置してみました)からお願いします。