Archive for 4月, 2011

2011/04/25 AppleScriptによる並列処理v3a

2011/04/25 ファイルのファイル名を求める

ファイルのファイル名を求めるAppleScriptです。

このぐらいだと、書くのに10秒とかそのぐらいの難易度ですが……あるMac系のプログラマの方(それなりに経験の長い方)がAppleScriptでファイル名を求める方法について、テキストを分解して云々と書かれているのを見て衝撃を受けました。

本Blogでは、たいていのサブルーチンがAppleScript Studio環境でも動くように動作確認しているものが多く、最近ではAppleScriptObjCのプロジェクトの中に放り込んでも動くことを確認している(サブルーチン呼び出し時の「of me」を削除することが必要)ケースもぼちぼちあります。

通常のAppleScript環境では動いても、AppleScript Studio環境では動かないようなものもあるため、仕方なく対策のために書き直しているものも多々あります。

とくに、ファイルからファイル名を求めるという処理は、通常のAppleScript環境では「瞬殺」レベルの簡単なものですが、AppleScript Studio環境ではかなり手を焼かされました。

……パスをテキストとして評価してparseするような処理をいろいろ掲載していますが、Finderに名前を問い合わせるという「基本」が前提です。少し、基本的な処理記述例もしつこく掲載しておくべきなのかもしれません。

振り返ってみると、過去に出版されていたAppleScriptの入門書と呼ばれるものでも、掲載されているサンプルのAppleScriptは……正直、いただけないものが多かったので……素直な記法で書き直して掲載しておいたほうがよいのかもしれません。

スクリプト名:ファイルのファイル名を求める
set aFile to choose file

tell application “Finder”
  set aName to name of aFile
end tell

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

2011/04/24 スリープ復帰日時を設定する

スリープからの復帰日時を指定するAppleScriptです。設定に成功するとtrueが、失敗した場合にはfalseが返ります。

シェルのpmsetコマンドで復帰日時を詳細に設定します。AppleScriptObjCで簡単な録音アプリケーションを作成したときに、録音開始時刻までスリープして予約時刻が近くなったらスリープ解除すべく作成したものです。

当初は、Cocoaの呼び出しによってAppleScriptObjCベースで書けないか調べていたのですが、結局Cocoa+Objective-CでもCarbonベースのシステムコールを呼び出しているようで……それだとAppleScriptObjCから呼べないはず(たぶん)。

そこで、他のやり方がないかどうか調査していたところ、あっさりとシェルコマンドでできることが分かったので、通常のAppleScriptで記述してみました。本サンプルプログラム中では、ユーザーのパスワードを空欄に指定していますが、文字定数で記述しておくか、あるいは一度ユーザーに入力を求めてKeychainに記録しておくなどの方法がベストでしょう(2回目以降はKeychainからパスワードを取得)。

スリープからの解除時にパスワードを要求するように設定してあるマシンでは、スリープ復帰時にパスワードの入力を求められます。

pmsetコマンド側で現在時刻との比較を行っており、スリープ復帰日時を「過去」に設定するとエラーになります。また、スリープしていない状態(起きている状態)でスリープ復帰日時を迎えても何も起こりません。動作確認のためには、一度Macをスリープさせることが必要です(メニューから実行するもよし、AppleScriptからSystem Events経由でsleepコマンドを実行するもよし)。また、シャットダウンしてある状態から自動起動することもないはずです。

スクリプト名:スリープ復帰日時を設定する
set aYear to 2011 –年
set aMonth to 4 –月
set aDate to 24 –日

set aHour to 22 –時
set aMinute to 51 –分
set aSecond to 0 –秒

set myAccount to do shell script “whoami” –他に管理者アカウントを指定することも可。その場合にはmyPasswordには管理者のパスワードを指定
set myPassword to “” –パスワード

set pRes to setAwakeDate(aYear, aMonth, aDate, aHour, aMinute, aSecond, myAccount, myPassword) of me

–スリープからの復帰日時を設定する
on setAwakeDate(aYear, aMonth, aDate, aHour, aMinute, aSecond, myAccount, myPassword)
  if myPassword = “” then
    set myPassword to text returned of (display dialog “パスワードを入力してください” default answer “” with hidden answer)
  end if
  
  
–現在のユーザーに管理権限がない場合にはリターン
  
if getUsersPrivileges(myAccount) of me = false then return false
  
  
  
–数値の整形(日付)
  
set tYear to retZeroPaddingText(aYear, 2) of me
  
set tMonth to retZeroPaddingText(aMonth, 2) of me
  
set tDate to retZeroPaddingText(aDate, 2) of me
  
  
–数値の整形(時刻)
  
set tHour to retZeroPaddingText(aHour, 2) of me
  
set tMinute to retZeroPaddingText(aMinute, 2) of me
  
set tSecond to retZeroPaddingText(aSecond, 2) of me
  
  
set tDateStr to (ASCII character 34) & tMonth & “/” & tDate & “/” & tYear & ” “ & tHour & “:” & tMinute & “:” & tSecond & (ASCII character 34)
  
  
try
    set sRes to (do shell script “/usr/bin/pmset schedule wake “ & tDateStr user name myAccount password myPassword with administrator privileges)
  on error erMes
    return erMes –時間を過去に設定したか、あるいは数値の範囲指定エラー。パスワードの指定ミスの可能性もあり得る
  end try
  
  
return true
  
end setAwakeDate

–指定ユーザーの権限を得る(管理者か、それ以外か) 10.4および10.5以降両用
–管理者だとtrueが、それ以外だとfalseが返る
on getUsersPrivileges(aUser)
  set aVer to system attribute “sys2″ –OSメジャーバージョンを取得する(例:Mac OS X 10.6.4→6)
  
  
set current_user to aUser
  
  
if aVer > 4 then
    –Mac OS X 10.5以降の処理
    
