Archive for the 'AppleScript Studio' Category

05/04 Xcode 3.2.2+Snow LeopardでAppleScript Studio環境を復活させる

Twitter経由で、「AppleScript applications with Xcode 3.2.2」なる記事を見つけました。

記事を読んで、最初はわけが分からなかったのですが……以下の手順でできることを確認しました。

(1)S.J. Louw氏が配布している「AppleScript-App-Templates-Xcode32.pkg」をダウンロードしてインストール。「Download Link」の文字が、黒地にグレーの文字で見つけにくいので要注意!!

downlink.jpg
▲視力検査なのか?!

inst0.jpg

(2)Terminal上で、「defaults write com.apple.InterfaceBuilder3 IBEnableAppleScriptStudioSupport -bool YES」を実行

inst011.jpg

(3)Xcode 3.2.2を起動して、「Application」テンプレートの中に新たにできた「AppleScript Application」をクリック

ass1.jpg

以下、従来どおりのAppleScript Studio環境(Xcode+Interface Builder)がMac OS X 10.6.3+Xcode 3.2.2の環境で使えることを確認しました。

ただ、Interface Builder上で使用できるGUI部品に一部制限があるようで、新しいルック&フィールをベースにしたGUI部品はAppleScript Studio環境では表示されなかったりします。

それでも、AppleScript Studio環境がMac OS X 10.6上で使用できることの意義は大きいと思います。長い目で見ればAppleScript ObjCに移行した方が「できること」が大幅に増えるので、移行すべきだとは思うのですが……そうそう急にAppleScript ObjCによる開発の仕事が発生するものでもないですし(汗)

以下、インストール手順を紹介します。

inst011.jpg

TerminalからAppleScript Studio復活コマンドを実行。以前からAppleがこれを紹介していたが、そのとおりにやってもASSは復活しなかった。Appleはダンマリを決め込んだまま……。ちなみに、これってインストーラーパッケージの中で実行するようにしておけばいいんじゃないかと>S.J. Louw氏

注釈:S.J.Louw氏本人からコメントがあって、「インストーラのScript中で実行しているから、わざわざコマンドラインで実行する必要はないよ」だそうです(汗) 話が早くてけっこうなことで。

inst1.jpg

▲インストーラをダウンロードして実行

inst2.jpg

▲Xcodeが起動中であればいったん終了させてくれ、とメッセージ表示。Interface Builderも同様

ass1.jpg

▲なつかしの、AppleScript Studioテンプレートが選択可能に

ass2.jpg

▲Xcode 3.2.2上でAppleScript Studioプロジェクトを新規作成したところ

ass3.jpg

▲XcodeでXibファイルをダブルクリックしてInterface Builderを起動。IBのインスペクタでAppleScript Studioの各種イベントハンドラが指定可能になっていることを確認できます

ass4.jpg

▲かる〜いご挨拶。これ1行を書いただけでバグが発覚したXcodeの恥ずかしいリリース版とかも過去にありました

ass5.jpg

▲ビルドして実行したところ。きちんと日本語が表示されています

11/08 AppleScriptの文字列比較(1)

AppleScriptについて聞かれるのが、文字列比較についての独特な表現についてです。

とりあえず、基本的な文字列比較について挙げておきましょう。

なお、イコールの表記についてはいくつか同義語が定義されており、「同じことを書くのに人によって記法が異なる」という傾向を生み出しています。

「is equal to」「is」「=」の3つがすべてイコールとして機能します。

なまりの強いAppleScriptのプログラムを手本とした場合に、よその人が書いたAppleScriptを見ても理解できなかったりするところです。

本Blogでは、海外のScripterとの交流の中で感じた、なるべく平易で分かりやすい記法を重視していますが、1つだけ例外があります。

不等号や等号付き不等号などにも別の表記方法があり……極力記号を使いたいところですが……これまでAppleScript Studio環境ではそれらの等号付き不等号がコンパイル時にハネられるとか、エラー発生原因になることが多く、UTF-8でソースコードを書けるようになったMac OS X 10.5までは、等号つき不等号を英語的な別の表記で書かざるを得なかった、という歴史的経緯があります。

