Archive for 1月, 2015

2015/01/30 ASOCアプリをScriptableにして値を返す

基本的に、「AppleScriptObjC Explored fifth edition」のP-157「25. Making it Scriptable」を読めば、ASOCアプリをAppleScript対応にして、AppleScriptからASOCアプリを操作できるようにすることは可能です(この本よりいい本はないので、ASOCを使おうとしたら買うべき)。

基本的には、

(1)Info.plistに「Bundle creator OS Type Code」を英数字4文字で設定

(2)Info.plistに「Scripting definition file name」を設定(”.sdef”ファイルのファイル名。拡張子を含む)

(3)Info.plistに、「Scriptable」のエントリを作成し、値をYES(Boolean)に設定

(4)Xcodeプロジェクトにsdefファイルを追加

(5)sdefファイルの内容を記述

(6)AppDelegate.applescriptにプロパティへのアクセス許可/不許可イベントハンドラ「application_delegateHandlesKey_」を追加

(7)sdef内で宣言したコマンド(class)に対応するサブscriptをASOCプロジェクト内に作成し、コマンド内で行う処理を記述

といった手順でScriptableにできるわけですが、ASOCアプリからAppleScript側に返す値の種類によって難易度が変わるということを、身を持って体験しました。

  string:文字列。かんたん。実際に確認ずみ
  file:ファイルパス。かんたん。実際に確認ずみ
  number:数値。かんたん。実際に確認ずみ
  record:レコード。超難しい。挫折
  record:レコード。情報が少ない。難しくはなかった

アプリケーションからAppleScriptコマンドの返り値をレコードで返すというのは、Finderをはじめとする一般的なアプリケーションでは常識的な動作であり、ASOCアプリでこれを行おうとすると大変、ということがわかりました。

これなら、アプリケーション操作と同等のAppleScript Librariesを(AppleScript用語辞書なしで)別途提供したほうがずっと簡単です。

Objective-Cのプログラムを併用することで、ASOCアプリからNSDictionaryの値をAppleScript側に返してあげられるとよいのですが、、、

あとになって、よくよく考えてみるとAppleScript対応のアプリケーションが、コマンドの実行結果として直接record型のデータを返してくるケースを見たことがないような気がしてきました。true/falseとか、結果が入ったオブジェクトをいくつも返してきて、結果を確認するためにはそれらのオブジェクトの属性にアクセスするような処理が多いような・・・