set adR to (do shell script “/usr/bin/dsmemberutil checkmembership -U “ & current_user & ” -G admin users”)
    
    
if adR = “user is a member of the group” then
      return true
    else
      return false
    end if
    
  else
    –Mac OS X 10.4までの処理
    
set admin_users to (do shell script “/usr/bin/niutil -readprop . /groups/admin users”)
    
tell (a reference to AppleScript’s text item delimiters)
      set {old_atid, contents} to {contents, ” “}
      
set {admin_users, contents} to {text items of admin_users, old_atid}
    end tell
    
    
if current_user is in admin_users then
      return true
    else
      return false
    end if
  end if
  
end getUsersPrivileges

–数値にゼロパディングしたテキストを返す
on retZeroPaddingText(aNum, aLen)
  set tText to (“0000000000″ & aNum as text)
  
set tCount to length of tText
  
set resText to text (tCount - aLen + 1) thru tCount of tText
  
return resText
end retZeroPaddingText

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

2011/04/22 AppleScriptによる並列処理 v3〜実行ムービー

AppleScriptを並列処理するAppleScriptの改良型、v3の実行中のムービーです。

先週末には出来上がっていたのですが、挙動がおかしい箇所があったので地道にデバッグしていました。

並列処理デモv3(5.7MB)

2011/04/16 AppleScriptによる並列処理 v2〜実際に使ってみての評価

AppleScriptによる並列処理 v2を実際に使ってみての評価です。

■前提となる話

たまたま、大量かつそこそこの容量のテキストファイルの処理をする必要があったので、マシンのCPU性能を生かすために並列処理を行わせてみた。

通常のデスクトップ用アプリケーションを複数同時起動できるわけではないし、複数のAppleScriptアプレットから同時に1つのアプリケーションにアクセスした場合にきちんと排他処理してくれたりするかどうかは不明(昔は、FileMaker Proがbegin transaction〜end transactionの構文をサポートしていたが、他で見かけたことがない)。そんなに高度なことを期待してもダメ。

InDesignとかIllustratorの処理を1台で並列実行とかいう話は無理。ただし、もしもMac OS X10.7になって複数ユーザーが同時ログインして別々のGUI環境を同時に実行できるとかいう環境になってくると、1つのユーザー環境から他のユーザー環境上のアプリケーションをコントロールできる可能性は……ないこともない。

AppleScriptのプログラムを動的に実行バイナリに変換して、同じ仕事を手分けして処理するという程度の話がこの話のゴール地点。テキストファイルの大量処理という話ならけっこう使える。

ひととおり使えるレベルのプログラムに育ったら、1台で複数の処理をやるのと同様に、複数のマシンで分散処理を行うプログラムを作ってみるとよさそう。安くてそこそこの性能のマシンを複数台集めるとか、オフィスや学校にあるMacを夜間に動かして仕事を分散処理させるのはけっこうよさそう。

■評価

処理対象のファイル一覧をスレッド数で分割して、まとめて渡すというやり方はよくない。処理対象のファイルにはサイズにバラツキがあるので、それぞれのスレッドの処理時間がバラバラ。「数」で割って仕事を割り振るのは、無駄が多いことが分かった。

p1.jpg

たとえば、4スレッドで並列処理を行っても、割り当て分を終わらせたスレッドは早々にプログラムを終了してしまう。処理が早く終わったスレッドは、処理が終わっていないスレッドの未処理分を処理すべきなのに、それができていない。最後のほうでCPUの利用効率が落ちてしまう。

また、途中でトラブルに遭遇して停止した場合、1スレッド文の処理がいきなりすべて無駄になってしまうのは時間の無駄すぎます。

そのため、1ファイルずつスレッド側に渡して処理するように変更すべきだと思われました。……というか、もう出来上がってテスト中です。

p2.jpg

2011/04/16 数値リストを連続部分に分解する(データを返す)

与えられたリストを連続部分に分解するAppleScriptです。本ルーチンは、連続状態を与えられたリストのデータで返します。

リスト中に「n文字目」といった情報が入っている場合には、アイテム番号を返されるよりはデータ内容を返したほうが有用です。実際に必要に迫られて作成した次第。

スクリプト名:数値リストを連続部分に分解する(データを返す)
set aList to {30, 40, 41, 42, 43, 44, 45, 46, 47, 48, 63, 64, 85, 86, 88, 89, 90, 107, 108, 109, 110, 111, 112, 113, 114, 115}

set aRes to splitListByBreakAndReturnContentList(aList) of me
–> {{30, 30}, {40, 48}, {63, 64}, {85, 86}, {88, 90}, {107, 115}}

–数値リストを連続部分に分解する(データを返す)
on splitListByBreakAndReturnContentList(aList)
  
  
set bList to detectChangeInNumSeriesList(aList) of me
  
–return bList
  
–> {9, 11, 13, 16}–アイテムの切れ目を検出する
  
  
  
if length of bList is not equal to 1 then
    set prevItem to contents of first item of bList
    
    
set bList to rest of bList
    
    
set outList to {{first item of aList, item prevItem of aList}}
    
    
repeat with i in bList
      set j1 to prevItem + 1
      
set j2 to contents of i
      
set the end of outList to {item j1 of aList, item j2 of aList}
      
set prevItem to j2
    end repeat
    
    
set aLen to length of aList
    
if j2 is not equal to aLen then
      set the end of outList to {item (j2 + 1) of aList, item aLen of aList}
    end if
  else
    set outList to {item (contents of first item of bList) of aList}
  end if
  
  
return outList
end splitListByBreakAndReturnContentList

on detectChangeInNumSeriesList(aNumList)
  set prevNum to (first item of aNumList)
  
set bNumList to rest of aNumList
  
  
set outList to {}
  
  
set iCount to 1
  
  
repeat with i in bNumList
    set j to contents of i
    
