JavaScript for Automation (JXA)

 Mac OS Xの自働化用の言語としてAppleScriptがありますが、Mac OS X10.10(Yosemite)から「OS標準で」JavaScriptでも制御ができるようになりました。

 これを、JavaScript for Automation (以後 JXA)と言います。
 詳細はJavaScript for Automation Release Notesを見てもらうとして、ここでは、ごく基本的な情報をまとめます。

とりあえずJavaScript動かしてみる

 JXAの記述には"アプリケーション/ユーティリティ"フォルダのスクリプトエディタを使う。
 ScriptEditorスクリプトエディタ

 スクリプトエディタの[環境設定]-[一般]で、[デフォルトの言語]をJavaScript(1.0)に。

 Mathクラス(注1)を使ってみる。上記のように書いて、メニューの[スクリプト]-[実行]か、[▶]︎ボタンをクリック(注2)。
 ウインドウの下の[結果]に、最後の行の値が出力される。

 ウインドウの左上あたりに[JavaScript]って出てるが、もし出てなかったらメニューの[表示]-[ナビゲーションバーを表示]で表示。
 ここのプルダウンで、AppleScriptとJavaScripとの切り替えができる。

 実行は頻繁に行うので、ショートカットキーのcommand+Rを覚えておくといいだろう。
 なお、コード補完機能はF5キー。コードの構文確認はcommand+Kだ。

アプリケーション(Applicationオブジェクト)

 アプリケーションの操作を行うには、Applicationオブジェクト(コンストラクタ)にアプリケーション名(注1)を与えて生成する。
 あとはこの変数に対してアクションを行う(注2)。

app = Application( "TextEdit" )

 それぞれのアプリケーションにどのような、要素(エレメント)命令(メソッド)および属性(プロパティ)が用意されているかは、用語説明を見る。
 メニューの[ウインドウ]-[ライブラリ]で一覧が表示されるので、ここから用語説明を確認できる(注3)。

命令(メソッド)

 引数なしの命令(メソッド)の記述方法は、特に注意は必要無い。
 対象となるオブジェクト(注1)に続けて「.」区切りで続け()を書くというお馴染みの方法が使える。

app.activate()  // 最前面に(注2)

 引数が複数ある場合、JXAはラベル付き引数の形を取る。
 引数をオブジェクトの形で渡す。個々のラベルについては用語説明を参照。

response = message.reply({
    replayAll: true,
    openingWindow: false
})

 ラベルなしと、ラベル付きの引数を並べる際は、順に書く。

Safari.doJavaScript('alert("Hello world")', {
    in: Safari.windows[0].tabs[0]
})

属性(プロパティ)

 属性(プロパティ)を読む場合、最後に()をつけて関数化するという仕様(注1)。
 JXAでアプリケーションを操作するスクリプトを書く際は、ここに注意!!

app.name()

 属性に代入する際に()は不要(注2)。

app.name = "新しい名前" // (注3)

要素(エレメント)

 要素の「複数形」を名前としたObjectプロパティが存在する。
 番号で指定する場合は次の通り(注1)。

app.windows[ 0 ]

 名前で指定する場合は次の通り(注2)

app.windows[ "仮タイトル" ]

 他に、ID指定 (byId())や、whoseを利用したフィルタ指定が使える。
 フィルタ指定の詳細は、JavaScript for Automation Release Notesを参照。

 JXAでも、JavaScriptの慣例通り、「.」区切りで要素を並べて深い階層の要素を指定できる。
 もちろん、そのパスを変数に代入して使ったり、続けて命令や属性を指定したりもできる。

 頭が大文字で単数形の要素名を使うとオブジェクトを生成できる。これはApplicationとやり方は同じ。

doc = app.Document()

 この状態ではアプリケーションに表示されないので、push命令で「複数形の要素名」に追加する(注3)。

app.documents.push( doc )

 生成時に初期値を渡すこともできるし、後で属性を書き換えてもいい。

doc = app.Document({name:"新規",text:"内容"})

機能拡張

 JXA本体及び、アプリケーション操作以外でも、多くの機能拡張方法が用意されている。

ライブラリ(Libraryオブジェクト)

 "~/Library/Script Libraries/"にスクリプトを保存しておくと、Libraryオブジェクトを使ってスクリプトを呼び出すことができる。
 次のように書けば、toolbox.scptファイルに記述したlog関数(ファンクション)を呼び出せる。

toolbox = Library('toolbox')
toolbox.log('Hello world')

標準機能拡張(StandardAdditions.osax)

 JXAには、StandardAdditions.osaxという命令の集合体が標準で用意されている。ライブラリパネルにあるので、詳細は用語説明を参照。
 これらの命令を使うにはアプリケーションの属性includeStandardAdditionsをtrueにすればいい。
 デフォルトはfalseなので、StandardAdditionsを使う前に、必ずtrueにすること。

app = Application("com.apple.finder")
app.includeStandardAdditions = true
app.activate()
app.displayDialog( "ハローワールド" )

 ここでは、displayDialog()(注1)が、StandardAdditionsに含まれる命令。includeStandardAdditions = trueを忘れているとエラーとなる。

スクリプト専用アプリケーション(System Events.app)

 他に、幾つかの「起動しても画面に表示されず、スクリプトから操作するためにあるアプリケーション」が存在する。
 特に、System Eventsに多くの機能が集中しており、スクリプトで操作できない場合に人の入力を真似るGUI Scriptingなど有用な機能が含まれる。
 次のスクリプトは、キーボードショートカットをスクリプトから発生させて、アプリケーションの操作をしている。

Application("com.apple.textedit").activate()
delay( 1 ) // ちょっと待つ(注2)
app = Application( "System Events" )
app.keystroke( "a",{ using: "command down"} )