・・・とか書いていたら、海外から「え、やったことあるけどサンプル送ろうか?」という声が(^ー^;;; ありがたく見せていただくことに。このあたりの書き方でイケるんでしょうか。

・・・あ、自分で試してたらできちゃった。これは、AppleScript用語辞書ファイル(sdef)の書き方の問題なのかー。本当にそれだけなんだー。難易度が高くない(情報が見つかりにくい)ことがわかりました。Objective-Cのコードを1行も書かずに、ASOCだけで実装可能でした。

2015/01/29 ASOC on Xcodeで他のScript上のハンドラを呼ぶ

Xcode上でASOCのプログラムを書いていると、「これどうやるんだろうなぁ?」と素朴な疑問に思うことがいくつもあります。

アプリケーションバンドル内で、いくつものファイルに分割された状態でscriptが存在しているわけで、そのscript間で機能を呼び出すにはどうしたらよいか、という疑問がありました。

delegate methodの呼び出し

メインのAppDelegate.applescript内のハンドラ(method)を、他のscriptから呼び出すのに、どうしたものか?

ありました。delegate methodを呼び出すやりかたが「AppleScriptObjC Explored」(Fifth Edition)のP-169に載っていました(汗)。

アプリ内の他のscriptのmethodの呼び出し

いろいろ、バンドル内のscript同士で呼び出すやりかたをまとめておきました。これがわからなくて困ったことがあったので、「最初にこういう資料があれば」と思うことしきりです。

call_otherscript.png

また、以前にまとめておいた「ASOCのプログラムをScriptableにする」方法が、最新のOS+最新のXcodeだと動かなかったりして、Scirptableにするやりかたも再度調査。こちらも、「AppleScriptObjC Explored」(Fifth Edition)のP-169に載っていました。

ただし、目下ASOCのアプリ側からAppleScriptにresultをrecordで返せなくて悩んでいます。さんざん調べたうえで、AppleScriptObjC Developers MLに「ええい、どうにでもなれ〜!」と投げてしまいましたが、果たしてどうなることやら、、、Appleのprivate methodを使ってデータ変換するやりかたなどまでは行き着いたものの、それで変換してもASOCアプリからAppleScript側にどーーしてもrecordを返せない、、、

→ Recordで返すのはあきらめました。オマケ機能だったので別にいいです。

2015/01/28 AppleScriptObjCで記述するAutomator ActionのTechnical Noteが公開された

OS X 10.10.2もリリースされましたが、驚いたのは昨日「Technical Note TN2322 Building a Cocoa-AppleScript (AppleScriptObjC) Automator Action」が公開されたことです。

しかも、Automated Workflows, LLCのBen Waldie(開発者、ライター)がこれを書いたようで(掲載Scirptに記名があるのと、この情報をBen WaldieのTwitterアカウント経由で知ったのとで、たぶん)二重に驚きました。

Apple社外のエンジニアがTech Noteなどのドキュメントを書くというのは、いいことだと思います。Apple社内のリソースが潤沢にあるわけではないので、出せる仕事は社外に出すというのは賢明な判断です。

英文で書いてありますが、わかりやすくていい記事です。

2015/01/26 テキストをhexdump(ASOC)v4

NSDataを使ってhexdumpを行うASOCのscriptの、若干の機能追加版です。

文字列の整形にCocoaの機能を使うように変更(データ量が増えた時にも安心できるように)しました。ただ、期待したよりは速くない感じです。

AppleScript名:テキストをhexdump(ASOC)v4
– Created 2015-01-26 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to “あいうえお”

–Hexdump
set theNSString to current application’s NSString’s stringWithString:aStr
set theNSData to theNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set theString to (theNSData’s |description|()’s uppercaseString())

–Remove “< " ">” characters in head and tail
set tLength to (theString’s |length|()) - 2
set aRange to current application’s NSMakeRange(1, tLength)
set theString2 to theString’s substringWithRange:aRange

–Replace Space Characters
set aString to current application’s NSString’s stringWithString:theString2
set bString to aString’s stringByReplacingOccurrencesOfString:” “ withString:“”

set aResList to splitString(bString, 2) as list
–> {​​​​​”E3″, ​​​​​”81″, ​​​​​”82″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”84″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”86″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”88″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”8A”​​​}

–Split NSString in specified aNum characters
on splitString(aText, aNum)
  
  
set aStr to current application’s NSString’s stringWithString:aText
  
if aStr’s |length|() aNum then return aText
  
  
set anArray to current application’s NSMutableArray’s new()
  
set mStr to current application’s NSMutableString’s stringWithString:aStr
  
  
set aRange to current application’s NSMakeRange(0, aNum)
  
  
repeat while (mStr’s |length|()) > 0
    if (mStr’s |length|()) < aNum then
      anArray’s addObject:(current application’s NSString’s stringWithString:mStr)
      
mStr’s deleteCharactersInRange:(current application’s NSMakeRange(0, mStr’s |length|()))
    else
      anArray’s addObject:(mStr’s substringWithRange:aRange)
      
mStr’s deleteCharactersInRange:aRange
    end if
  end repeat
  
  
return (current application’s NSArray’s arrayWithArray:anArray)
  
end splitString

★Click Here to Open This Script 

2015/01/25 テキストをhexdump(ASOC)v2

NSDataを使ってhexdumpを行うASOCのscriptの、若干の機能追加版です。

2文字ごとにペアにして出力するようにしました。

AppleScript名:テキストをhexdump(ASOC)v2
– Created 2015-01-25 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to “あいうえお”
set theNSString to current application’s NSString’s stringWithString:aStr
set theNSData to theNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set theText to text 2 thru -2 of (theNSData’s |description|()’s uppercaseString() as text)
set tList to words of theText
set ttext to tList as string

set aRes to my splitString:ttext everyChar:2
–> {​​​​​”E3″, ​​​​​”81″, ​​​​​”82″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”84″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”86″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”88″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”8A”​​​}

–文字列を指定文字数で分割
on splitString:aText everyChar:aNum
  
  
set aStr to current application’s NSString’s stringWithString:aText
  
if aStr’s |length|() aNum then return aText
  
  
set anArray to current application’s NSMutableArray’s new()
  
set mStr to current application’s NSMutableString’s stringWithString:aStr
  
  
set aRange to current application’s NSMakeRange(0, aNum)
  
  
repeat while (mStr’s |length|()) > 0
    if (mStr’s |length|()) < aNum then
      anArray’s addObject:(current application’s NSString’s stringWithString:mStr)
      
mStr’s deleteCharactersInRange:(current application’s NSMakeRange(0, mStr’s |length|()))
    else
      anArray’s addObject:(mStr’s substringWithRange:aRange)
      
mStr’s deleteCharactersInRange:aRange
    end if
  end repeat
  
  
return (current application’s NSArray’s arrayWithArray:anArray) as list
  
end splitString:everyChar:

★Click Here to Open This Script 

2015/01/24 テキストをhexdump(ASOC)

何回もトライしてできていなかった、NSData経由でのデータのhexdump。Shane Stanleyから教えてもらいました。

ただ、しつこく何回も掲載している割には、現時点でこれといった用途はないのですが・・・この処理をクリアしておくときっとあとでいいことが。

AppleScript名:テキストをhexdump(ASOC)
– Created 2015-01-24 by Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set aStr to "あいうえお"
set theNSString to current application’s NSString’s stringWithString:aStr
set theNSData to theNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set theText to text 2 thru -2 of (theNSData’s |description|()’s uppercaseString() as text)
–>  "E38182E3 8184E381 86E38188 E3818A"

★Click Here to Open This Script 

2015/01/24 ASOCでTwitter投稿

ASOCでTwitterへの投稿を行うAppleScriptです。

以前にXcode上のASOC用に作っておいたものを、若干書き換えてScript Editor上から直接実行できるようにしてみたものです。

tw1.png

tw2.png

本当は画面上にいちいち投稿ビューが表示されないものを追いかけていたのですが、Blocksの記述が必要でAppleScriptObjCの仕様上、呼び出せないものだったのでとりあえずこのぐらいで。

正直、この程度の処理にあまり苦労したくないので、最終的にはcommand lineから呼び出すtwitterクライアント「tw」を使ったほうが現実的かも、、、

AppleScript名:ASOCでTwitter投稿
– Created 2015-01-24 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

set aString to “ぴよ〜”

set twService to current application’s NSSharingService’s sharingServiceNamed:(current application’s NSSharingServiceNamePostOnTwitter)
set twService’s delegate to me

set shareItems to current application’s NSMutableArray’s alloc()’s initWithObjects:aString
tell twService to performWithItems:shareItems

★Click Here to Open This Script 

2015/01/24 テキストをhexdump

テキストをhexdumpするAppleScriptです。hexdumpしてからデータ処理するという方法は、案外まだまだ使える「手口」なので紹介しておきます(対象データがバカでかくないことが前提条件)。

fhexdump1.png

文字列の最後についている”0A”はLFですね。

AppleScript名:テキストをhexdump
set aStr to “あいうえお”
set aRes to retHexDumpedStr(aStr)
–> “E3 81 82 E3 81 84 E3 81 86 E3 81 88 E3 81 8A 0A “

–与えられたデータ内容をhexdumpして返す
on retHexDumpedStr(aStr)
  set aRes to do shell script “echo “ & quoted form of aStr & ” | hexdump -v -e ’/1 \”%02X \”’”
  
return aRes
end retHexDumpedStr

★Click Here to Open This Script 

2015/01/24 spotlightでファイルタグを指定してファイル一覧を取得

OS X 10.9, Mavericksで採用されたFinderのFile Tagで、指定フォルダ以下のファイルをリストアップするAppleScriptです。

だいたい、OS Xの新機能については1つのバージョンを置いてAppleScript側が追いつく、というスタイルが定着していたので、OS X 10.10でFile Tagのサポートがなかったことには(よくないほうの意味で)驚きました。

ただ、10.10で導入されたScript Editor上でのAppleScriptObjCの機能のサポートによって、File Tagの設定、取得、追加についてはAppleScriptObjCで書くもの、という認識を持っていました。

それでも、指定フォルダ以下のFile Tagによるファイル抽出については情報が出ていなかったので、いろいろと調べたところ・・・mdfindで検索するのが一番簡単という結論に。

filetag1.png

本来的には、フィルタ参照(get every file whose tags is equal to {”Blue”, “Green”} 的な)で抽出できることがAppleScript的な「作法」ではあるものの、Appleがそうした機能を提供していない(できていない)という状況に対処するためには、こうしたサブルーチンの利用が現実的でしょう。

ただ、{”グリーン”, “ブルー”, “ヤンキー”} というファイルタグを持つファイルが{”グリーン”, “ブルー”}でリストアップされてしまうので、まだ調整の余地がありそうです。

filetag2.png

などと書いたので、Finder上でSpotlightの動作を調べてみたら・・・この通りの動きをするようなので、仕方ないのかも、、

filetag3.png

AppleScript名:spotlightでファイルタグを指定してファイル一覧を取得
– Created 2015-01-24 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions

set aFol to path to desktop
set aTagList to {“グリーン”, “ブルー”}
set aRes to getFileListByTagsWithSpotLight(aTagList, aFol) of me
–> {alias “Macintosh HD:Users:me:Desktop:fra5.png”, alias “Macintosh HD:Users:me:Desktop:fra6.png”}

–指定階層下で、指定タグ(複数をリストで指定)を持つファイルを取得(alias list)
on getFileListByTagsWithSpotLight(aTagList as list, startDir as {alias, string})
  –Error Case
  
if aTagList = {} then return {}
  
if startDir as string = “” then return
  
  
–Make File Tag Search Query String
  
set aQuery to {}
  
repeat with i in aTagList
    set aTag to contents of i
    
set aStr to “kMDItemUserTags==” & aTag
    
set the end of aQuery to aStr
  end repeat
  
set queryText to retArrowText(aQuery, ” && “) of me
  
  
–Make mdfind command string
  
set sDirText to quoted form of POSIX path of startDir
  
set shellText to “/usr/bin/mdfind ’” & queryText & “’ -onlyin “ & sDirText
  
try
    set aRes to do shell script shellText
  on error
    return {}
  end try
  
  
–Parse Result into alias list
  
set pList to paragraphs of aRes
  
set aList to {}
  
repeat with i in pList
    set aPath to POSIX file i
    
set aPath to aPath as alias
    
set the end of aList to aPath
  end repeat
  
return aList
  
end getFileListByTagsWithSpotLight

–リストを任意のデリミタ付きでテキストに
on retArrowText(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 retArrowText

★Click Here to Open This Script 

2015/01/24 ASOC on Xcodeでアプリケーションに埋め込めるようASObjCExtrasを改修中

ASObjCExtras.frameworkを使って、Xcode上でASOCによるプログラム開発は行えますが、それを配布できるようアプリケーション中に埋め込めるようになっているのか?

ASObjCExtrasは再配布に制限を設けられておらず、アプリケーションやアプレットに埋め込んで配布できるよう、それ自体にコードサインされていません。むしろ、そのように使えるように仕様が定められ、作られているはずのものです。

実際に行ってみたところ、ASObjCExtrasのバージョン1.2では「そうなっていなかった」という衝撃の事実が発覚(1日悩んでしまいました)。コンピュータの業界的にというべきか、プログラミング全般というべきか、マニュアルやドキュメントのとおりに実際のモノが作られているかは、自分の手で地道に確認する必要があります。これ、本当。

即座にShane Stanleyにフィードバックし、対策ビルドの開発が行われ・・・今朝方確認した未リリースの最新ビルドではきちんと埋め込めるようになっています(埋め込みおよび埋め込んだアプリケーションバンドル内のフレームワークを正しく参照するようになった)。

現在、Xcode上でASOCでアプリケーション開発を行っている方は、外部に配布なりMac App Storeでの配布・販売を考えている場合には、バージョン1.2からアップデートされたASObjCExtras.frameworkがリリースされるまで「待つ」ことをおすすめします最新バージョンのv1.2.3を埋め込むことを強く推奨します。

# とか書いている間に、新バージョンv1.2.3が公開されました。変更点はここに書いているように、Xcodeプロジェクトへの埋め込み対応改善です

以下、実際にXcode上のASOCのプロジェクトにASObjCExtras.frameworkを組み込んで、アプリケーションに同frameworkを埋め込む手順です。

fra1.png

fra2.png

fra3.png

fra4.png

fra5.png

fra6.png

あとは、ビルドしてアプリケーションバンドル内に/Contents/Frameworks/ASObjcExtrasが存在していることを確認し、ASObjCExtrasなどのサードパーティ製フレームワークが一切含まれていない別ユーザーアカウントにて動作するかどうか確認をすれば大丈夫でしょう。

2015/01/22 指定したアプリケーションの各lproj内の各stringsファイルのパス一覧を取得する

指定したアプリケーションの各言語のlprojフォルダ内にある、各stringsファイルへのパスを取得するAppleScriptです。

各アプリのローカライズ情報を取り出すプログラムはいろいろと試してはいるものの、作り方とかローカライズ方法が全然違うアプリケーションが紛れていることがあり、イレギュラーを除去すべきなのかも(Apple純正でもSafariあたりは全然違うし)。

AppleScript名:指定したアプリケーションの各lproj内の各stringsファイルのパス一覧を取得する
– Created 2015-01-22 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set apPath1 to (path to applications folder) –dialog’s default directory
set anAlias to choose file of type "com.apple.application-bundle" default location apPath1 with prompt "ローカライズ情報を取得するアプリケーションを選択"
set aRes to getStringsPathWithinEachLanguageInApp(anAlias)
–> {{langStr:"English", stringsPath:{"/Applications/mi.app/Contents/Resources/English.lproj/Localizable.strings"}}, {langStr:"Japanese", stringsPath:{"/Applications/mi.app/Contents/Resources/Japanese.lproj/Localizable.strings"}}}

–指定したアプリケーションの各lproj内の各stringsファイルのパス一覧を取得する
on getStringsPathWithinEachLanguageInApp(anAlias)
  –アプリケーションのローカリゼーションを取得する(言語の重複を許容)
  
set locList to getSpecifiedAppFilesLocalizationListWithDuplication(anAlias) of me
  
  
set lprojList to {}
  
  
repeat with ii in locList
    
    
set jj to contents of ii
    
    
set appPath to POSIX path of anAlias
    
set appBundle to (current application’s NSBundle’s bundleWithPath:appPath)
    
set resPath to ((appBundle’s pathForResource:jj ofType:"lproj") as string) & "/"
    
    
if resPath is not equal to missing value then
      
      
set aLangBundle to (current application’s NSBundle’s bundleWithPath:resPath)
      
if aLangBundle is not equal to missing value then
        
        
set stringsList to (aLangBundle’s pathsForResourcesOfType:"strings" inDirectory:"") as list
        
        
if stringsList is not equal to {} then
          set the end of lprojList to {langStr:jj, stringsPath:stringsList}
        end if
        
      end if
    end if
    
  end repeat
  
  
return lprojList
  
end getStringsPathWithinEachLanguageInApp

–指定アプリケーションファイルの、指定Localeにおけるローカライズ言語リストを求める。重複を許容
on getSpecifiedAppFilesLocalizationListWithDuplication(anAppAlias as alias)
  
  
set aURL to (current application’s SMSFord’s URLFrom:anAppAlias)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
  
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:"en" –Output Locale Name in English (en)
  
  
–Get Localization Info
  
set locList to aBundle’s localizations()
  
–> {"de", "English", "fr", "French", "German", "ja", "Japanese"}
  
  
return locList as list
  
end getSpecifiedAppFilesLocalizationListWithDuplication

★Click Here to Open This Script 

2015/01/22 言語と地域の「優先する言語」を取得する(ASOC)

「システム環境設定」の「言語と地域」で、「優先する言語:」に指定されている言語リストを取得するAppleScriptです。

本当は、OS上で用意している言語(35言語)のリストが欲しかったのですが、とりあえず。

international.png

AppleScript名:言語と地域の「優先する言語」を取得する(ASOC)
– Created 2015-01-22 by Takaaki Naganoya
– 2015 Piyomaru Software
– Technical Q&A QA1391
– How can I determine the order of the languages set by the user in the Language tab of the International preference pane?
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set defaultsO to current application’s NSUserDefaults’s standardUserDefaults()
set globalDomain to defaultsO’s persistentDomainForName:"NSGlobalDomain"
set langArray to (globalDomain’s objectForKey:"AppleLanguages") as list
–>  {​​​​​"ja", ​​​​​"en-US", ​​​​​"en-GB", ​​​​​"fr", ​​​​​"en"​​​}

★Click Here to Open This Script 

2015/01/20 Xcode上のASOCにScript Editor上のASOCの機能を移植する

最初、AppleScriptObjC(Cocoaの機能を呼び出せるAppleScript)というのは、Xcode上でしか書けないものでした(OS X 10.6で登場)。

asoc1.png

それが、10.7、10.8、10.9、10.10と進化するにしたがい、実行環境や記述環境が広がっていきました(これらのほかに、AutomatorやFileMaker Pro上の実行環境もありますが、ここでは扱いません)。

当初、OS X 10.6で導入されたXcode上のASOCは、同様にXcode上でGUIベースのAppleScriptアプリ開発を行う「AppleScript Studioの置き換え」と言われてきたため、Cocoaの機能を利用するのはもっぱらGUIまわりの部分だけで、内部処理の部分は従来のピュアなAppleScript(Cocoaの機能を利用しない)で作られることが多かったように思います(そうでないケースもありますが)。

OS X 10.10になってCocoaの機能を呼び出すASOCが、ほぼ通常のAppleScriptと不可分の位置付けとなったため、通常のAppleScriptでも積極的にCocoaの機能を利用するよう本Blogでも方向転換を行い、内部処理もCocoaの機能を呼び出すScriptでXcode上のASOCアプリケーション開発も行うようになりました。

実際に、実践的なAppleScriptのプログラム(レコードのリスト同士のdiffを求める)で試してみたところ・・・ピュアなAppleScriptだけで書いたものが420行、処理に10秒ちょっとかかっていたのが、ASOCで1からすべて書き直したところ行数が270行に(40%減)、処理速度は8.4倍の1.2秒までに短縮されました。

# 上記の処理時間計測は、考えられるMAXのサイズのテストデータを処理させたものです

asoc2.png

そこで、GUIアプリ開発環境であるXcode上のASOCにこのScriptを移し替えたいよね、という話に(必然的に)なるわけです。

ASOC on XcodeにASOC on Script EditorのScriptを

ASOC on Xcodeでは、プロジェクト作成時に自動的に作成されるdelegate scriptには、アプリケーション本体のさまざまな挙動(GUIに近い部分とか)を記述し、処理本体部分は別のScriptに追い出して記述するのが自分の流儀です。

というわけで、本体scriptから外部script(bundle内に存在)を呼び出すことになります。このあたりは、XcodeのInterface Builder上で、

asoc3.png

NSObjectの部品を画面上に配置して、そのNSObjectのclassを外部Scriptのscript文で指定したclassと同じものに設定。

asoc4.png

delegate script側で外部script連結用のpropertyを作成して、

asoc5.png

Interface Builder上でpropertyとNSObjectをつなげば、delegate script側から外部scriptのハンドラを呼び出せるようになります。まだ、ここまでは「準備」の部分です。

asoc62.png

ハンドラの書き方、呼び出し方が問題

〜delegate scriptから外部script上のハンドラを呼び出す

asoc7.png

外部script呼び出し用に作成したproperty(ここでは、diffLib)を用い、

  diffLib’s testMe:”TEST”

のように書きます。

呼び出される側のハンドラ側についてはOS X 10.10では、(1)パラメータの型指定、および(2)パラメータ省略時のデフォルト値指定ができるようになっていますが、ASOC on Xcodeでは(1)のみ可能でした。

  on testMe:aStr as string–parameter casting

(2)はコンパイル時にエラーになってしまいます。

  on testMe:aStr as string:”"–default value–Error
  on testMe(aParam as string:”")–Error

外部script内で他のハンドラを呼び出す

asoc8.png

ここで問題。Script Editor上のASOCで書いていたハンドラは、

  testSub(param1,param2)

のような書き方を(自分は)していたわけですが、これをすべてASOC流の、

  testSubWithp1: p2:

などと書き換えるのは大変です。

試行錯誤してみたところ、これを、

  my testSub(param1,param2)

と書けば、あまりASOC on Script Editorのハンドラ名称を書き換えなくても大丈夫でした(よかった)。myと書かないとコンパイルが通らないパターンとか、of meでもコンパイルが通るパターンもあるようで・・・安全のためにmyを使っています。

外部Scriptにおけるuse文

まだプロジェクトが終わっていないので、暫定情報ですが・・・

ASOC on Xcode上ではuse frameworkは書くとエラーになるので書きません。

一応、use AppleScript version “2.4″とかuse scripting additionsといった記述をscript節の内側に書いています。

asoc9.png

だいたいこんな感じでしょうか。試行錯誤をいろいろ行ったおかげで、思ったよりもうまく行っています。

2015/01/19 script節の中にASObjCExtrasの命令を記述する

ASObjCExtras.frameworkの機能を使って、いろいろと大きなScriptを書きはじめました。

そうすると、Script文で分割された巨大なAppleScriptなどもあるわけで・・・script節の中にASObjCExtrasの命令だったり、普通のCocoaの機能呼び出しの命令を書こうとして、実行時にエラーになったというところから話がはじまります。

プレーンなASOCのScriptだとこんな感じです。Script Editor上に記述して、実行できます。

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

set a to retVerInfo()
–> “SMSFord 1.2.2″

on retVerInfo()
  set theVersionInfo to current application’s SMSFord’s |description|() as text
  
return theVersionInfo
end retVerInfo

★Click Here to Open This Script 

こうした書き方では何も問題はありません。

問題となるのはこんなふうに(step2)、script文(script clause)でくくった場合です。エラーになって実行できません。場合によっては、エディタごとクラッシュしてしまいます。

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

set a to retVerInfo() of aLib
–> error “SMSFord は“description”メッセージを認識できません。” number -1708 from SMSFord

script aLib
  on retVerInfo()
    set theVersionInfo to current application’s SMSFord’s |description|() as text
    
return theVersionInfo
  end retVerInfo
end script

★Click Here to Open This Script 

いろいろと試行錯誤してみたところ、Xcode上のASOCのScriptと見比べてみて、parentの宣言が足りないということがわかりました。これに加えて、script節の冒頭にuse文を書いてframeworkの使用を宣言してみました。

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

set a to retVerInfo() of aLib
–> “SMSFord 1.2.2″

script aLib
  
  
use framework “Foundation”
  
use framework “ASObjCExtras”
  
  
property parent : class “NSObject”
  
  
on retVerInfo()
    set theVersionInfo to current application’s SMSFord’s |description|() as text
    
display dialog theVersionInfo
    
    
return theVersionInfo
  end retVerInfo
  
end script

★Click Here to Open This Script 

Xcode上で記述するASOCのscriptにこうしたASObjCExtrasの利用を広げようとした場合、メインScript以外はこうした記述になるわけで、script節の中に記述する方法を明らかにしておく必要があったのでした。

2015/01/19 2Dリストから、指定アイテムNoで、指定データが該当する最初のものを返す v3

2D Listから、指定アイテムNoが指定データとイコールの要素を返すAppleScriptです。

{{”piyomaru”, 1}, {”Piyomaru Software”, 2}, {”Naganoya”, 3}, {”Takaaki”, 4}, {”MacBook Pro Retina mid 2012″, 5}}

という2D Listがあった場合に、各アイテム中のアイテムNo.が1(item 1)のデータが”Takaaki”のものについて、

{”Takaaki”, 4}

というデータを、該当するデータがなかった場合には{}を返します。

AppleScript名:2Dリストから、指定アイテムNoで、指定データが該当する最初のものを返す v3
– Created 2015-01-19 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set aList to {{"piyomaru", 1}, {"Piyomaru Software", 2}, {"Naganoya", 3}, {"Takaaki", 4}, {"MacBook Pro Retina mid 2012", 5}}

set gList to searchInListByIndexItem(aList, 1, "Takaaki")
–> {"Takaaki", 4}

–2Dリストから、指定インデックスアイテムで、指定データが該当する最初のものを返す
on searchInListByIndexItem(aList as list, itemNum as integer, hitData as string)
  –ListからNSMutableSetへの型変換
  
set setKey to current application’s NSMutableSet’s setWithArray:aList
  
  
–Predicate Stringを組み立てる
  
if itemNum < 1 then return {}
  
set aPredicateStr to ("SELF[" & (itemNum - 1) as string) & "] == ’" & hitData & "’"
  
  
–抽出
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicateStr
  
set aRes to (setKey’s filteredSetUsingPredicate:aPredicate)
  
set bRes to aRes’s allObjects()
  
  
–NSArrayからListに型変換して返す
  
set cRes to (bRes’s ASify()) as list
  
  
if cRes is not equal to {} then
    set cRes to contents of first item of cRes
  end if
  
  
return cRes
end searchInListByIndexItem

★Click Here to Open This Script 

2015/01/16 FM13をASから骨までしゃぶる

仕事で用件があってFileMaker Pro 13 AdvancedをOS X 10.10の環境にインストールしてみました。

一応最新のバージョンということになりますが、1年に1度バージョンアップを行ってきたFileMaker Proとしては、そろそろ13.5なり14なりのバージョンが出てもおかしくない頃です。

OS X 10.10のAppleScript環境に、FileMaker Pro 13はどの程度追いついているのでしょうか?(AppleScript用語辞書自体は、FM Pro v11から変わっていません)。

fm13_1a.png

Scriptステップ「AppleScriptを実行」にASOCのScriptを書いてみる

OS X 10.10で普通のAppleScriptにASOCのScriptをまぜて書けるようになったので、FileMaker ProのScriptステップ「AppleScriptを実行」にASOCを書いてみましょう。

fm13_2a.png

ASObjCExtras.frameworkのテスト用の機能を呼び出してみます。

fm13_3.png

予想外の結果が! 

fm13_4.png

ASOBjCExtras.frameworkは64bit onlyのframework。・・・ということは・・・FileMaker Pro 13というのは・・・

fm13_5.png

fm13_6.png

32bitアプリケーションだったんですね(汗) Shane Stanleyに一応フィードバックしておきましょう。

一応、ASObjCExtras.frameworkを使わないASOCのプログラムを実行させてみたら、「AppleScriptを実行」から実行できました。ただ、ASObjCExtrasが使えないと不便で仕方ないので、使えるようになってほしいところです。

# Xcode上でビルドオプションを変更すれば済むような話でもなさそうなので、実現するかどうかはちょっとわからないです、、、どうせFMも毎年アップデートするので、、、じきに64bitに、、、

JSXを書いて実行してみる!

これは、あっさり却下されました。記入欄にAppleScriptの文法にマッチしないものを書き込むとエラーになります。

ASOC経由でJavaScriptを実行してみる

ASOC経由でJavaScript Coreの機能を呼び出して、JavaScriptを実行させたところ実行できました。また、WebViewを動的に生成してJavaScriptの実行を行い、そちらもうまく実行できました。やはり、WebViewが使えると「できること」の範囲が広がります。

# 最初、WebViewの動的生成&JS実行は行えない、と書きましたが・・・追試を行い、問題なく実行できることを確認しました

異なるバージョンのFM間でScriptを相互呼び出し

異なるバージョンのFMを(2つ同時に起動しておいて)、「AppleScriptを実行」から異なるバージョンのFM DB上のScriptを実行できました。

2015/01/15 セキュリティ上、リスキーな技術

AppleScriptで自動処理システムを構築する際に、「リスキーな」「危険度の高い」技術というものがあります。

OS X 10.6以降、セキュリティポリシーが大幅に以前とは変更され、「怪しい動きをしたプログラムはkillされる」ようになったと感じています。

ここでは、開発マシン=OS X 10.10.1、ターゲットマシン=10.9.5で開発、運用した場合にハマった例をご紹介します(Custom URL Protocol以外この環境)。

(1)GUI Scripting

Appletを連続して動かすような場合に、GUI Scriptingを使っていると問題に直面するケースがあります。

どうもGUI Scriptingを同時に許可するAppletの数に上限があるようで、サブプログラムを8個ほどAppletにして起動させたままにしておいたところ、実行環境でいくつかのAppletは GUI Scriptingを許可されませんでした。

fig1.png

結局、この「GUI Scriptingを許可する暗黙の上限」を回避するため、Sub-AppletをMain Appletの中に読み込むようにプログラムを書き換えました。

fig2.png

この(↑)形態に書き換えたら、実行環境上で問題なく動作するようになりました。

(2)Custom URL Protocol

Appleが最も目の敵にしているのが、このCustom URL Protocol。

mailto:とかhttp:といったURL Protocolは一般的ですし、本Blogでもapplescript:というURL Protocolをプログラムリストの末尾に入れてScript EditorにScriptを転送させていますが・・・ユーザー(開発者)が定義したCustom URL Protocolは、その動作が厳しく監視されています。

Custom URL Protocol経由でAppleScriptを起動して、さらにそこから他のアプリケーションに命令を出したりすると、1つぐらいは大丈夫なんですが、複数・・・だいたい3つぐらいのアプリケーションに命令を出したりすると、OS側が「疑わしい動作」と判断してクラッシュさせられるケースが多いと感じています。

fig4.png

明文化もされていないし、Appleからの発表もありませんが、Custom URL Protocolを利用するようなツールで調子にのって大規模なAppleScriptを書くと、思わぬ落とし穴にハマる危険があります。

(3)AppleScript Libraries

自分も目を疑ったのが、このAppleScript Librariesの導入にともなうクラッシュ。システムのメンテナンスを行いやすいようにAppleScript Librariesを活用するように書き換えてみたら・・・

fig3.png

ターゲットマシン上でクラッシュするようになってしまいました(T_T)。開発マシン上では問題なく動くのですが、ターゲットマシンに持っていくと100%クラッシュしました。

ちなみに、これを・・・

fig2.png

の形式に差し戻したら、クラッシュしなくなりました。

ターゲット環境がOS X 10.9だったので、10.9上でのAppleScript Librariesの管理機能がまだこなれていなかったために発生した問題なのか、それとも開発環境/ターゲット環境の(OSバージョン以外の)違いが引き起こした問題なのか、具体的な発生源の追求できるところまで時間がありませんでした。このような構成で、問題が発生したという事実のみ明記しておきます。

たかだかAppleScriptのAppletをただ漫然と動かしていても(Code Signはしているんですが、、、)、OS側の疑惑の目を向けられるとは・・・非常にやりにくいところです。少なくとも開発者にはルールを明かしてほしいところです。

2015/01/14 NSMutableIndexSetの初期化、作成

NSMutableIndexSetの初期化、データ作成のサンプルです。

NSMutableIndexSetの作成について、初期化の仕方がいまひとつ分かりませんでしたが、Shane Stanleyから「こうやればいいよ」と教えてもらったScript(そのまんま)です。

AppleScript名:NSMutableIndexSetの初期化、作成
–By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set anIndexSet to current application’s NSMutableIndexSet’s indexSet() –initialize
anIndexSet’s addIndexesInRange:(current application’s NSMakeRange(3, 20)) –make range

set aList to (current application’s SMSFord’s arrayWithIndexSet:anIndexSet) as list
–>  {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}

★Click Here to Open This Script 

2015/01/14 ASObjCExtras.frameworkのScripting Guideを公開

本Blogでも多用しているShane Stanleyの「ASObjCExtras.framework」のScripting Guideを公開しました。フリーでご利用いただけます。すべて英文ですが、図や表を中心に構成しているため、そんなに難しくないと思います。

scripting_guide1.png

当初、Shane StanleyからASObjCExtras.frameworkのβ版の評価を依頼され、「Shane Stanleyのお願いだったら断るわけにはいかないでしょう」と、細々と始めたこの作業。ここまで行くとは思いませんでした(汗)。

ASObjCExtras.frameworkにはヘッダーファイルに詳細な説明があるんですが、あくまでObjective-Cを書ける人向けで、Scripterが見てもちょっと分かりづらいものでした。ASObjCExtras.frameworkについては、強力で桁違いの処理速度を実現できるので、とっつきづらいのは非常にもったいないと考えました。

また、すべての機能についてサンプルScriptが用意されているわけでもないので、実際に使ってみようとすると、かなりの試行錯誤が必要でした(過去形)。一部の機能については「これって必要なんだろうか?」と思うものもあります(用途がいまひとつイメージできない、とか)。

その逆に、「こういう命令は必要」というフィードバックを行い、追加された命令もいろいろあります。

評価・・・ということは「実際に使ってみる」ということで、実際にScriptを書いてみないと動きや機能が分かりません。必然的に、ASObjCExtras.frameworkのすべてのmethodを使ったサンプルScriptが手元にたまるわけで・・・なら、それを(モノによっては簡略化して)ドキュメントにまとめれば、Scripting ManualになるよねーということでShane Stanleyに提案。

img_2800.JPG

Scripting Guideで注意したのは、Cocoaの解説書を読んでいてつねづね思っていた「パラメータが具体的に何なのか分かりにくい」点への配慮でした。method名に書かれているパラメータがObjective-Cに不慣れな人間にはとても分かりづらく、しかもパラメータ数が増えると何がなんだかわからなくなるので、,箸△箸い辰慎号を用いて説明してみました。ここは、他の解説書にない改良点だと思います。

cocoa_method_reference.png

ドキュメントはすべてPagesで作成しましたが、このPagesにとても苦労させられました。とくにPagesの機能のなさ(リンクはPDFに書き出してAcrobat上で手で作成したし。ドキュメント内のリンク作成機能がないのは勘弁してほしいです)に苦しめられました。再利用のしやすさ(iBooks Autherに持って行きやすいとか)からPagesを選択しましたが、こんなに苦労するとは。

本Scripting Guideは、作成中のAppleScript本の体裁をほぼそのまま利用したので、そちらで作っている内容の検証とか、Pagesのノウハウ蓄積とか、そういう意味で今後の作業に(個人的に)フィードバックできるものと思われます。AppleScript本については、紙で出すとか出さないとかいう話もあるのですが、まだちょっと分かりません。

2015/01/13 Display情報の取得

NSScreenの情報にアクセスして、各ディスプレイの詳細情報を取得するAppleScriptです。

img_0866.png

非Retina DisplayはResolutionが{width:72.0, height:72.0}、MacBook Pro Retina 2012のRetina Displayは{width:144.0, height:144.0}と返ってきます。

screens0.png

ただ、結果を見直してみると・・・RetinaではないDisplayでも{width:144.0, height:144.0}と返ってくるケースがあり、実際にScreen CaptureをそのDisplay上で行うとRetina解像度のキャプチャ画像が取得されたりして、ソフトウェア的なResolutionのことを指しているのではないかとも思われます。

moni12.png

その割に、同一構成でMacBook Pro Retina本体をLid Closed Mode運用させると(本体を閉じると)、Resolutionで軒並み{width:72.0, height:72.0}が返ってくるあたりが悩ましいです。

ここでは、「OSが返してくる値がこうなっている」という事実のみ書いておきます。ご覧の通り、とくにScript側で数値に手心を加えているわけではありません。

moni3.png

上記のように、同一の表示&同一のDisplay上でのScreen Capture画像を比較した場合に、Retina Displayを含むモニター群の場合の画面キャプチャと、含まない場合の画面キャプチャでは解像度が異なっていました。仮想画面自体が、Retina Modeと非Retina Modeの切り替えを行うのかもしれません。

なお、本ScriptはCocoa経由でデスクトップピクチャを調査しようとして作成した副産物です(でも、AppleScript経由で調べたほうが圧倒的に楽、、、、)。

AppleScript名:display情報の取得
– Created 2015-01-13 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AppKit”

–Retina Displayを含む構成のとき
set dInfoList to retScreenInfos()
–>  {{screenSize:{{width:1920.0, height:1200.0}}, screenResol:{{width:144.0, height:144.0}}, screenIsScreen:true, screenNumber:”69501832″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}, {screenSize:{{width:1920.0, height:1200.0}}, screenResol:{{width:144.0, height:144.0}}, screenIsScreen:true, screenNumber:”69513475″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}, {screenSize:{{width:1920.0, height:1200.0}}, screenResol:{{width:144.0, height:144.0}}, screenIsScreen:true, screenNumber:”69731202″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}, {screenSize:{{width:1920.0, height:1080.0}}, screenResol:{{width:72.0, height:72.0}}, screenIsScreen:true, screenNumber:”458586661″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}}

–Retina Displayを含まない構成のとき(MacBook Pro Retina本体のLid Closed Mode時)
set dInfoList to retScreenInfos()
–>  {{screenSize:{{width:1920.0, height:1200.0}}, screenResol:{{width:72.0, height:72.0}}, screenIsScreen:true, screenNumber:”69501832″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}, {screenSize:{{width:1920.0, height:1200.0}}, screenResol:{{width:72.0, height:72.0}}, screenIsScreen:true, screenNumber:”69513475″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}, {screenSize:{{width:1920.0, height:1080.0}}, screenResol:{{width:72.0, height:72.0}}, screenIsScreen:true, screenNumber:”458586661″, screenColorSpace:”NSCalibratedRGBColorSpace”, screenColDepth:”8″}}

on retScreenInfos()
  set sList to (current application’s NSScreen’s screens()) as list
  
  
set dList to {}
  
repeat with i in sList
    set a to i’s deviceDescription()
    
set aSize to a’s NSDeviceSize as list
    
set aResol to a’s NSDeviceResolution as list
    
set aScrn to a’s NSDeviceIsScreen as boolean
    
set aNum to a’s NSScreenNumber as string
    
set aColSpc to a’s NSDeviceColorSpaceName as string
    
set aColDepth to a’s NSDeviceBitsPerSample as string
    
set the end of dList to {screenSize:aSize, screenResol:aResol, screenIsScreen:aScrn, screenNumber:aNum, screenColorSpace:aColSpc, screenColDepth:aColDepth}
  end repeat
  
  
return dList
  
end retScreenInfos

★Click Here to Open This Script 

2015/01/09 Keynote, Pages, Numbersがアップデート。AppleScript用語辞書は変更なし

Keynote、Pages、Numbersがアップデートされ、それぞれ、

 Keynote:Version 6.5.2
 Pages:Version 5.5.2
 Numbers:Version 3.5.2

となりました。マイナーバージョンアップであり、セキュリティ関連のみのアップデートで、AppleScript用語辞書には変更はありません。

毎回、例によってAS Dictionary(Now, By MugginSoft)でAppleScript用語辞書をHTMLに(AppleScriptで)書き出しして、FileMergeでdiffをとって用語辞書の差分を検出しており、今回は変更が検出されませんでした。

keynote.png

pages.png

numbers.png

2015/01/07 ASObjCExtras ICU Transformのサンプル

ASObjCExtras.frameworkのScripting Manualを作成中で、すべての命令にサンプルコードを掲載しています。その一環としてテストしたサンプルScriptです。

scripting_manual.png

AppleScriptの世界ではICU(International Components for Unicode)は縁がない仕組みですが、ASObjCExtrasからその機能を呼び出せます(stringFrom: ICUTransform: inverse:)。

ICUのWebを見ると、いろいろと便利な変換機能が利用できることが見て取れますが、各言語間のアルファベット的な「変換」はしてくれても「翻訳」ではないので、その性質を知っておくことが重要と思われました。

半角-全角変換などはこのICUを使うと手軽に処理できてよいでしょう。

AppleScript名:ICUTransformのサンプル
–Sample Code
– Created 2015-01-06 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set theString to “ながのや, たかあき” –Hiragana
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Hiragana-Latin” inverse:false) as text
–> “naganoya, takaaki”

set theString to “ながのや, たかあき” –Hiragana
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Hiragana-Katakana” inverse:false) as text
–> “ナガノヤ, タカアキ”–Katakana

set theString to “Takaaki, Naganoya”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Hiragana-Latin” inverse:true) as text
–> “たかあき、 ながのや”–Hiragana