if prevNum = (j - 1) then
      
    else
      set the end of outList to iCount
    end if
    
    
copy j to prevNum
    
set iCount to iCount + 1
  end repeat
  
  
–すべて連続していた場合への対処
  
if outList = {} then
    set outList to {{1, length of aNumList}}
  end if
  
  
return outList
  
end detectChangeInNumSeriesList

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

2011/04/16 数値リストを連続部分に分解する(アイテム番号を返す)

与えられたリストを連続部分に分解するAppleScriptです。本ルーチンは、連続状態を与えられたリストのアイテム番号で返します。

AppleScriptによる高速処理、とくにアプリケーションのコントロールの高速化を考えたときに、たとえばあるオブジェクト中の文字の書式を変更するという処理。頭から1文字ずつ処理するよりも、「○文字目から○文字目までの書式を変更しろ」と指定したほうが、はるかに速く処理できます。

アイテム番号で結果を返す本ルーチンは、そうした高速処理に絶大な威力を発揮します。

スクリプト名:数値リストを連続部分に分解する(アイテム番号を返す)
set aList to {40, 41, 42, 43, 44, 45, 46, 47, 48, 63, 64, 85, 86, 88, 89, 90, 107, 108, 109, 110, 111, 112, 113, 114, 115}

set aRes to splitListByBreakAndReturnItemNumList(aList) of me
–> {{1, 9}, {10, 11}, {12, 13}, {14, 16}, {17, 25}}

–数値リストを連続部分に分解する(アイテム番号を返す)
on splitListByBreakAndReturnItemNumList(aList)
  set bList to detectChangeInNumSeriesList(aList) of me
  
  
if length of bList is not equal to 1 then
    set prevItem to contents of first item of bList
    
    
set bList to rest of bList
    
    
set outList to {{1, prevItem}}
    
    
repeat with i in bList
      set j1 to prevItem + 1
      
set j2 to contents of i
      
set the end of outList to {j1, j2}
      
set prevItem to j2
    end repeat
    
    
set aLen to length of aList
    
if j2 is not equal to aLen then
      set the end of outList to {j2 + 1, aLen}
    end if
  else
    set outList to {contents of first item of bList}
  end if
  
  
return outList
end splitListByBreakAndReturnItemNumList

on detectChangeInNumSeriesList(aNumList)
  set prevNum to (first item of aNumList)
  
set bNumList to rest of aNumList
  
  
set outList to {}
  
  
set iCount to 1
  
  
repeat with i in bNumList
    set j to contents of i
    
if prevNum = (j - 1) then
      
    else
      set the end of outList to iCount
    end if
    
    
copy j to prevNum
    
set iCount to iCount + 1
  end repeat
  
  
–すべて連続していた場合への対処
  
if outList = {} then
    set outList to {{1, length of aNumList}}
  end if
  
  
return outList
  
end detectChangeInNumSeriesList

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

2011/04/13 AppleScriptによる並列処理 v2

2011/04/12 コードのチェック

コードのチェックを行うAppleScriptです。

製品コードなど、決められた桁数で指定の桁が数値で一部がアルファベットになっているなど、ルールに基づいて書かれている必要のある文字列の形式チェックを行うAppleScriptです。

こういうのは、あまり書いていても面白くないですが、より汎用性を持たせたものを作ってみました。

 数値桁に「9」と書いておくと、0〜9まで受け付けます。
 数値桁に「5」と書いておくと、0〜5まで受け付けます。
 文字桁に「Z」と書いておくと、A〜Zまで受け付けます。
 文字桁に「z」と書いておくと、a〜zまで受け付けます。

スクリプト名:コードのチェック3
set aCode to "1102V01" –チェック対象のコード
set ruleCode to "9999Z99" –コードのルールを外部から供給
set aRes to chkCode(aCode, ruleCode) of me
–> {true, true, true, true, true, true, true}

set aCode to "1102-V-01" –チェック対象のコード
set ruleCode to "9999-Z-99" –コードのルールを外部から供給
set aRes to chkCode(aCode, ruleCode) of me
–> {true, true, true, true, true, true, true, true, true}

–コードのチェックを行う
on chkCode(aCode, ruleCode)
  
  
–set ruleCode to "9999Z99" –コードのルールを記述しておく–> コードのルール自体も外部から供給するようにしてみた
  
set ruleList to characters of ruleCode
  
  
–コード長のチェック
  
set rLen to length of ruleList
  
set aLen to length of aCode
  
if aLen is not equal to rLen then return false
  
  
–与えられたコードをリストに分解
  
set aList to characters of aCode
  
  
set resList to {}
  
  
repeat with i from 1 to rLen
    
    
set j1 to contents of item i of ruleList
    
set j2 to contents of item i of aList
    
    
set {fromID, toID} to getCharRange(j1) of me
    
set aCharCode to ASCII number of j2
    
    
if (aCharCode fromID) and (aCharCode toID) then
      –ルールに合っている場合には何もしない
      
set the end of resList to true
    else
      set the end of resList to false
    end if
    
  end repeat
  
  
return resList
  
end chkCode

–文字レンジを返す
on getCharRange(a)
  set aCode to ASCII number of a
  
  
if aCode 48 and aCode 57 then
    return {48, aCode} –数字(0〜指定数字まで)
  else if aCode 65 and aCode 90 then
    return {65, aCode} –アルファベット(大文字 A〜指定文字まで)
  else if aCode 97 and aCode 122 then
    return {97, aCode} –アルファベット(大文字 a〜指定文字まで)
  else
    return {aCode, aCode} –その他(ハイフンなどの記号を想定。上記とかぶらない文字)
  end if
end getCharRange

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

2011/04/10 サウンド入力デバイス名を取得する

AppleScriptObjCで、サウンド入力デバイス名のリストを取得するプログラムです。

