Archive for 10月, 2014

2014/10/30 ASからASOCのハンドラを呼び出す際には

昨日の問題の解決・・・小数を含む数値のリストをASOC(「AppleScriptObjC」あるいは「AppleScript/Objective-C」と呼ばれる)のハンドラに渡してソートさせた場合に、小数を含む値に丸め誤差が発生するという問題についてです。

これまでにも、Xcode上でASOCのプログラム(GUIベースのアプリケーション)は作ってきましたが、ASOCのハンドラ同士で呼び出しを行う際には極力文字列でデータをやりとりし、AppleScript側からASOC側を呼び出すことはしてきませんでした。

OS X 10.9までは、スクリプトエディタ上でもASOCのプログラムは書けたものの、あくまでアプレット上で完全にプログラムが分断された状態で動いていたために、見えてこなかった問題点でもあります。

つまり、この問題はOS X 10.10で1本のScript中で通常のAppleScriptとASOCが混在する状況が生まれたために見えてきた問題ともいえます(個人的に、OS X 10.9までのScript Editor上で記述するASOC(Interface BuilderでGUIが作れない)にあまり魅力を感じていなかったので、全然使っていなかったし)。

xcode.png

# Xcode上でも「小数点を含む数値リスト」を処理した場合には、同様の問題が出ました。Scripting Bridgeそのもののバグっぽいですね。Appleにバグレポートを書いて担当者を説得するにも時間がかかるので、目下解決策を検証中です。解決はしたのですが、自分ひとりだけでは済まないので、、、

調査したところ、list→NSArrayの型変換では問題は発生していないようでしたが、ASOCのハンドラからNSArray→listの型変換を行って値を返す際に問題が発生していました。

NSArray→listの型変換そのものはScripting Bridgeの処理系が行っているため、現状ではどーーにもなりません(Scripting Bridgeのバグ)が、「数値リスト」ではなく「数字のテキストのリスト」を扱うようにすれば、数値リストの型変換にともなう丸め誤差の発生を回避できます。

つまり、数値文字列のリストをASOC側に渡して、ソーティング(大小比較)自体を「数値として評価」すればOK。

というわけで、ASOC側には「数値リスト」ではなく「数字の文字列リスト」を渡し、ASOC側でそれぞれの要素を「数値として大小比較」するようにしてみました。念のため、有効桁数の多いfloatValueとして数値評価させています。

だいたい、AppleScriptObjCでプログラムを組む場合には、User Interface系をAppleScriptObjCで実装し、メインロジック部分は別途「通常のAppleScript」で組むことで、メンテナンス性を高めつつ、使いやすさを向上させる・・・といったケースが多いところです。

このため、AppleScriptObjCからAppleScriptを呼び出す場合、メニューやラジオボタンのID(整数)であったり、ファイルのパス(文字列)であったりと、今回の問題にひっかからない利用の仕方がほとんどでした。

OS X 10.10の「通常のAppleScriptの中にAppleScriptObjCを書けるようにする」という拡張により、これまでに少なかった「AppleScriptからAppleScriptObjCの機能を利用し、値を受け取る」という処理パターンが増え、問題点が露見したのでしょう。

AppleScript名:asoc_sorting_number_list_v2
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions

set aList to {“-1.1″, “1″, “10″, “0.1″, “1.1″, “1.12″, “-1″, “-100″} –Original List
set aRes to sort1DNumList_ascOrder_(aList, true)
–> {”-100″, “-1.1″, “-1″, “0.1″, “1″, “1.1″, “1.12″, “10″}–sort ascending

set bRes to sort1DNumList_ascOrder_(aList, false)
–> {”10″, “1.12″, “1.1″, “1″, “0.1″, “-1″, “-1.1″, “-100″}–sort descending

–Sort 1-Dimension List(String Number List)
on sort1DNumList:theList ascOrder:aBool
  tell current application’s NSSet to set theSet to setWithArray_(theList)
  
tell current application’s NSSortDescriptor to set theDescriptor to sortDescriptorWithKey_ascending_(“floatValue”, aBool)
  
set sortedList to theSet’s sortedArrayUsingDescriptors:{theDescriptor}
  
return (sortedList) as list
end sort1DNumList:ascOrder:

(*
–Sort 1-Dimension List(Number List)
on sort1DNumList2:theList ascOrder:aBool
  set anArray to current application’s NSArray’s arrayWithArray:theList
  set sortedList to anArray’s sortedArrayUsingSelector:(”numberCompare:”)
  return (sortedList) as list
end sort1DNumList2:ascOrder:
*)

–Sort 1-Dimension List(String List)
on sort1DList:theList ascOrder:aBool
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/10/29 AppleScriptからASOCのソートルーチンを利用する上での注意事項

OS X 10.10で一般のAppleScriptとASOCを普通にまじえて記述できるようになったため、いろいろと試してみました。

すると、ソートについていろいろと注意すべき点がわかってきたのでまとめてみます。

文字列のソート順序が違う

ためしにさまざまな文字を与えて並び順を調べてみたら、微妙に違うことがわかりました。

テストデータとして、