set theString to “Takaaki, Naganoya”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Katakana-Latin” inverse:true) as text
–> “タカアキ、 ナガノヤ”–Katakana

set theString to “Shane, Stanley”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Katakana-Latin” inverse:true) as text
–> “シャネ、 スタンレイ”–Katakana…..this seems odd. “シェーン, スタンリー” will be a right spelling

set theString to “Takaaki, Naganoya”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Fullwidth-Halfwidth” inverse:true) as text
–> “Takaaki, Naganoya”–Double Width Alphabet

set theString to “Naganoya, Takaaki”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Latin-Hangul” inverse:false) as text
–> “나가노야, 타카아키“–Hangul

★Click Here to Open This Script 

2015/01/07 AS Holeのアクセス推移

as_hole.png

2008年から約7年続いている本Blog「AS Hole」のアクセス推移です。記事数の推移に連動してアクセス数が増えているように見えるんですが、ここ2年ぐらいはスパムコメント数がひどく(スパムフィルタで弾いています)、純粋に数字どおり(Hit)の伸びが実態とは受け取っていません。ただ、Page Viewあたりは「そんな感じかな」という気がします(年間合計100万PV)。

本Blogを運営していて感じるのは、読者がいるのかいないのか、ひたすら不明といったところです。いわゆる常連さんというか固定読者はあんまりいなさそうなんですが、Googleの検索エンジンでやたらとヒットするので、うすく広く知られている感じ。