sound1.jpg

実行すると、ログ表示ウィンドウに……

sound2.jpg

と、デバイス名の一覧がログ表示されます。

プロジェクトにQTKit.frameworkをリンクしておく必要があるところが要チェックポイントです。

sound3.jpg

→ getinputdevice.zip

2011/04/10 ドロップされたASをdiff表示 v4

ドロップされたASをFileMergeでビジュアルdiff表示v3のバージョンアップ版です。2つのAppleScriptのファイルをFileMerge(Xcodeをインストールすると一緒に入る)でビジュアルdiff表示します。

GUI Scriptingを用いているため、実行のためにはこれをオンにしておく必要があります。また、本Scriptはアプリケーション形式で保存したうえで実行(AppleScriptのファイルを2つ、ドラッグ&ドロップ)することになります。

スクリプト名:ドロップされたASをdiff表示 v4
on run
  –環境確認を行うべき(書いてない)
  
  
–FileMergeの起動を最初にやっておく
  
if running of application “FileMerge” then –Mac OS X 10.5だか10.6で拡張された属性
    –すでに起動している場合には何もしない
  else
    tell application “FileMerge”
      launch
    end tell
  end if
  
end run

on open fileList
  
  
tell application “Finder”
    set sortedList to sort fileList by creation date –オリジナルはmodification dateだったが……趣味の問題?
  end tell
  
  
set sortedList to sortedList’s reverse
  
  
set oldPath to writeASSourceToTempFolder((sortedList’s item 1) as alias)
  
set newPath to writeASSourceToTempFolder((sortedList’s item 2) as alias)
  
  
do shell script “/usr/bin/opendiff “ & oldPath’s POSIX path’s quoted form & ” “ & newPath’s POSIX path’s quoted form & ” > /dev/null 2>&1 &”
  
  
–指定プロセスでDialogが出て処理が停まっている場合には、強制的にダイアログをクローズして処理続行させる
  
clickFrontDialog(“FileMerge”) of me
  
end open

–AppleScriptのソースを取得してファイルに書き出し
on writeASSourceToTempFolder(macPath)
  set aName to (info for macPath size 0)’s name
  
set tmpPath to ((path to temporary items from system domain) as text) & aName & “__” & (do shell script “/usr/bin/uuidgen”) & “.txt”
  
  
set scptText to do shell script “/usr/bin/osadecompile “ & macPath’s POSIX path’s quoted form
  
  
set accessFile to open for access file tmpPath with write permission
  
set eof of accessFile to 0
  
write scptText to accessFile starting at eof as text
  
close access accessFile
  
  
return tmpPath
end writeASSourceToTempFolder

–表示されるダイアログを乗り越えるためのルーチン(「キャンセル」もしくは「Cancel」ではない方のボタンを押す)
on clickFrontDialog(aProcName)
  –余計なプロセスを起動せずにactivate
  
activateAproc(aProcName) of me
  
  
–ダイアログ検出
  
tell application “System Events”
    tell process aProcName
      if (count every window) 1 then
        if subrole of window 1 = “AXDialog” then
          tell window 1
            set bCount to count every button
            
            
–「キャンセル」もしくは「Cancel」ではない方のボタンを取得
            
repeat with i in {“キャンセル”, “Cancel”} –キャンセルに該当するキーワードのリストを各国語分だけ用意しておく???
              set bList to (every button whose title is not equal to (contents of i))
              
if length of bList is not equal to bCount then
                set aButton to first item of bList
                
tell aButton
                  click
                  
exit repeat
                end tell
              end if
            end repeat
          end tell
        end if
      end if
    end tell
  end tell
end clickFrontDialog

on activateAproc(aName)
  tell application “System Events”
    set aList to every process whose name is aName and visible of it is true
    
    
if length of aList 1 then
      set aProc to first item of aList
      
tell aProc
        set frontmost to true
      end tell
      
    else
      tell application aName to activate
    end if
    
  end tell
end activateAproc

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

2011/04/10 ドロップされたASをdiff表示 v3

ドロップされたASをFileMergeでビジュアルdiff表示のバージョンアップ版です。GUI Scriptingを用いているため、実行のためにはこれをオンにしておく必要があります。

asdiff.jpg

コメント欄でedama2さんに提案していただいたものがv2。これは、TextWranglerを併用しなくてよくなったのと、shell commandを呼び出したあとできちんと終了するように改良したものです。また、一時ファイルのファイル名にオリジナルが何か分かるように作成されている点も有用な改良点だと思います。

ただ……それをそのまま掲載したのでは能がないので、このv3を作成してみました。

(1)日本語入りAppleScriptをFileMergeでオープンした際に表示される「Files are not ASCII」の警告ダイアログを無理やりGUI Scriptingでスキップ

filem1.jpg

(2)FileMergeのactivate時に、なぜか複数のFileMergeが起動されてしまう、という現象が(一時的に)見られたため、起動中のFileMergeのプロセスを特定して、そいつをactivateするようにした
filem2.jpg

(3)ファイル変更日時でソートされていたものを、ファイル作成日でソート

といった変更を加えています(3つ目のは単なる個人的な趣味の問題です)。

AppleScriptのDiff表示は、AppleScriptエディタの標準機能として搭載してほしいぐらいのものです。

ほかにも、複数のAppleScript間の使用サブルーチン比較表の作成プログラム、構文要素を考慮した置換(変数名のみ置換、定数のみ置換 など)などもすでにAppleScriptで書いたものがあり、日常的に使っていると「なぜこれが標準搭載されていないのか?」と不思議に思えるほどです。

osadecompileコマンドについては、ほとんど使ったことがなかったのですが……こういう分析系のタスクに用いるのがよいのだろうと思っています。あとは、AppleScriptの実行前に「危険な内容」が含まれていないかを事前にチェックするために利用する、ぐらいでしょうか。