等号つき不等号については、記号で書いた方が分かりやすいのですが、仕方なく英語的表記で記述していた、ということです。

スクリプト名:文字列比較
set a to “abc”
set b to “c”

–イコールかどうか?
if a is equal to b then
  display dialog “Equal”
else
  display dialog “Not Equal”
end if

–前方一致
if a begins with b then
  display dialog “begins with”
else
  display dialog “Not begins with”
end if

–後方一致
if a ends with b then
  display dialog “ends with”
else
  display dialog “Not ends with”
end if

–部分一致
if a contains b then
  display dialog “contains”
else
  display dialog “not contains”
end if

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

02/28 diskutilitiesで起動HDDのユーザー権限修復

HDDのメンテナンスを行うMac OS X標準添付ツール「ディスクユーティリティ」はAppleScript対応ではありませんが、コマンドラインから使えるcui版の/usr/sbin/diskutilを呼び出すことで同様の機能をAppleScriptから利用できます。

diskutil.jpg

「ユーザー権限の修復」なんて、AppleScriptにはまったく関係ないのでは? といえばさにあらず。作成したAppleScript(GUIつきのAppleScript Studioアプリ)をユーザーに配布したときに、「ユーザー権限がないのでファイルを書き込めないというエラーに遭遇した」というレポートがあがってきたことがありました。

この問題をつぶさに調査したところ、ユーザーのホームディレクトリ下のtemporary itemsフォルダ(path to temporary items from user domain)に作業ファイルを書き込もうとしたところ、ユーザーがディスクユーティリティでHDDのメンテナンスをまったく行っていなかったために、ユーザー権限が正しく設定されていなかったため、と判明しました。

path to temporary items from system domainで同様の問題に遭遇したことはなかったのですが、Mac OS X 10.5からはデフォルトのtemporary items folderがpath to temporary items from user domainに変更になったため、その流儀に則ってScriptを書いたのですが……それが裏目に出たということでしょうか。

ディスクユーティリティによるHDDのメンテナンスをユーザーに強制できるものでもないので、処理が正確に行われることを期待するのであれば、AppleScript(AppleScript Studioアプリ)の初回起動時なり、インストーラー実行時にインストーラースクリプト中でdiskutilを実行しておくとよいかもしれません。

diskutilコマンドには有用な機能が多いので、man pageの内容をひととおり確認しておくことをおすすめします。

スクリプト名:diskutilitiesで起動HDDのユーザー権限修復
with timeout of 3600 seconds
  do shell script "diskutil repairPermissions /" with administrator privileges
end timeout

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

02/21 pdfinfoの結果をparseするv2

フリーのxpdf-toolsに含まれるpdfinfoを呼び出して、その結果をレコードにparseするAppleScriptです。

レコードの属性ラベルにパーティカルバー(「|」)を付けているのは、AppleScript Studioアプリ内でTableViewに突っ込むことを目的として作成したからです。

これまでに作成したサブルーチンを組み合わせた程度のものですが、たいへん便利に使っています。

→ 問題点が見つかったので、新バージョン(v3)を使用してください。

スクリプト名:pdfinfoの結果をparseするv2
set aRes to Title: iPhone Coding HowTo-J
Author: Takahiro Kaneko
Creator: Pages
Producer: Mac OS X 10.5.3 Quartz PDFContext
CreationDate: Thu Jun 26 04:09:03 2008
ModDate: Thu Jun 26 04:09:03 2008
Tagged: no
Pages: 31
Encrypted: no
Page size: 595 x 842 pts (A4)
File size: 988819 bytes
Optimized: no
PDF version: 1.3

set bRes to parseInfopdfData(aRes) of me

on parseInfopdfData(aRes)
  set aList to paragraphs of aRes
  