そもそも、人工知能風インタフェース「Newt On」(2002年)とか音声認識インタフェース「Kotodama」(2003年)とかの大プロジェクトが終わったときに、その中で使用したルーチンの山をひとりでメンテナンスするのが大変だったので、小分けにしてシャッフル掲載して、フィードバックを得ることでOSアップデート時のメンテナンス負荷を下げよう、というのがスタート地点でありました。

最近は、どちらかといえば海外からの反応があるので、記事を日本語ではなく英語で書くことを真剣に考えたほうがよいのかもしれません。

2015/01/07 【基礎】アプリケーションの操作は、用語辞書に書いてあるとおり記述しないと動かない

「コンピュータは、あなたが思ったとおりには動かないが、操作したとおりに動く」

名言だと思います。同様に、

「プログラムは、あなたが思った/願ったようには動かないが、書いたとおりに動く」

と言い換えることが可能です。さらに、

「AppleScriptは、あなたが願ったようには動かないが、書いたとおりに動く」

とも言い換えられます。とくに、アプリケーションの操作については「AppleScript用語辞書」に書いてあるとおりに書くのが鉄則です。それ以外の書き方をして「動いてしまった」としても、その方が不思議なわけで。

自分でも、海外のScripter連中でもそうだと思うのですが、Scriptを書いている最中は、AppleScript用語辞書を数枚ひらきっぱなしです。AppleScriptObjCのプログラムを書いているときには、AppleのReferenceサイトも表示させっぱなしです。さらに難問になってくるとUS AppleのAppleScript Users MLとか、www.macscripter.netとかを検索しまくることになり複数モニタが欠かせません(3枚使っているといったらShane Stanleyに「ずいぶん枚数多いな!」と驚かれましたが、、、)。