スクリプト名:ドロップされたASをdiff表示 v3
on open fileList
  
  
tell application “Finder”
    set sortedList to sort fileList by creation date –オリジナルはmodification dateだったが……趣味の問題?
  end tell
  
  
set sortedList to sortedList’s reverse
  
  
set oldPath to writeASSourceToTempFolder((sortedList’s item 1) as alias)
  
set newPath to writeASSourceToTempFolder((sortedList’s item 2) as alias)
  
  
do shell script “/usr/bin/opendiff “ & oldPath’s POSIX path’s quoted form & ” “ & newPath’s POSIX path’s quoted form & ” > /dev/null 2>&1 &”
  
  
–指定プロセスでDialogが出て処理が停まっている場合には、強制的にダイアログをクローズして処理続行させる
  
clickFrontDialog(“FileMerge”) of me
  
end open

–AppleScriptのソースを取得してファイルに書き出し
on writeASSourceToTempFolder(macPath)
  set aName to (info for macPath size 0)’s name
  
set tmpPath to ((path to temporary items from user domain) as text) & aName & “__” & (do shell script “/usr/bin/uuidgen”) & “.txt”
  
  
set scptText to do shell script “/usr/bin/osadecompile “ & macPath’s POSIX path’s quoted form
  
  
set accessFile to open for access file tmpPath with write permission
  
set eof of accessFile to 0
  
write scptText to accessFile starting at eof as text
  
close access accessFile
  
  
return tmpPath
end writeASSourceToTempFolder

–表示されるダイアログを乗り越えるためのルーチン(「キャンセル」もしくは「Cancel」ではない方のボタンを押す)
on clickFrontDialog(aProcName)
  –余計なプロセスを起動せずにactivate
  
activateAproc(aProcName) of me
  
–tell application aProcName to activate
  
–tell application “FileMerge” to activate
  
  
–ダイアログ検出
  
tell application “System Events”
    tell process aProcName
      if (count every window) 1 then
        if subrole of window 1 = “AXDialog” then
          tell window 1
            set bCount to count every button
            
            
–「キャンセル」もしくは「Cancel」ではない方のボタンを取得
            
repeat with i in {“キャンセル”, “Cancel”} –キャンセルに該当するキーワードのリストを各国語分だけ用意しておく???
              set bList to (every button whose title is not equal to (contents of i))
              
if length of bList is not equal to bCount then
                set aButton to first item of bList
                
tell aButton
                  click
                  
exit repeat
                end tell
              end if
            end repeat
          end tell
        end if
      end if
    end tell
  end tell
end clickFrontDialog

on activateAproc(aName)
  tell application “System Events”
    set aList to every process whose name is aName and visible of it is true
    
if length of aList 1 then
      set aProc to first item of aList
      
tell aProc
        set frontmost to true
      end tell
    end if
  end tell
end activateAproc

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

2011/04/08 入れ子のリストをタブ区切りのテキストに

入れ子のリストをタブ区切りのテキストに変換するAppleScriptです。

分布リスト作成v1で作成したデータをテキスト化するために、さくっと作ってみましたが……同じものを以前に何度か作ったような……。

スクリプト名:入れ子のリストをタブ区切りのテキストに
set aList to {{"on handler1(thisYear, m)", "●", "●"}, {"on handler2(writeOutPathStr)", "●", ""}, {"on handler3(templatePath)", "●", ""}, {"on handler4(lastCellAdrNum)", "●", ""}, {"on handler5(aList, namePrefix, layerName)", "●", ""}, {"on handler2(aList, aDelim)", "", "●"}, {"on handler3(aList)", "", "●"}, {"on handler7(targetFrameName)", "", "●"}, {"on handler8(aStr)", "", "●"}}

set aText to retItemDelimedAndParagraphDelimedText(aList, tab, return) of me
–>
(*
"on handler1(thisYear, m)  ●  ●
on handler2(writeOutPathStr)  ●  
on handler3(templatePath)  ●  
on handler4(lastCellAdrNum)  ●  
on handler5(aList, namePrefix, layerName)  ●  
on handler2(aList, aDelim)    ●
on handler3(aList)    ●
on handler7(targetFrameName)    ●
on handler8(aStr)    ●
"
*)

–入れ子のリストを、アイテム間のデリミタとパラグラフ間のデリミタを指定してテキスト化
–というか、入れ子のリストをタブ区切りテキストにするのが目的
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

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

2011/04/08 分布リスト作成 v1

2つのAppleScriptプログラム内の使用ハンドラ一覧の分布リストを作成するAppleScriptです。AppleScriptのプログラム解析用のAppleScriptです。

構成が似通ったプログラムを大量に作ったときに、使用サブルーチンの差異を一覧表にまとめるべく作成しました。

資料を作成するために手作業で集計を行っていたのですが、あまりに面倒なのでプログラムで片づけることにしてみました。

このバージョンでは、2つのAppleScriptのハンドラ同士を比較していますが、実際には8つぐらいのAppleScriptのサブルーチン分布リストを作成。ハンドラの抽出については、別途AppleScriptのプログラムで行っておきましたが……そこまですべて通しで全自動で比較表を作ってみるようにしてもよいかもしれません。

冷静に評価してみると、このプログラム……けっこう応用範囲が広そうです。

スクリプト名:分布リスト作成 v1
–ハンドラ対照表を作成する

property trueChar : “●”
property falseChar : “”

set h1List to getH1() of me
set h2List to getH2() of me

–元になるアイテムリストを作成する
set newList to {}
repeat with i in h1List
  set aLine to {contents of i, trueChar}
  
set the end of newList to aLine
end repeat