set aRec to {|aTitle|:”", |aSubject|:”", |anAuthor|:”", |aCreator|:”", |aProducer|:”", |aCreationDate|:”", |aModDate|:”", |aTagged|:”", |aPages|:”", |anEncrypted|:”", |aPageSize|:”", |aFilesize|:”", |anOptimized|:”", |aPDFver|:”"}
  
repeat with i in aList
    set j to contents of i
    
先にデータのみにしておいて…
    
set bRes to fetchDataAfterColon(j) of me
    
レコードの中身を指定する
    
if j begins with Title: then
      set |aTitle| of aRec to bRes
    else if j begins with Subject: then
      set |aSubject| of aRec to bRes
    else if j begins with Author: then
      set |anAuthor| of aRec to bRes
    else if j begins with Creator: then
      set |aCreator| of aRec to bRes
    else if j begins with Producer: then
      set |aProducer| of aRec to bRes
    else if j begins with CreationDate: then
      set |aCreationDate| of aRec to bRes
    else if j begins with ModDate: then
      set |aModDate| of aRec to bRes
    else if j begins with Tagged: then
      set |aTagged| of aRec to bRes
    else if j begins with Pages: then
      set |aPages| of aRec to bRes
    else if j begins with Encrypted: then
      set |anEncrypted| of aRec to bRes
    else if j begins with Page size: then
      set |aPageSize| of aRec to changePTStoMM(bRes) of me
    else if j begins with File size: then
      set |aFilesize| of aRec to bRes
    else if j begins with Optimized: then
      set |anOptimized| of aRec to bRes
    else if j begins with PDF version: then
      set |aPDFver| of aRec to bRes
    end if
  end repeat
  
return aRec
end parseInfopdfData

コロンの次の文字からスペースではない文字をスキップして残りの文字を返す
on fetchDataAfterColon(aData)
  set cList to characters of aData
  
set sPos to offset of : in aData
  
set sPos to sPos + 1
  
repeat with i from sPos to (length of cList)
    if contents of item i of cList is not equal to then
      set aRes to (items i thru -1 of cList) as string
      
return aRes
    end if
  end repeat
  
return “”
end fetchDataAfterColon

on parseByDelim(aData, aDelim)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set dList to text items of aData
  
set AppleScript’s text item delimiters to curDelim
  
return dList
end parseByDelim

ポイント数値のテキストをmmに書き換える
on changePTStoMM(aText)
  set wList to words of aText
  
  
set attrList to {}
  
repeat with i in wList
    set the end of attrList to numChk(i) of me
  end repeat
  
  
set aCount to count wList
  
  
set aReText to {}
  
repeat with i from 1 to (aCount - 1)
    set anItem to item i of wList
    
set anAttr to item i of attrList
    
if anAttr = true then
      set the end of aReText to (point2mm(anItem as number) of me) as string
    else
      set the end of aReText to anItem
    end if
  end repeat
  
set the end of aReText to “mm”
  
set last item of aReText to mm
  
  
set aText to makeStrFromListWithDelim(” “, aReText) of me
  
return aText
  
end changePTStoMM

リストを任意のデリミタ付きでテキストに
on makeStrFromListWithDelim(aDelim, aList)
  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 makeStrFromListWithDelim

ポイントからmmへの変換
on point2mm(a)
  return (a * 0.353) 1ポイント=0.353mm
end point2mm

数値かどうか調べる
on numChk(aNum)
  set aClass to (class of aNum) as string
  
if aClass = number or aClass = double or aClass = integer or aClass = real then
    return true
  else if aClass = string or aClass = text or aClass = unicode text then
    try
      set bNum to aNum as number
      
return true
    on error
      return false
    end try
  end if
end numChk

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

12/20 特定の文字ではじまるリスト要素を削除して返す

改行によって区切られたテキストのうち、コメント行や改行のみの行を除外するために作成したサブルーチンです。AppleScript Studioのプロジェクトで、text viewに各種データを記入してもらうようにしておいて、その中にコメントを記入できるようにすることを意図したものです。
(more…)