ひと昔前(Classic Mac OSの時代)、AppleScript用語辞書はわざわざ人間(開発者)が書くもので、さらに実際のアプリケーション側の機能とリンクしていない「ただの書き方見本」だったので、「用語辞書には書いていないけれど使える」とかいう「隠し命令」なんてものもありました(初代のEntourageとか)。単なる書きもれ、ケアレスミスでしたが、マニアさんの間では「隠し命令」の存在がちょっと「通」な話題になっていたりしました。

いまのAppleScript用語辞書はXMLファイル(sdefファイル)で、この用語辞書がイコールAppleEventの解釈用の辞書であって、「書き方見本」ではありません。そのため「隠し命令」が存在する余地というのはありません。逆にいえば、用語辞書のとおりに動かなかったら完全なバグなわけです(実装が「不完全」「残念」なために期待したとおりに動かないというKeynote/Numbers/Pagesは例外として)。

たまたま、魔が差してTwitter上で議論になったのですが・・・アプリケーションにファイルをオープンさせる場合には、AppleScript用語辞書をScript Editorでオープンして、コマンドなりオブジェクトなりの使い方を調べることになります。このあたり、Objective-CでCocoaのAPIの使い方をAppleのサイトで調べながら書くのと同じです。AppleScript用語辞書は、アプリケーションバンドル内にあってScript Editorからオープンできます。