set aRes to makeDiffTable(newList, h2List, 1) of me
–> {{”on handler1(thisYear, m)”, “●”, “●”}, {”on handler2(writeOutPathStr)”, “●”, “”}, {”on handler3(templatePath)”, “●”, “”}, {”on handler4(lastCellAdrNum)”, “●”, “”}, {”on handler5(aList, namePrefix, layerName)”, “●”, “”}, {”on handler2(aList, aDelim)”, “”, “●”}, {”on handler3(aList)”, “”, “●”}, {”on handler7(targetFrameName)”, “”, “●”}, {”on handler8(aStr)”, “”, “●”}}

on makeDiffTable(newList, h2List, blankCount)
  set aLen to length of h2List
  
set aaLen to length of newList
  
  
repeat with i from 1 to aLen
    –ハンドラを取り出す
    
set aCon to contents of item i of h2List
    
    
set hitF to false
    
    
repeat with ii from 1 to aaLen
      set bCon to (first item of (item ii of newList))
      
      
if bCon = aCon then
        set the end of (item ii of newList) to trueChar
        
set hitF to true
        
–exit repeat
      end if
    end repeat
    
    
if hitF = false then
      set newItem to {aCon}
      
repeat blankCount times
        set the end of newItem to falseChar
      end repeat
      
set the end of newItem to trueChar
      
      
set the end of newList to newItem
    end if
    
  end repeat
  
  
–newListのアイテム数が合わない箇所にブランクを足す
  
set maxItemNum to retMaxListNumber(newList) of me
  
set newList to setBlankToEndToShorterItem(newList, maxItemNum, “”) of me –ブランク要素として”_”を指定する
  
  
  
return newList
  
end makeDiffTable

–与えられたリストのうち、最大長に足りないアイテムに対し、blankItemを追加する
on setBlankToEndToShorterItem(aList, maxItemNum, blankItem)
  set newList to {}
  
repeat with i in aList
    set j to contents of i
    
set aLen to length of j
    
if aLen is not equal to maxItemNum then
      set loopNum to maxItemNum - aLen
      
repeat with ii from 1 to loopNum
        set the end of j to blankItem
      end repeat
    end if
    
set the end of newList to j
  end repeat
  
  
return newList
end setBlankToEndToShorterItem

–リスト中のすべてのアイテムをループして、アイテム数の最大のものを求める
on retMaxListNumber(aList)
  set maxNum to 0
  
repeat with i in aList
    set aLen to length of i
    
if maxNum < aLen then
      set maxNum to aLen
    end if
  end repeat
  
  
return maxNum
end retMaxListNumber

on getH1()
  return paragraphs of “on handler1(thisYear, m)
on handler2(writeOutPathStr)
on handler3(templatePath)
on handler4(lastCellAdrNum)
on handler5(aList, namePrefix, layerName)”

end getH1

on getH2()
  return paragraphs of “on handler1(thisYear, m)
on handler2(aList, aDelim)
on handler3(aList)
on handler7(targetFrameName)
on handler8(aStr)”

  
end getH2

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

2011/04/05 Mail.appでメールの送信(プレーンテキストへの変換つき)

Mail.appでメールの送信を行うAppleScriptの、Mac OS X 10.6用に機能追加を行ったものです。

Mac OS X 10.6からは、AppleScript経由でMail.appをコントロールしてメールを作成・送信すると、なんとプレーンテキスト(=標準テキスト)ではなく、リッチテキストで送られてしまいます。言われてみないと、なかなか気づきにくい箇所です(コメント欄でのご指摘ありがとうございます)。

ただ……Mail.appの環境設定をいじくっても、挙動に変化はありません。AppleScriptから送信命令を送ると、リッチテキストになってしまいます。

バグ……だと思うのですが、すでにMac OS X 10.6.x上で修正の見込みはなく、その場しのぎにGUI Scriptingでメニューからコマンドを叩き込んで、強制的にプレーンテキストに変換してから送信するようにしてみました。

Mac OS X 10.7に向けて、修正の要望を出しておく必要があります。ただ、Appleはあくまでも統計的にバグの処理を行うため……1人や2人が騒いだところで取りあいません。このバグが直ってほしいと思った方は、Appleに意見を送ってください。

スクリプト名:Mail.appでメールの送信(プレーンテキストへの変換つき)
set aSubject to "メールのサブジェクトです"
set theSender to "maro@sender.com" –送信者アドレス
set recipientAddr to "maro@reciever.jp" –受信者アドレス
set bodyText to "ひよこさんへ
こんにちは。ひよこさん。
ひよこクラブへのおさそいです。

ぴよ〜"

sendMail(aSubject, theSender, recipientAddr, bodyText) of me

–Mail.appでメール送信を行う
on sendMail(theSubject, theSender, recipientAddr, bodyText)
  tell application "Mail"
    set newMessage to make new outgoing message with properties {subject:theSubject, content:bodyText}
    
tell newMessage
      set visible to true
      
set sender to theSender
      
make new to recipient at end of to recipients with properties {address:recipientAddr}
      
      
makeAMessageToPlain() of me
      
      
send
    end tell
  end tell
end sendMail

–作成したメールがリッチテキストなら標準テキスト(プレーンテキスト)に直す
on makeAMessageToPlain()
  activate application "Mail"
  
  
tell application "System Events"
    tell process "Mail"
      –メニュー内容が「Mail.app」>「フォーマット」>「標準テキストにする」だったら現在リッチテキストだと判断し、コマンド実行
      
set aTitle to title of menu item 11 of menu 1 of menu bar item 8 of menu bar 1
      
      
if aTitle = "標準テキストにする" then
        –「標準テキストにする」をクリック
        
click menu item 11 of menu 1 of menu bar item 8 of menu bar 1
      end if
    end tell
  end tell
end makeAMessageToPlain

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

2011/04/03 InDesign CS3でBookを作成するテスト

InDesign CS3で複数の書類をまとめた「Book」を作成するAppleScriptです。

別途、ノンブルを振っておく必要があるようです(作ったけど)。