set aList to {”ん”, “ば”, “は”, “ぱ”, “あ”, “ま”, “ぁ”, “ア”, “か”, “ァ”}

のようなデータを用意し、昇順ソートを指定した際に、

AS: {”あ”, “ァ”, “ぁ”, “ア”, “か”, “は”, “ば”, “ぱ”, “ま”, “ん”}
ASOC:{”あ”, “ぁ”, “ア”, “ァ”, “か”, “は”, “ば”, “ぱ”, “ま”, “ん”}

と、文字の並び順が微妙に異なります。ASOC側でもうちょっとソートのパラメータを指定すると結果が変わったりしてきそうですが、とりあえず現状はこんな感じです。

どちらかといえば、ASだけで行うソートの方に不自然さを感じるぐらいですし、いまどき半角カタカナでもないでしょうから無視できそうですが・・・問題は次です。

小数を含む数値リストをソートすると丸め誤差が出る

小数を含む数値のリスト、

set aList to {1, 1, 10, 0.1, 1.1, -1, -100}

をデータとして与えてソートしてみたところ、返ってきた結果は、

–> {-100, -1, 0.10000000149, 1, 1.100000023842, 10}

微妙に値が変わってしまいました

回避策についてはいろいろ調べていますが、(US AppleのAppleScript Users MLに久しぶりに投稿してShane Stanleyに教えてもらいました)小数の値を含む数値リストのやりとりをするのは得策ではなさそうな雰囲気です(いまのところ)。

少なくとも、ASから数値をASOCのハンドラに渡しただけでは変わらないようです。

いろいろ試してみたところ、ハンドラ側でNSNumberに変換して処理を行い、結果を戻すさいに「as number」とか「as real」とかで型変換して返すときに丸め誤差が出てしまう模様です。

数値のパラメータであれば、ASから数値をASOCハンドラに渡して、丸め誤差を出さずに返すことはできたのですが、リストに入れてしまうとお手上げの状態。

まだ、試行錯誤を行ってみる必要があるようです。

AppleScript名:asocで1D Listをソート
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions

–昇順ソート(ASOC)
set aList to {“ん”, “ば”, “は”, “ぱ”, “あ”, “ま”, “ぁ”, “ア”, “か”, “ァ”}
set aRes to sort1DList_ascOrder_(aList, true)
–> {”あ”, “ぁ”, “ア”, “ァ”, “か”, “は”, “ば”, “ぱ”, “ま”, “ん”}

–降順ソート(ASOC)
set bRes to sort1DList_ascOrder_(aList, false)
–> {”ん”, “ま”, “ぱ”, “ば”, “は”, “か”, “あ”, “ぁ”, “ア”, “ァ”}

–昇順ソート(AppleScript)
set cRes to shellSortAscending(aList)
–> {”あ”, “ァ”, “ぁ”, “ア”, “か”, “は”, “ば”, “ぱ”, “ま”, “ん”}

–昇順ソート(AppleScript)
set dRes to shellSortDescending(aList)
–> {”ん”, “ま”, “ぱ”, “ば”, “は”, “か”, “ぁ”, “ア”, “あ”, “ァ”}

set aList to {“1″, “01″, “10″, “000.1″, “1.1″, “-1″, “-100″}
set aRes to sort1DList_ascOrder_(aList, true)
–> {”-1″, “-100″, “000.1″, “01″, “1″, “1.1″, “10″}

set aList to {1, 1, 10, 0.1, 1.1, -1, -100}
set aRes to sort1DNumList_ascOrder_(aList, true)
–> {-100, -1, 0.10000000149, 1, 1.100000023842, 10}

–1D List(数値)をsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DNumList:theList ascOrder:aBool
  tell current application’s NSSet to set theSet to setWithArray_(theList)
  
tell current application’s NSSortDescriptor to set theDescriptor to sortDescriptorWithKey_ascending_(missing value, true)
  
set sortedList to theSet’s sortedArrayUsingDescriptors:{theDescriptor}
  
return (sortedList) as list
end sort1DNumList:ascOrder:

–1D List(文字)をsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DList:theList ascOrder:aBool
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

–入れ子ではないリストの昇順ソート
on shellSortAscending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) > temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortAscending

–入れ子ではないリストの降順ソート
on shellSortDescending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) < temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortDescending

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

AppleScript名:asocで少数を含む数値を誤差のない形式でASに返す
–By Shane Stanley

use AppleScript version “2.4″
use framework “Foundation”
use scripting additions

set e to 0.1
set f to numberFloatWith_(e)
–> 0.1

on numberFloatWith:aNum
  set theNum to current application’s NSNumber’s numberWithFloat:aNum
  
return (theNum’s descriptionWithLocale:(current application’s NSLocale’s systemLocale())) as real
end numberFloatWith:

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/10/29 ASDictionary v0.13.4がMuggin Softからリリース

appscriptプロジェクトの遺産とも呼ぶべき「ASDictionary」。AppleScript対応アプリケーションの用語辞書をHTML形式で書き出すツールとして日々利用しています。