textedit_asdic.png

で、この「AppleScript用語辞書を見る」ことをしない方がけっこう多いようで・・・逆にこれを見ないでよくプログラムが書けるもんだと感心してしまうんですが、用語辞書を見ないとハマりやすいんですね。

前述のように、アプリケーションの操作は「決められたとおりに書かないと正しく動かない」ものであり、さらにその先に「ファイルをオープンもしないで中を調べたりはできないよ」といったアプリケーションの挙動(経験則に基づく)の話になるわけなんですけれども、まずは用語辞書を見ないと分かりません。

アプリケーションで書類をオープンする際には、ごく一部の残念な例外(Adobeのアプリ)をのぞいては、パス情報をaliasにしてopenコマンドに渡す必要があります。

textedit_open.png

ここで、POSIX pathやらfileやらを渡してもオープンはしないわけです。

AppleScript用語辞書には「openコマンドにはaliasを渡してね」と書いてあるので、

textedit_open.png

alias以外を渡すのはアウトです(aliasのlistはOK)。それ以外の形式のパス情報を渡して、たまたま間違って動いていたとしても、たまたまです。それ以上でも、それ以下でもありません。

最近は、AppleScript用語辞書にHTMLコンテンツを入れることができるようになり、一部のアプリケーションでは用語辞書内にサンプルScriptを掲載しだして、「サンプルをそのままコピペで動く」いい時代になってきたはずなんですが、これまた残念なことに「Apple社内の連中が書くScriptが絶望的に読みにくい」ために(theとかresultとか使いまくる&1行を長く記述して初心者にわかりにくい)、サンプルを読むと逆に理解しづらくなるという事態が(ーー;;

もういっそのこと、アプリケーションバンドル内に、典型的な利用法を記述したAppleScript Librariesを内蔵してしまって、Scriptから呼び出せるようにすべきではないかとも考える次第です。

余談:

途中から(OS X 10.8あたり?)挙動が変わってしまって困っていた、Mail.appのmove命令。前は複数のmessageをlistに入れて一気にmoveできていたのが、1つのmessageしかmoveできないように変わり、処理速度を稼げなくなっていました(複数一度にmoveできたほうが速い)。

いましらべたら、

mailapp_move.png

複数のmessageを示すobject(s)の表記がありますね。でも、「Move an object to new location」とも書いてあり・・・微妙な。