スクリプト名:InDesign CS3でBookを作成するテスト
set myFileList to {}

set myFolder to choose folder

tell application “Finder”
  tell folder myFolder
    try
      –普通のケース
      
set fList to (every file whose name ends with “.indd”) as alias list
    on error
      –多分、ファイルが1つしか存在しなかったケース
      
set f2List to (every file whose name ends with “.indd”)
      
set fList to {}
      
repeat with i in f2List
        set the end of fList to i as alias
      end repeat
    end try
  end tell
end tell

if (count fList) > 0 then
  set myBookFile to choose file name with prompt “Save book file as”
  
  
tell application “Adobe InDesign CS3″
    set myBook to make new book with data {full name:myBookFile}
    
    
repeat with i in fList
      set j to contents of i
      
tell myBook
        make book content with data {full name:j}
      end tell
    end repeat
  end tell
  
else
  display dialog “No InDesign files were found in the selected folder.”
end if

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

2011/04/03 QuickTime Player Xで録音を開始する

QuickTime Player Xで録音を開始するAppleScriptです。

AppleScriptObjCでRadikoの番組を予約録音するアプリケーションを自分用にやっつけで作ったときに、録音をQuickTime Player Xで行わせようとして作成したものです。

以前に試して作っておいたはずなのですが、いざ引っ張り出したら動かず……あわてて作り直した次第です。

スクリプト名:QuickTime Player Xで録音を開始する
tell application id “com.apple.QuickTimePlayerX”
  set recSound to (new audio recording)
  
  
tell recSound
    start
  end tell
end tell

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

2011/04/02 ドロップされたASをFileMergeでビジュアルdiff表示

FileMergeで2つのAppleScriptをdiff表示するAppleScriptです。

使用時には、アプリケーション形式で保存してドロップレットとして使います。保存したドロップレットに2つのAppleScriptのファイルをドラッグ&ドロップすると、osadecompileコマンドでAppleScriptをUTF-8のテキストに変換し、さらにTextWranglerでBOMつきのUTF-8に変換します。変換が終わったら、FileMergeでdiff表示します。

実行にはFileMerge(Xcodeをインストールすると入ってくる)と、TextWrangler(Bare Bones Softwareからフリーで配布されています)が必要です。Mac OS X 10.6上でのみ実行可能です。

「指定のAppleScriptをosadecompileでUTF-8のテキストに変換して、さらにUTF-8(BOMつき)にTextWranglerで変換して、パスを返す」AppleScriptを作ったあとに、FileMergeのフィルタからAppleScriptを呼び出してみたのですが、パスを返したらパスの文字列同士でdiffをとられてしまうし、ファイル内容をstdoutに返したら元のダメな表示に逆戻り。この方法ではまったくダメでした。

そんなときに、FileMergeをコマンドラインから呼び出せるはずだと思い立ち、いろいろ探してみると「opendiff」という名前で呼び出せるという情報に行き当たりました

そこで、これまでに作ったさまざまなサブルーチンをごてごてとコピー&ペーストでまとめつつ、ちょっとだけコードを書いて希望の動作を行わせることに成功。

diff3.jpg

日本語が入ったAppleScriptのプログラムを、きちんとdiff表示できています。

diff4.jpg

ただ、opendiffを呼び出したあとにドロップレットがそのまま実行から帰ってこないのですが……このあたりはまだ検討の余地がありそうです。

もともと、FileMergeのフィルタとして作り始めた本AppleScriptですが、結局AppleScriptのドロップレットにするのであれば、なんとなくAppleScriptエディタをAppleScriptからコントロールして操作するとか、そういう方法のほうがTextWranglerを引っ張り出してくる必要もなかったんじゃないかと思うものです。

まあでも、トータル半日ぐらいで希望の動作が実現できたのでよしとしましょう。実行後にドロップレットから結果が返ってこないで無反応になってしまいますが、強制終了させてやってください。

diff5.jpg

スクリプト名:ドロップされたASをdiff表示
on run
  –環境チェック
  
  
(*


  本来はここで、TextWranglerやFileMerge(opendiff)などが実行環境に存在するかどうかチェックすべき
  実行するOSのバージョンもチェックしておいたほうがよい
  
  *)

end run

on open fileList
  –2つのファイルがドロップされているかを判定。フォルダとかバンドルがドロップされる可能性は否定できないものの、ノーチェック
  
–スクリプトバンドルがきたら、AppleScriptエディタ経由で内容を取得する???
  
if length of fileList is not equal to 2 then
    display dialog “かならず1組(2つ)のファイルをドロップしてください。” buttons {“OK”} default button 1 with icon 1
    
return
  end if
  
  
–ファイル作成日でソート(古いものから新しいものへ)
  
set aliasRes to sortAliasByCreationDate(fileList) of me
  
  
set {file1Path, file2Path} to aliasRes
  
  
–UTF8 with BOMに変換
  
set file1PosixPath to decompileASandSaveAsUTF8withBOM(file1Path) of me
  
set file2PosixPath to decompileASandSaveAsUTF8withBOM(file2Path) of me
  
  
do shell script “/usr/bin/opendiff “ & quoted form of file1PosixPath & ” “ & quoted form of file2PosixPath & “&”
  
end open

–指定のAppleScriptをosadecompileでUTF-8のテキストに変換して、さらにUTF-8(BOMつき)にTextWranglerで変換して、パスを返す
on decompileASandSaveAsUTF8withBOM(aFile)
  set aTmp to ((path to temporary items from system domain) as string) & (do shell script “/usr/bin/uuidgen”) & “.txt”
  
set aTmpFile to POSIX path of aTmp
  
  
do shell script “/usr/bin/osadecompile “ & quoted form of POSIX path of aFile & ” > “ & quoted form of aTmpFile
  
  
  
–TextWranglerを起動して不可視に
  