どう利用しているかといえば、辞書をHTML書き出ししておいて、バージョンごとの差分を求める際に使っています。別にHTML書き出ししていなくても、sdefファイル同士で差分を求めてもよさそうなところですが・・・Webにすぐ掲載できるとかいったメリットから、HTMLで書き出して蓄積しています。

dicthtml.png

diff.png

公式に配布されているASDictionary v0.13.1は、最終更新日が2011年1月9日。
なんと、OS X 10.10, Yosemiteにアップデートするとうまく動きません(AppleScriptからコントロールしての辞書書き出しができなかった)。

少々困っていたところ、Muggin SoftからOS X 10.8上で動作するようにビルドし直したとおぼしき、ASDictinary v0.13.2が配布されているのを見つけました

mugginsoft.png

ダウンロードしてみたら、バージョン番号が若干あがってv0.13.4。

asdictionary.png

おそらく、オープンソースで開発されてきたASDictionaryのv0.13.2がOS X 10.8対応、v0.13.3が10.9対応、v0.13.4で10.10対応ぐらいの・・・ビルドし直しぐらいの手直しを行われたものと思われます。

Muggin Softが管理しているASDictionaryもオープンソースで、ソースが公開されています。

とりあえず、OS X 10.10上で起動して、AppleScriptから操作できるASDictionaryが存在しているということです。

追記:いろいろ使ってみたところ、OS X 10.10上でASDictionaryでASDictionary自体のAppleScript用語辞書をHTML書き出しすることはできないようです。

2014/10/28 OS X 10.10のasocで1D Listをソート

OS X 10.10のAppleScript処理系は、マルチコアのCPUを活かして高速に処理を行うように変更されているように見えます。実際、スクリプトエディタ上でAppleScriptを実行すると、

process.png

OS X 10.9までよりもはるかに多くの内部スレッドを生成し、マシンパワーを絞り出すようにチューニングされていることがうかがわれます。

OS X 10.10ではCocoaの機能を直接呼び出すAppleScriptObjCが気軽に使えるようになったため、処理パワーが必要とされるリスト(配列)のソートをAppleScriptObjCと通常のAppleScriptで実行して処理時間を計測してみました。

処理対象の1-Dimension List(1次元配列)は10万アイテムで構成(すべて固定長の乱数を文字列化したもの)。

実行環境は、MacBook Pro Retina 2012(Core i7 2.66GHz x 4 core , 8GB RAM)およびMacBook Air 2011(Core i5 1.6GHz ,4GB RAM)です。

sort_res.png

結果は、ASOCの方が6〜18倍程度高速でした。これは、本機能を使わない手はありません。

ただ、調子に乗って100万アイテムのリストを作らせてみたら・・・RAMを使いつぶしたりスクリプトエディタを無反応にさせたりしたため、10万アイテム程度がこのぐらいのスペックのコンピュータ上でAppleScriptでデータ処理を行わせる場合には適正レベルといえるでしょう。

ぜひ、2D Listのソートもやらせたいですね。

AppleScript名:asocで1D Listをソート(じっけん)
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions

script spd
  property aList : {}
end script

–テスト用データリストの作成(10万アイテム)
set (aList of spd) to {}
repeat with i from 1 to 100000
  set the end of (aList of spd) to (random number from 10000 to 99999) as string
end repeat

–昇順ソートの時間計測
set a1Dat to current date

set aRes to sort1DList_ascOrder_((aList of spd), true)
set item1 to contents of (items -9 thru -1 of aRes)

set b1Dat to current date
set c1Dat to b1Dat - a1Dat

–降順ソートの時間計測
set a2Dat to current date

set aRes to sort1DList_ascOrder_((aList of spd), false)
set item1 to contents of (items -9 thru -1 of aRes)

set b2Dat to current date
set c2Dat to b2Dat - a2Dat

return {c1Dat, c2Dat}
–> {1, 1}

–1D Listをsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DList:theList ascOrder:aBool
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

AppleScript名:asで1D Listをソート(じっけん)
use AppleScript version “2.4″
use scripting additions

script spd
  property aList : {}
end script

–テスト用データリストの作成(10万アイテム)
set (aList of spd) to {}
repeat with i from 1 to 100000
  set the end of (aList of spd) to (random number from 10000 to 99999) as string
end repeat

–昇順ソートの時間計測
set a1Dat to current date

set aRes to shellSortAscending(aList of spd)

set b1Dat to current date
set c1Dat to b1Dat - a1Dat

–降順ソートの時間計測
set a2Dat to current date

set aRes to shellSortDescending(aList of spd)

set b2Dat to current date
set c2Dat to b2Dat - a2Dat

return {c1Dat, c2Dat}
–> {19, 8}

–入れ子ではないリストの昇順ソート
on shellSortAscending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) > temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortAscending

–入れ子ではないリストの降順ソート
on shellSortDescending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) < temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortDescending

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/10/28 Script Editor 2.7で新しいdocumentの属性値「event log」にアクセスする

OS X 10.10, Yosemiteのスクリプトエディタ(旧称:AppleScriptエディタ)バージョン2.7のAppleScript用語辞書で、唯一拡張されたdocumentオブジェクトの属性値「event log」にアクセスするAppleScriptです。