コマンドラインシェル(doShellScript)

 doShellScript()を使えば、shシェルのShellスクリプトが使えるので、コマンドラインでできることは、ほぼなんでもできる(注3)。
 Mac OS XにはPerlやRubyなどの多くの言語が入っているので、それらの機能も使えるということだ。

app = Application.currentApplication()
app.includeStandardAdditions = true
app.doShellScript( "whoami" )

API呼び出し(ObjCオブジェクト)

 さらにJXAはObjCオブジェクトを使って、Mac OS XのAPIにアクセスできる。
 こうなると本当になんでもありだが、凝ったものを作りたい場合は、通常のアプリケーション開発環境であるXcodeを使ったほうが楽だ。
 せいぜい数個のAPIを呼び出すと「楽ができる」という場合に使うのが良いだろう。

ObjC.import('Cocoa')
$.NSBeep()

 Cocoa API以外にも、stdioとかの低レベルなライブラリもインポートできて驚く(注4)。
 他に、Refオブジェクト、Automationオブジェクトなども用意されている。詳細はJavaScript for Automation Release Notesを。

ファイルパス(Pathオブジェクト)

 JXAはファイルのパスを文字列として直接扱わず、Pathオブジェクトとして扱う。
 PathオブジェクトはPOSIXパス(注1)を渡してやると生成され、それを各アプリケーションの命令に渡してやれば動作する。

path = Path( "/User/test.txt" )
Application( "Finder" ).open( path )

 もちろん、open()はアプリケーションによって動作は異なる。
 ここでは、Finderに渡しているので、ファイルのダブルクリックに相当する動作を行う。

 主なファイルパスは、標準機能拡張のpathTo()によって取り出せる。

app = Application( "Finder" )
app.includeStandardAdditions = true
app.pathTo( "desktop", {from:"user domain"} )

 デスクトップ以外にも多くのパスが取れる。詳しくは用語説明。

サンプルスクリプトについて

 メニューの[ファイル]-[テンプレート]を使えば、幾つかのテンプレートスクリプトを選択できる。
 ただし、AppleScript用しか用意されていない。
 "/Library/Application Support/Script Editor/Templates/"にあるので、必要なら自前でスクリプトを追加したり書き換えたりは、簡単にできる。

 コンテクストメニュー(右クリック または control+クリック)を使うと、コードスニペットが挿入できる。
 ただし、AppleScript用しか用意されていない。
 "/Library/Scripts/Script Editor Scripts/"にあるので、必要なら自前でスクリプトを追加したり書き換えたりは、簡単にできる。

 [環境設定...]-[一般]の中に[スクリプトメニュー]という項目があり、[メニューバーにスクリプトメニューを表示]という項目がある。
 これをONにしておくと、メニュー右側にSっぽい形をしたスクリプトアイコンが出てくる。この中にはすぐに使えるスクリプトが沢山入っている。
 中身はAppleScriptだが、使う分には問題ない。
 optionキーを押しながらメニューを選ぶと、フォルダかスクリプトが開くので、必要なら自前でスクリプトを追加したり書き換えたりは、簡単にできる。

スクリプトの保存と使用法

 スクリプトは、メニューなどに入れて使う基本的な形式のスクリプト(.scpt)、フォルダとなっていて関連ファイルもパッケージできるスクリプトバンドル(.scptd)、バンドル状で且つ実行可能なアプリケーション(.app)、プレーンなテキストであるテキスト(.applescript)の各形式で保存できる。
 特にアプリケーションの場合、run、openDocuments、printDocuments、idle、reopen、quitという名前の関数が定義されていると、それぞれ特殊な役割をする。

 ターミナル(シェル)用には、osascript、osacompile、osadecompile、osalangといった、JXA(およびAppleScript)用のコマンドが用意されており、コマンドラインからもJavaScriptを利用できる。
 各コマンドについては、それぞれのmanを参照。

感想

 AppleScriptによってかなり環境が整備されていたこともあって、それに乗っかる形で作られたJXAは、(サードパーティー製のJavaScriptコントロールの歴史も長いこともあって)、OSに装備されたばかりでも、かなり使える感じに仕上がっている。
 記録が動作しないとか、コード補完や単語の選択範囲その他のスクリプトエディタの対応度が今ひとつなとこや、テンプレートやサンプルなどの周辺整備が遅れているという事はあるが、これもおいおい整備されると期待したい。
 逆にAppleScriptが先にあったため(注1)、それに合わせる必要があって、(whoseなど)なんだかややこしい仕様になっている。

 以前から、標準ブラウザSafariのJavaScript対応に力を入れたり、Dashbordの開発言語にJavaScriptを採用するなど、Mac OS Xの標準スクリプトをJavaScriptに切り替えようという動きは存在していたが、この2014年になって、やっと本丸の自動化言語をAppleScriptからJavaScriptへ切り替えようという準備ができた、というところ。
 ただ、先に書いたようにAppleScript環境が整備されていて、スクリプティングのノウハウなどの周辺環境にも蓄積があるので、サクッと切り替わることはなく、しばらくは混沌とした状況が続くと思われる。

 とりあえずの今使ってみた感想は、十分実用になりそうだし、AppleScriptやらずにJavaScriptだけ使ってもそこそこなんとかなりそうな雰囲気。
 というところ。

追記
 Appleから公開されているコードのフォーマットに沿って、このページでは、var宣言なし、行末;無しで書いてるけど、どーも気持ち悪い。
 JXAでvarを書けないというわけじゃないので、きちんと書いたほうがいいんじゃないかなー。

追記
Home · dtinth/JXA-Cookbook Wiki · GitHubが参考になりそうです。

追記
Mac : JavaScript for Automation (JXA) 例文辞典も参考になりそうです。