tell application “TextWrangler” to launch
  
  
tell application “System Events”
    tell process “TextWrangler”
      set visible to false
    end tell
  end tell
  
  
  
–TextWranglerでUTF8のファイルをUTF8(With BOM)に変換する
  
tell application “TextWrangler”
    set aDoc to open file aTmp reading as UTF8 file
    
set distEncoding to encoding of aDoc
    
    
–BOMなしのUTF8だったらBOMつきUTF8に変更する
    
–”Unicode (UTF-8)”
    
–”Unicode (UTF-8, with BOM)”
    
if distEncoding = “Unicode (UTF-8)” then
      set encoding of aDoc to “Unicode (UTF-8, with BOM)”
    end if
    
    
save aDoc add to recent list boolean false –最近使ったファイルの一覧に登録しない
    
close aDoc without saving
    
  end tell
  
  
return aTmpFile
  
end decompileASandSaveAsUTF8withBOM

–ファイル作成日でソート(古いものから新しいものへ)
on sortAliasByCreationDate(aliasList)
  
  
tell application “Finder”
    set aList to {}
    
    
repeat with i in aliasList
      set j to contents of i
      
set the end of aList to {j, creation date of j}
    end repeat
    
    
set cList to shellSortListAscending(aList, 2) of me –で作成日時でソート
    
    
    
set aliasList to {}
    
set fnCount to 1
    
repeat with i in cList
      set j to contents of (item 1 of i)
      
      
set the end of aliasList to j
    end repeat
  end tell
  
  
return aliasList
  
end sortAliasByCreationDate

–シェルソートで入れ子のリストを昇順ソート
on shellSortListAscending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) > (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListAscending

–シェルソートで入れ子のリストを降順ソート
on shellSortListDecending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) < (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListDecending

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

2011/04/02 指定のAppleScriptをosadecompileでUTF-8のテキストに変換して、さらにUTF-8(BOMつき)にTextWranglerで変換して、パスを返す

指定のAppleScriptをosadecompileコマンドでUTF-8のテキストに変換し、さらにTextWranglerでBOMつきのUTF-8に変換して返すAppleScriptです。

AppleのXcodeと一緒にインストールされる「FileMerge」は2つのファイルの間の相違点をピックアップするdiff系のツールとしては、Mac OS X上では最高に出来がよく、使いやすいものだと(個人的に)思っています。

diff1.jpg

これを使ってAppleScriptのプログラムの相違点・変更点をリストアップできると便利ですが、残念ながらFileMergeが扱えるのはテキストファイルであり、AppleScriptのファイルをそのまま渡すことはできません。

そこで、Mac OS X標準装備のosadecompileコマンドを用いてAppleScriptのファイルをテキストファイルに変換し、それをFileMergeに渡せるとよいのではないかと考えました。

FileMergeにはファイルタイプごとに前処理を行うフィルタを設定でき、osadecomileコマンドを呼び出せるとよさそうです。TextWranglerでなくとも、UTF-8をBOMつきのUTF-8に変換するコマンドラインツールがあれば、そちらのほうがよさそうですが……さて、あるものなのか……???

diff2.jpg

ところが……このAppleScriptの中に日本語の文字列が入っているとうまく行きません。

osadecompileが出力する結果はUTF-8。しかし、そのままではFileMergeが読み込むことはできません。iconvを使ってUTF-8からさまざまな文字コードに変換してFileMergeに渡してもダメ。

さらに調べてみると、UTF-8でもBOMつきのUTF-8に変換すると、FileMergeが処理できることが分かりました。iconvでは役に立たなかったので、BareBones Softwareからフリー配布されているテキストエディタTextWranglerで変換するようにしてみました。

まだ機能を詰め切れていませんが、これで指定のAppleScriptの内容をFileMergeで比較できるように変換できています。

あとは、変換したファイルをいかにFileMergeに渡すかですが…………いっそ、このAppleScriptを/usr/local/binあたりに放り込んでおいて、FileMergeのフィルタから呼び出せばいいのかもしれません。

スクリプト名:指定のAppleScriptをosadecompileでUTF-8のテキストに変換して、さらにUTF-8(BOMつき)にTextWranglerで変換して、パスを返す
set aFile to choose file
set aRes to decompileASandSaveAsUTF8withBOM(aFile) of me

–指定のAppleScriptをosadecompileでUTF-8のテキストに変換して、さらにUTF-8(BOMつき)にTextWranglerで変換して、パスを返す
on decompileASandSaveAsUTF8withBOM(aFile)
  set aTmp to ((path to temporary items from system domain) as string) & (do shell script “/usr/bin/uuidgen”) & “.txt”
  
set aTmpFile to POSIX path of aTmp
  
  
do shell script “/usr/bin/osadecompile “ & quoted form of POSIX path of aFile & ” > “ & quoted form of aTmpFile
  
  
  
–TextWranglerを起動して不可視に
  
tell application “TextWrangler” to activate
  
  
tell application “System Events”
    tell process “TextWrangler”
      set visible to false
    end tell
  end tell
  
  
  
–TextWranglerでUTF8のファイルをUTF8(With BOM)に変換する
  
tell application “TextWrangler”
    set aDoc to open file aTmp reading as UTF8 file
    
set distEncoding to encoding of aDoc
    
    
–BOMなしのUTF8だったらBOMつきUTF8に変更する
    
–”Unicode (UTF-8)”
    
–”Unicode (UTF-8, with BOM)”
    
if distEncoding = “Unicode (UTF-8)” then
      set encoding of aDoc to “Unicode (UTF-8, with BOM)”
    end if
    
    
save aDoc add to recent list boolean false –最近使ったファイルの一覧に登録しない
    
close aDoc without saving
    
  end tell
  
  
return aTmp as alias
  
end decompileASandSaveAsUTF8withBOM

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