実行すると、

scr1.png

オープン中のドキュメントの名称一覧を表示(用語辞書をのぞく)するので、どれかを選択すると、イベントログの内容を表示します。

この、event logが何に該当するかといえば・・・

log1.png

log2.png

log3.png

log4.png

「返された値」の内容がそのまま(プレーンテキストになって)入ってきます。使い道はいまひとつ分かりませんが、、、

AppleScript名:Script Editor 2.7で新しいdocumentの属性値「event log」にアクセスする
tell application “Script Editor”
  –AppleScript用語辞書のウィンドウを除外して、オープン中の書類名をすべて取得
  
set dList to name of every document whose path does not end with “.sdef”
  
  
set dRes to choose from list dList
  
if dRes = false then return
  
set ddRes to first item of dRes
  
  
tell document ddRes
    set aLog to event log –指定ドキュメントウィンドウ中の「返された値」に表示された内容がプレーンテキストで入る
  end tell
end tell

aLog

–>
(*
“tell application \”Script Editor\”
  get properties of document \”ASDictionary\”
    –> {modified:false, path:\”/private/var/folders/h4/jfhlwst88xl9z0001s7k9vk00000gr/T/ASDictionary.sdef\”, class:document, name:\”ASDictionary\”}
end tell
結果:
{modified:false, path:\”/private/var/folders/h4/jfhlwst88xl9z0001s7k9vk00000gr/T/ASDictionary.sdef\”, class:document, name:\”ASDictionary\”}

*)

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/10/28 AppleScriptでNFKC Casefoldの影響を除外した文字列比較

AppleScriptの文字列比較においては、カタカナとひらがな、半角と全角文字が同一視されており、「これってどーなの?」と指摘しつつも、それに対してApple側は「Unicodeの規格にのっとった実装だ」と一貫して主張してきました。

この規約が「NFKC Casefold」というルールなわけですが、OS X 10.10からは通常のAppleScript(スクリプトエディタ上で動かすAppleScript)でもASOC(「AppleScriptObjC」もしくは「AppleScript/Objective-C」)を書けるようになったため、Cocoaの機能がダイレクトに使えるようになりました。

ASOCの機能を利用して、ふつーのプログラミング言語的な文字列比較を行ってみました。

もう、AppleScriptの処理系の気に入らない制限(数値変数の有効精度がいまどきたったの10桁であるとか)はこの手でとっとと書いてしまうのがよいのでしょう。

追伸:Shane Stanleyから「それ、ポインタ同士で比較してんの間違いだよ〜」と指摘があって、大急ぎで修正しました。はい。

AppleScript名:文字列比較をASOC(Cocoa)とAppleScriptで実施するテストルーチン v2
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions

set a to “”
set b to “リットル”
set c to compStrA_B_(a, b)
–> false

set a to “バビブベボ”
set b to “ハヒフヘホ”
set c to compStrA_B_(a, b)
–> false

set a to “AAA”
set b to “AAA”
set c to compStrA_B_(a, b)
–> true

–文字列比較をASOC(Cocoa)とAppleScriptで実施するテストルーチン
on compStrA:a b:b
  set aStr to current application’s NSString’s stringWithString:a
  
set bStr to current application’s NSString’s stringWithString:b
  
return (aStr’s isEqualToString:bStr) as boolean
end compStrA:b:

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/10/24 Script整形表示用AppleScriptをJavaScriptに対応

いつもBlogへの投稿に使っている、AppleScriptの整形投稿用のAppleScript(ASのソースを読み取って、HTML化してファイル出力)を、JavaScriptに対応させてみました。

とりあえず、Script Editorで表示させている最前面のウィンドウの書類がAppleScriptなのかJavaScriptなのかを検出して、色などを変更させてみました。

# 単にBlog掲載用のHTML書き出しプログラムをJavaScriptに対応させた(些細な修正)だけです

AppleScript名:asでdisplay dialog
use scripting additions
display dialog “こんにちは” with title “たいとるだよ” default answer “なんか入力して”