08/11 ポップアップメニュー項目の高速生成

AppleScript Studioで、ポップアップメニューの項目を追加する場合には、一度popup buttonのmenu内のmenu itemを削除してから、リストに入った項目をひたすらループで追加していくのが常でしたが……海外のMLの過去ログを調べてみたら、call method命令で一括追加する方法を発掘しました。

InDesign CS3用のScriptアプリで、使用可能な書体をすべて取得してpopup menuに追加しようとしたら……システム中に600書体ぐらいあって(汗) さらに、1画面中にそれが複数個あったりして(汗) popup buttonにメニュー項目を追加したときに、それなりに待たされてしまうので困っていました。このルーチンを使うと、激的に高速化できます。

call method命令だとオーバーヘッドが大きいので(cocoa分らないと書けないし、、、)、よほどの効果が期待できないかぎり、使わないようにしているものですが……このぐらい速度向上が著しい場合には、使うべきでしょう。

menuObjectにpopup buttonへのオブジェクト参照(popup button 1 of window 1とか)を入れ、textListにpopup menuに入れるテキストのリスト({”melon”, “apple”, “mango”}など)を入れて本ルーチンを呼び出せばOKです。

スクリプト名:ポップアップメニュー項目の高速生成
ポップアップメニューの初期化(2008/8/11超高速化)
on initPOPUPMenu(menuObject, textList)
  
tell menuObject
    
tell menu 1
      
delete every menu item
    
end tell
  
end tell
  
  
call method addItemsWithTitles: of menuObject with parameter textList
end initPOPUPMenu

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

05/29 ファイルのMac OS形式のフルパスのテキストから、ファイル名のみを取得

AppleScript Studioのアプリケーション中では、ファイルの名称を取得するのが大変です。Finderに対して命令してもダメ、System Eventsに命令してもダメ。アプリケーションにドラッグ&ドロップしたファイルの名称を取得してウィンドウのタイトル欄に表示しようとしても……できないということになります。Adobeのアプリのバグの多さにもうんざりさせられますが、Appleのバグの多さにも困ったものです。

注:普通のAppleScriptでは、Finderにファイルの名前を聞けば教えてくれます。本ルーチンはむしろ特殊環境用(AppleScript Studio用)です。

そこで、Mac OS形式のファイルパス(例: Macintosh HD:Users:someone:Documents:test.txt)を文字列として評価し、「:」で区切られたアイテムのうち、一番最後にあるものをファイル名として取り出します。

念のために書いておきますが、AppleScript Studioアプリケーション中ではない、通常のAppleScriptでは……ファイル名を求めるのはFinderに対してファイルのnameを取得するだけで楽勝で取得できます(4行ぐらい)。ここで紹介しているやりかたは、この通常の手段が利用できない場合の非常用の手段です。お間違えなく。

スクリプト名:ファイルのMac OS形式のフルパスのテキストから、ファイル名のみを取得
set a to choose file
set aStr to a as string
set aStr to aStr as Unicode text
set aRes to getLastLayerOfDirData(aStr) of me

テキストで与えられたパスデータから、最終階層のテキストのみを返す
on getLastLayerOfDirData(aData)
  set aColon to:as Unicode text
  
copy aData to bData
  
if bData ends with:then
    set bData to text 1 thru -2 of bData
  end if
  
  
set aLen to length of bData
  
set rData to reverse of (characters of bData)
  
set rData to rData as string
  
set dPos to offset of aColon in rData
  
set resData to text (aLen - dPos + 2) thru -1 of bData
  
return resData
  
end getLastLayerOfDirData

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

05/02 入り組んだリストの中に指定要素が存在するかどうかをチェック

{{1, 2, 3}, {4, 5, 6}, {7, 8, 9, {-1, -2, -3}}} ……のような入り組んだリスト型変数の中に、指定の要素が含まれていないかどうかをチェックするサブルーチンです。
(more…)