–> {button returned:”OK”, text returned:”なんか入力して”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

JavaScript名:jsでdisplay dialog
anApp = Application.currentApplication();
anApp.includeStandardAdditions =
true;
anApp.displayDialog (
’こんにちは’,
  {
    withTitle: ’たいとるだよ’,
    defaultAnswer:
’なんか入力してね’
  }
);

//–> {“buttonReturned”:“OK”, “textReturned”:“なんか入力してね”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

js1.png

プログラムリスト末尾のScriptリンクをクリックすると、自動でScript Editorに内容は転送されるものの、その言語がASかJSかの指定まではできないので(あってもよさそうなもんですが)、異なるOSA言語をデフォルトに設定している方は、

js2.png

OSA言語ポップアップからJSに変更してコンパイル(構文確認)を行ってください。

2014/10/23 複数のOSA言語がデフォルトでインストールされたはじめてのシステム

OS X 10.10ははじめて複数のOSA言語がデフォルトでインストールされてリリースされたMac OSとなりました。

過去に、English Dialect、French Dialect、Japanese DialectといったAppleScriptの「方言」が存在していた時期は(Classic Mac OS 8.5まで)ありましたが、複数のOSA言語がデフォルトで提供されたのははじめてです。

OSA言語自体は、昔からサードパーティ製のさまざまなものが存在しており、その中でも最も有名かつ長命だったのはUserland FrontierやLate Night SoftwareのJavaScript OSAでしょう。

今回、OS X 10.10ではApple純正のJavaScript OSAが標準で添付されており(Script EditorのOSA言語ポップアップから選択可能)、一般のユーザーがOSA言語を自由に選択可能な状態となったのははじめてのことです。

osapopup1.png

osa1.png

osa2.png

osa3.png

AppleScriptでScript Editorに対してOSA言語の一覧を取得し、各種OSA言語のプロパティを取得してみましょう。

スクリプト名:OSA言語のプロパティを取得
tell application “Script Editor”
  set langList to every language
  
–> {language “AppleScript Debugger 5″, language “AppleScript”, language “JavaScript”}
  
  
repeat with i in langList
    tell i
      properties
      
      
–> {name:”AppleScript Debugger 5″, supports compiling:true, description:”AppleScript Debugger 5 Scripting System”, class:language, supports recording:true, id:”asDB”}
      
      
–> {name:”AppleScript”, supports compiling:true, description:”AppleScript。”, class:language, supports recording:true, id:”ascr”}
      
      
–> {name:”JavaScript”, supports compiling:true, description:”JavaScript”, class:language, supports recording:false, id:”jscr”}
      
    end tell
  end repeat
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

▲language “AppleScript”のdescriptionが”AppleScript。”になっているのはバグ(指摘済みだが直るのやら ーー;)

AppleScriptはrecordingをサポートしているが、JavaScript OSAはrecordingをサポートしていないよ、という情報が取得できました(ほとんどrecordingは活用されていないし、Appleもアプリケーションにrecording対応は求めていないのでいいんじゃないかと)。

JavaScript OSAはAppleScriptとほぼ同じことができます。

アプレットの記述ができ、実行専用で保存するとソースが見られなくなります。β版のときには大丈夫だったのですが、リリース版の10.10ではJavaScriptで記述したアプレットは、「実行専用」で保存してもScript Editorで中身が見えますね、、、(ーー;;

Script Menuから呼び出すことができ、Terminalからosascriptコマンドで呼び出すこともできます。

GUI Scriptingの機能も使えるし、Cocoaの機能も使えます。

ただ、AppleScript Librariesの機能はJavaScript OSA(v1.0)にはないので、そこはこれからの(OS X 10.11以降での)対応になる見込みです。また、Xcode上でASOCのようなGUIベースのアプリを作ることは(現時点では)できません。

64ビットに対応していないアプリケーション内のScriptメニューからは、おそらくJavaScript OSAは呼び出せないものとおもわれます。

標準添付のApple純正JavaScript OSAに対する印象については、内部計算(配列変数を作らせて値を埋めるとか)についてはものすごく速いものの、アプリへのコントロールを行わせるとAppleScriptの10倍ぐらい遅い、という感じです(簡単な属性へのアクセスでそのぐらい差がつく)。

得意不得意が明確すぎるというか、バージョン1.0なんてそんなもんだろう、というところでしょうか。

ひとついえるのは、OS Xがすでに安定期に入っているため、Classic Mac OSからMac OS Xへの移行期のような苦しみは味あわずに済みそう、というところでしょうか。

ただ、日本語環境でろくにテストをしていないのはAppleのお家芸なので、JavaScript OSAについては何が出てくるやら、、、、

2014/10/23 OS X 10.10リリースノート(正式公開版)

報告したつまらないバグが修正されないままリリースされたOS X 10.10。いまひとつテンションが上がらないところですが、リリースノートの内容を確認していきましょう(2回目ですが、変更点もあるので再確認)。

# 地味に、iWorkのAppleScript対応機能が向上したことの方が注目度大

■AppleScriptの機能追加

・プログレスバーの表示

ビルトインのプログレス表示機能が追加されました。現在、どのぐらいの進捗状況なのかをバー表示できます。

progress.png

本機能は3段階で実装されています。

(1)Script Editorの実行中表示(回転式インジケータ)
(2)メニューバーに表示される実行中表示(回転式インジケータ)
(3)プログレスバー表示

(1)および(2)については、Script Editor上で実行したり、Script Menuから呼び出して実行すると表示されるものです。

(3)が目玉。ただし、プログレスバーを表示させるためにはいくつか条件が必要です。

(条件1)アプレットとして保存、実行していること

Script Editor上で実行している状態ではプログレスバーは表示されません(けっこー重要)

(条件2)各種プロパティを定義してあること

progress total steps(プログレスバー表示時のステップ数)
progress completed steps(どのステップで終了か)
progress description(プログレスバー表示中の表示タイトル)
progress additional descripion(プログレスバー表示中のサブタイトル)

なお、progress total steps、progress completed stepsが「-1」に設定してあるとプログレスバー表示は行われません。

スクリプト名:プログレス表示テスト_exe
set progress total steps to 10
set progress completed steps to 10
set progress description to “Progress….”
set progress additional description to “This work is now in progress..”

repeat with i from 1 to 10
  display dialog “Now” buttons {“OK”} default button 1 giving up after 1
  
set progress completed steps to i
end repeat

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

・複数タイプ指定の型変更(cast)

通常、「a as integer」などのように、asで明示的な型変換が行えますが、このさいの型をリストで列挙できるようになりました。

a as {integer, string}

で、最初にヒットしたものが有効になるようです。

“3″ as {integer, string}だとintegerの3が返ってきます。
“A” as {integer, string}だとstringの「A」が返ってきます。
false as {integer,string}のようにマッチするものがない場合にはエラーを発生させます。

・ハンドラの型指定パラメータ

on changeStr(a as string) のように、ハンドラに型指定が書けるようになりました。dateを渡す必要があるのにdateでないものが来た場合に備えて型テストをしたりしていましたが、そういう手間が減りました。積極的に使っていきたいところです。

・無意味句を用いたハンドラ記述時のデフォルト値指定

AppleScriptでハンドラを「英文っぽく」書くために用意されている意味のない予約語(意味なし予約語)でハンドラ記述した場合に、省略されたパラメータがあったときに初期値を宣言しておけるというものです。

すぐに思いつく使い方は、AppleScriptの標準命令を乗っ取って別の使い方をするような場合でしょうか。

スクリプト名:型およびデフォルト値指定のハンドラ宣言
–パラメータを指定したり、しなかったりしてもハンドラ側でデフォルト値を設定していれば大丈夫
display dialog “こんにちは”
display dialog “こんにちは” with icon 1
display dialog “こんにちは” with icon 1 buttons {“OK”}
display dialog “こんにちは” with icon 1 buttons {“OK”} default button 1
display dialog “こんにちは” with icon 1 buttons {“OK”} default button 1 giving up after 10

–標準命令のdisplay dialogの実行を横取りする
on display dialog aString as string : “” with icon i as integer : 0 default button aButton as integer : 1 buttons aList as list : {} giving up after aSec as integer : 10
  
  
say aString using “Otoya” speaking rate 200
  
end display dialog

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

AppleScriptの標準命令の多くはこうして乗っ取って、別の動きをさせられるものの・・・プログラム中でパラメータを指定していたりしなかったりするので、たとえば動作確認のためdisplay dialogでエラーメッセージを表示させていたものを、運用時にはすべてログファイルにタイムスタンプとメッセージ内容を書き出すように変更したい、といった場合に・・・なかなか難しいものがありました。すべて同じパラメータ数が指定されているかチェックし直す必要があったり。1か所でもオプション指定数が異なると、乗っ取った側のハンドラでエラーが出てしまったり。

この新機能を使うと、AppleScript標準命令の乗っ取りも簡単かつ確実に行えるので・・・とても使い手があります。とくに、標準命令にバグを作られた(「is in」演算子でバグを作られたMac OS X 10.3のよーに)場合でも回避できそうな感じがするのでとってもいいです(回避できるケースばかりではないので、注意が必要)。

あと、AppleがObjective-Cで実装したおっそーーい命令(なぜかAppleScriptで書き直した方が数倍速くなる)を高速なルーチンに置き換えて処理するとか。

・AppleScript/Objective-Cサポートの強化

10.6でXcode上でのみ記述、10.7からScript Editor上でも記述できるようになったものの、明確に通常のAppleScriptと区別されてきたAppleScript/Objective-C(Cocoa-AppleScript。以下、ASOC)。

Cocoaの機能をAppleScript上で利用できるようにする機能です。

10.10では、より通常のAppleScriptとの垣根が低くなりました。Cocoa-AppleScript AppletでなくてもASOCの機能が使えるようになりました。Script Editor上で実行するAppleScriptでも、直接ASOCの機能を呼び出せます(下記リストも、Script LinkをクリックしてScript Editorに転送してコンパイル(構文確認)してすぐに実行できます)。

ただし、ASOCがらみの機能は別スレッドで実行されるとのことで、スレッド上で実行するとエラーになるものもあるので注意しろ、と参考にならなさそうな注釈が書かれています。

実際にASOC機能入りのAppleScriptをいろいろいじくってみると、ハンドラの中身は現行の(Objective-Cライクな記法が許容されている)ASOCなんですが、

 例:readPlistAt:thePath

ハンドラの外はObjective-Cライクな記法が許容されない、OS X 10.8の頃のASOCの記法が必要とされるというわけで、

 例:readPlistAt_(thePath)

ちょっと中途半場な印象を受けます。明示的にrunハンドラの中に記述しても状況は変わりません。とてもおかしな仕様です。
→ Update : その後、回避策が発見されました。my readPlistAt:thePath と書くと、構文確認時のエラーを回避できます。

スクリプト名:ASOCで指定PDFのページ数をかぞえる
use framework “Foundation”
use framework “AppKit”
use framework “QuartzCore”

use scripting additions
use AppleScript version “2.4″ –10.9上では実行できない。10.10以降

property nsurl : class “NSURL”
property PDFDocument : class “PDFDocument”

set aFile to choose file of type {“com.adobe.pdf”}
set aRes to pdfPageCount_(aFile as string)

–指定PDFのページ数をかぞえる(10.9対応。普通にPDFpageから取得)
–パラメータ:PDFファイルのHFS path(string)
–返り値:PDFファイルのページ数(整数値)
on pdfPageCount:aFile
  
  
tell application “Finder”
    set aFileURL to (URL of file aFile)
  end tell
  
  
set aFileURL to aFileURL as Unicode text
  
set theURL to nsurl’s URLWithString:aFileURL
  
  
set aPDFdoc to PDFDocument’s alloc()’s initWithURL:theURL
  
set aRes to aPDFdoc’s pageCount()
  
  
return aRes as integer
  
end pdfPageCount:

–指定PDFのページ数をかぞえる(10.9対応。普通にPDFpageから取得)
–パラメータ:PDFファイルのPOSIX path(quoteされていない)
–返り値:PDFファイルのページ数(整数値)
on pdfCountPOSIXpath:aPDFPosixPath
  
  
set aFile to (aPDFPosixPath as POSIX file) –POSIX pathからPOSIX fileにcast
  
  
tell application “Finder”
    set aFileURL to (URL of (aFile as alias)) –aliasからでないとURLが取得できない
  end tell
  
  
set aFileURL to aFileURL as Unicode text
  
set theURL to nsurl’s URLWithString:aFileURL
  
  
set aPDFdoc to PDFDocument’s alloc()’s initWithURL:theURL
  
set aRes to aPDFdoc’s pageCount()
  
  
return aRes as integer
  
end pdfCountPOSIXpath:

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

使ってみると・・・あれ? これってすごい便利かも、、、通常のAppleScriptでCocoaの機能が使えると、アプレット上でないと実行できない(10.9まで)よりもはるかに便利。

また、AppleScript Libraries用のScriptを書く場合に、 “AppleScript/Objective-C Library”チェックボックスにチェックを入れる必要がありましたが、10.10からはその必要がなくなりました(チェックボックス自体もなくなった)。

もしも、AppleScript Librariesを10.9用にも利用する(提供する)場合には、バンドル中のInfo.plist中に

OSAAppleScriptObjCEnabled = YES

のエントリを記述する必要があるとのことです。

廃止された機能

恒例の「xxxx.scripting」というOS標準搭載のGUIなしアプリケーションが廃止になった、ということは(10.10では)ないのですが、SpeechRecognitionServerがなくなりました。

speechfeedback.gif
▲AppleScriptから呼び出したり、(英語のみの)音声認識インタフェースからAppleScriptを実行したりできた(終始、実用性はいまひとつだった)

英語の音声認識コマンドが廃止になったということで、びみょーーに使い道がなかったし、英語にしか対応してこなかったので、いまひとつだったため・・・とくに、廃止されてもダメージはありません。

AppleScriptのCGI系の予約語がいまだに廃止されないのが不思議ですが、どこかにCGI機能を活用しているヘビーユーザーでもいるのでしょうか?

2014/10/23 編集中のScript BundleあるいはApplet全体の行数をかぞえる

Script Editor(10.9まではAppleScriptエディタ。以下、エディタと略)で編集中のアプレットやバンドル形式のAppleScriptの、バンドル内に入れてあるサブScriptすべての行数をカウントするAppleScriptです。

バンドル形式のAppleScript、あるいはアプレットに入れてあるサブScript(共有機能を突っ込んであったり、個別に動作確認する必要があるモジュールなど)すべての行数をカウントしようとすると、わりとやっかいで面倒です。

scripts.png

そこで、専用のScriptを組んでカウントすることにしました。

実行すると、エディタ上でオープンしているAppleScriptの一覧が出るので、

asdiaog.png

任意のScript(バンドルScript、アプレット)を選択すると、バンドル内のすべてのScriptの名称と行数、さらに総合計の行数をTAB区切りテキストに(エディタの「結果」欄に)出力します。

エディタで直接バンドル内のAppleScriptを(AppleScript側から指令して)オープンさせるとエラーになるので、shellのosadecompileコマンドを使ってAppleScriptを(中間言語形式から)テキストに逆変換し、行数をカウントしています。

行数のカウントまでshell側で「wc -l」で行わせた方が速そうでしたが、AppleScript側でテキストを取得できれば、改行のみの行を除外するといった処理もできるので、今後の発展性を考慮してshell側でカウントしないようにしました。

ちなみに、バンドル形式のAppleScript、あるいはアプレットについては、バンドル内に入れたAppleScriptをエディタ上から直接オープンすると、変更したり保存したりする際にエディタが暴走してしまいます(10.9、10.10で確認)。バンドル内のAppleScriptを編集したい場合には、Finder上でバンドル内容を表示させたうえで、Finder側でバンドル内のAppleScriptをダブルクリックして編集するとエディタが暴走せずに編集、保存が可能です。

スクリプト名:編集中のScript BundleあるいはApplet全体の行数をかぞえる
set resList to {}
set resTotal to 0

–編集中のAppleScriptのうち、行数カウント対象を選択する
tell application "Script Editor"
  set dList to name of every document
  
  
set dRes to choose from list dList
  
if dRes = false then return –キャンセル時の対応
  
  
set ddRes to first item of dRes
  
  
tell document ddRes
    set aPath to path
  end tell
  
end tell

–バンドル中のScriptをすべて取得
set realPath to aPath & "/Contents/Resources/Scripts/"
set sRes to do shell script "ls " & (quoted form of realPath)
set sList to paragraphs of sRes

repeat with i in sList
  
  
set j to contents of i
  
set jj to realPath & j
  
  
–osadecompileコマンドで中間言語形式のAppleScriptをテキストに逆変換して行数をかぞえる
  
set tempAS to do shell script "/usr/bin/osadecompile " & quoted form of jj
  
set tempList to paragraphs of tempAS
  
set asCount to length of tempList –wc- lでカウントしてもいいが、本文の内容を取り出せておけたほうが便利
  
  
–出力リストに名称、行数のペアで追加
  
if j = "main.scpt" then
    set the end of resList to {ddRes & " (" & j & ")", asCount}
  else
    set the end of resList to {j, asCount}
  end if
  
  
–合計行数の算出
  
set resTotal to resTotal + asCount
  
end repeat

–総合計を出力リストの末尾に追加
set the end of resList to {"Total:", resTotal}

–2D ListをTAB区切りテキストに変換して結果に出力
set resText to retItemDelimedAndParagraphDelimedText(resList, tab, return) of me

–>
(*
"A1.scpt  264
A2.scpt  360
A3.scpt  358
A4.scpt  298
B1.scpt  298
B2.scpt  243
B3.scpt  294
B4.scpt  258

(中略)

設定ファイルから実行年月日&時のリストを展開.scpt  299
Total:  3570"
*)

–入れ子のリストを、アイテム間のデリミタとパラグラフ間のデリミタを指定してテキスト化
–というか、入れ子のリストをタブ区切りテキストにするのが目的
on retItemDelimedAndParagraphDelimedText(aList, itemDelim, paragraphDelim)
  set aText to ""
  
  
repeat with i in aList
    set aStr to retDelimedText(i, itemDelim) of me
    
set aText to aText & aStr & paragraphDelim
  end repeat
  
  
return aText
end retItemDelimedAndParagraphDelimedText

on retDelimedText(aList, aDelim)
  set aText to ""
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retDelimedText

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/10/06 最前面のQuickTimeムービーの現在のコマをコピーしてPhotoshopでJPEG保存

オープン中のQuickTimeムービーの現在のコマを静止画としてコピーして、PhotoshopでWeb用のJPEG(サイズが小さい)として書き出すAppleScriptです。

QuickTimeムービーの見どころだけをかいつまんでJPEGに書き出していたのですが、画面キャプチャをとったりなんだりで、3ステップぐらい手作業が必要だったので、いっぱつで実行できるようにAppleScriptを書いてみました。

各所にdelayを入れて時間待ちしているところがミソです。入れないとAppleScriptの実行が速すぎて、コピーなどの動作が完了しなかったためです。

また、QuickTime Player(X)の機能がおっそろしく少ないので、仕方なくGUI Scriptingを併用(現在のコマの内容をコピーする)しています。実行には、GUI Scriptingを許可しておく必要があります。

4d31d0bd-9459-478a-8fd5-f941a13f5453.jpg
▲ムービーから1コマだけ抜き出してPhotoshopでWeb用JPEG(サイズが小さい)に書き出した結果

スクリプト名:最前面のQuickTimeムービーの現在のコマをコピーしてPhotoshopでJPEG保存
set dPath to path to desktop
set dPathStr to dPath as string
set fPath to dPathStr & (do shell script “uuidgen”) & “.jpg”

–QuickTime書類の縦横ドットサイズを取得
tell application “QuickTime Player”
  
  
–ムービーを開いていないかチェック
  
set dCount to count every document
  
if dCount < 1 then
    display dialog “ムービーを開いた状態で実行してください” with icon 2 with title “エラー:書き出し対象のムービーが存在しません” buttons {“OK”} default button 1
    
return
  end if
  
  
tell document 1
    set {xWidth, yHeight} to natural dimensions
  end tell
  
end tell

–QuickTime Playerの書類の現在の位置の内容(静止画)をコピー
activate application “QuickTime Player”

delay 0.1

tell application “System Events”
  tell process “QuickTime Player”
    click menu item 5 of menu 1 of menu bar item 4 of menu bar 1 –編集>コピー
  end tell
end tell

delay 0.1

–Photoshopで新規書類を作成してペースト
tell application id “com.adobe.photoshop”
  activate
  
  
delay 0.1
  
  
make new document with properties {width:xWidth, height:yHeight}
  
paste
  
  
delay 0.1
  
  
–Resize & Save
  
tell current document
    
    
set aH to height
    
set aW to width
    
    
set aH1 to aH * 0.25
    
set aW1 to aW * 0.25
    
    
–resize image width aW1 height aH1
    
    
export in file fPath as save for web
    
    
close without saving
    
  end tell
  
  
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に