05/19 指定文字列から、指定ペア文字で囲まれる部分を削除。前方からスキャンして削除実行

指定文字列に対して、指定ペア文字列で囲まれる部分を削除するAppleScriptです。

この手の処理では、文字列の前と後ろからサーチするような処理が多いですが、本プログラムはどちらも前方からサーチします。

プログラムのタイトルを見ただけでは何に使ったものかさっぱり分かりませんが、プログラムリストを見ると一目瞭然。CocoaのAPIの名前をAppleScriptObjCに自動置換するための試作品です(ものすごく強引で、やっつけ仕事のオンパレードなうえに、なんでもかんでも処理できるようにはなっていません)。

もうちょっとこねくり回すとなんとかなってくるものでしょうか。

スクリプト名:指定文字列から、指定ペア文字で囲まれる部分を削除。前方からスキャンして削除実行
–Cocoaのメソッド名をAppleScriptObjCフォーマットに変換するテスト

–set aStr to “getColorR:(float *)r G:(float *)g B:(float *)b atIndex:(int)i ofSimulationType:(int)type;”
set aStr to “drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withFont:(UIFont *)font minFontSize:(CGFloat)minFontSize actualFontSize:(CGFloat *)actualFontSize lineBreakMode:(UILineBreakMode)lineBreakMode baselineAdjustment:(UIBaselineAdjustment)baselineAdjustment”

set sStr to “(”
set eStr to “)”

–変数の型で2語に別れるものを徹底的に置換
set aStr to repChar(aStr, “unsigned short”, “unsignedshort”) of me
set aStr to repChar(aStr, “signed short”, “signedshort”) of me
set aStr to repChar(aStr, “signed int”, “signedint”) of me
set aStr to repChar(aStr, “unsigned int”, “unsignedint”) of me

if aStr does not end with “;” then
  set aStr to aStr & “;”
end if

set aStr to trimStrByCharPair(aStr, sStr, eStr, 0, 0) of me

set aStr to repChar(aStr, tab, “”) of me
set aStr to repChar(aStr, character id 10, “”) of me

set aStr to repChar(aStr, “;”, ” “) of me
set aStr to repChar(aStr, “:”, “_”) of me

set bList to parseByDelim(aStr, {“_”, ” “}) of me
set aRes to “”
repeat with i from 1 to ((length of bList) - 1) by 2
  set j to contents of item i of bList
  
set aRes to aRes & (j & “_”)
end repeat

set bRes to “(”
repeat with i from 2 to (length of bList) by 2
  set j to contents of item i of bList
  
set bRes to bRes & (j & “, “)
end repeat
set bRes to bRes & “)”

set bRes to repChar(bRes, “, )”, “)”) of me –ゴミ掃除

set cRes to aRes & bRes
–>”drawAtPoint_forWidth_withFont_minFontSize_actualFontSize_lineBreakMode_baselineAdjustment_(point, width, font, minFontSize, actualFontSize, lineBreakMode, baselineAdjustment)”

–指定文字列から、指定ペア文字で囲まれる部分を削除。前方からスキャンして削除実行
on trimStrByCharPair(aStr, sStr, eStr, sTrimOffset, eTrimOffset)
  repeat
    set sOffst to offset of sStr in aStr
    
set eOffst to offset of eStr in aStr
    
    
set aLen to length of aStr
    
if aLen < 2 then return “”
    
    
if sOffst = 0 or eOffst = 0 then
      exit repeat
    end if
    
    
if sOffst > 1 then
      set tmp1Str to text 1 thru (sOffst - 1 + sTrimOffset) of aStr
      
set tmp2Str to text (eOffst + 1 + eTrimOffset) thru -1 of aStr
      
set aStr to tmp1Str & tmp2Str
      
    else if sOffst = 1 then
      set aStr to text (eOffst + 1) thru -1 of aStr
      
    else if sOffst = aLen - 1 then
      set aStr to “”
      
    end if
    
  end repeat
  
  
return aStr
end trimStrByCharPair

–文字置換ルーチン
on repChar(origText, targStr, repStr)
  set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end repChar

–与えられた文字列を、指定デリミタ文字でparseしてリストにして返す
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

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

05/17 MacTech Magazine for iPadが登場

2012/5/16に「MacTech Magazine」のiPad版が登場しました。iPadアプリをダウンロードして、iPadでMacTech Magazineが読めるようになりました。アプリ内でアドオンとして、1冊4.99ドル(450円)、3冊で10.99ドルでオンライン購入できるようになっています。

これまでは、船便や航空便によって、本誌の価格よりもかなり割高なお値段でしか入手できませんでした。ようやく電子版が登場し(昔にもあったような気もしますが)、輸送費用なしで入手できるようになったわけです。最新版がそのまま入手できるわけではないようなので、タイムラグまで解消されるわけではないようですが、それでも紙の本より割安感が出ています。

r0014565.jpg

MacTech Magazine自体については、開発者向けの情報源というべきなのか、開発に興味のあるエンドユーザー向けなのか、ソリューションプロバイダー向けなのか……「開発者向け」というにはやや記事ジャンルに偏りがあるように見えます。

具体的にどのジャンルに偏っているかといえば、UNIX shellとかAppleScript(!)。毎号にAppleScriptの関連記事が載っているとは言いませんが、けっこうな確率で掲載されています。

実際、2012年1月号(フリー)と3月号(450円)にはAppleScriptObjCの記事が掲載されており、参考になりました。

05/13 モニタの解像度を取得する(複数モニタ対応)

Mac本体に接続されたディスプレイ(モニタ)の解像度を取得するAppleScriptについて、これまでにいろいろなものを紹介してきました。

なぜ、1つのやり方ですまないかといえば……System Eventsなどで直接ディスプレイ解像度を取得するような命令セットが用意されてこなかったためで……さまざまなやり方を組み合わせて求めてきました(とくに、AppleScript Studioのプログラム内に入れたときに使えるかどうかが問題でした。けっこう、Finderに対する命令がきかない場合があったりで、冷や汗をかかされました)。

scrn1.png

このような環境で試してみましたが、iPad 3をAir Displayを使って外付けディスプレイとして接続した場合には、「環境設定」には表示されるものの、OSの各種コマンドでAir Display接続したiPadの情報は取得できません。

disp5.png

scrn3.png

scrn2.png

注:以下のサンプルAppleScriptは、Air Display経由のiPad 3を「外した」状態で(MacBook Pro本体+外付けLCDの2モニタ構成で)実行しています

■方法1 FinderのDesktop WindowのScroll Areaのsizeを取得

複数ディスプレイのトータルのサイズを取得。個別のディスプレイのサイズについては取得できません。

スクリプト名:displaySize1
–http://piyocast.com/as/archives/73
tell application “System Events” to set {rightLimit, bottomLimit} to size of scroll area 1 in process “Finder”
–> {3840, 1251}

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

■方法2 FinderのDesktop WindowのScroll Areaのsizeを取得

1つのモニタの情報のみ取得。

スクリプト名:displaySize2
–http://piyocast.com/as/archives/1560
tell (do shell script “/usr/sbin/system_profiler SPDisplaysDataType | grep Resolution”) to set {newR, newB} to {word 2 as number, word 4 as number} – get screen size for monitor

–> {1920, 1200}

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

■方法3 com.apple.windowserverの設定ファイルを読み込む方法

1つのモニタだけの情報を取得するようになっているので、そもそも複数のモニタには対応していません。

スクリプト名:displaySize3
–http://piyocast.com/as/archives/1561
return {word 3 of (do shell script “defaults read /Library/Preferences/com.apple.windowserver | grep -w Width”), word 3 of (do shell script “defaults read /Library/Preferences/com.apple.windowserver | grep -w Height”)}

–> {”1920″, “1200″}

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

■方法4 Finderでdesktop Windowのサイズを取得

複数モニタのトータルのデスクトップサイズを取得できます。モニタの位置によっては、マイナスの数値なども返ってきます。

スクリプト名:displaySize4
–http://piyocast.com/as/archives/1562
tell application “Finder”
  get bounds of window of desktop
  
–> {0, -51, 3840, 1200}
end tell

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

とりあえず、いままでのやり方を見直して……とりあえず、system_profilerコマンドを経由して複数のモニタの解像度を取得するようにしてみました。

スクリプト名:モニタの解像度を取得する(複数モニタ対応)
–17インチMacBook Pro本体ディスプレイ
set aRes to retDisplayResolution() of me
–> {{1920, 1200}}

–17インチMacBook Pro本体ディスプレイ(Main) + 外付け24インチモニタ(1920 x 1080)(Sub)の場合
set aRes to retDisplayResolution() of me
–> {{1920, 1200}, {1920, 1080}}

–17インチMacBook Pro本体ディスプレイ(Sub) + 外付け24インチモニタ(1920 x 1080)(Main)の場合
set aRes to retDisplayResolution() of me
–> {{1920, 1200}, {1920, 1080}}

–モニタ解像度を取得。複数モニタ接続時にはリストで列挙
on retDisplayResolution()
  
  
set sRes to do shell script “/usr/sbin/system_profiler SPDisplaysDataType | grep Resolution”
  
  
set sList to paragraphs of sRes
  
set resList to {}
  
  
repeat with i in sList
    set j to contents of i
    
set x1 to word 2 of j as number
    
set y1 to word 4 of j as number
    
set the end of resList to {x1, y1}
  end repeat
  
  
return resList
  
end retDisplayResolution

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

05/06 ASObjC Runner〜ASOCの機能にAppleScript的な記法を

ASObjC Runnerは、ASOCのスクリプト(テキストベース)の実行機能をMac OS X 10.6.xと10.7.xに提供するフリーのアプリケーションです。作者はShane Stanley。ほかにも、Late Nighet SoftwareのMark Alldrittなども協力しているとのこと。

asobjc1.png

ASOCは基本的には、ランタイムアプリケーション上で動作するものですが、ASObjC Runnerではテキストファイル形式のASOCのプログラムをそのまま実行します。

tell application “ASObjC Runner”

のtellブロック内にscriptを記述します。サンプルはmacosxautomation.comに掲載されているので、雰囲気を伺い知ることができます。

asoctable.png

……以前までは、ASobjC Runnerはあまり個人的な興味を喚起されない存在だったのですが、途中のバージョンアップで面白い要素が追加されました。

Cocoaのオブジェクトに対して標準的なAppleScriptの用語でアクセスできるようにする機能です。

正確にいえば……ASObjC Runner側で想定している範囲の機能について、ASObjC RunnerがAppleScript用語辞書を用意しており、その用語を使って一般的なAppleScript的なアプローチでScriptingが行えるというわけです。

AS用語辞書が提供されており、通常のAppleScript的な用語が利用できるのは、

リスト操作(ソートや検索など)、文字列操作(検索、置換、日付文字列書式指定など。正規表現による検索/置換文字列指定の機能を含む)、ファイル操作(情報取得、コピー、削除、移動、ファイル作成、plist読み書き)などのほか、プログレスバー付きダイアログの表示

などです(ASOC的記法で書けば、より広い範囲のCocoaの機能をダイレクトに使用できます)。

05/04 iPhotoで選択中の写真から曜日別の件数を個別にカウント

iPhotoで選択中の写真からファイル作成日付を取得し、それぞれの写真の撮影曜日を取得して日曜日〜土曜日の各曜日の撮影枚数を集計するAppleScriptです。

iPhoto上で写真を(複数、かつ大量に)選択状態にしておき、本AppleScriptを実行すると撮影曜日ごとの枚数を集計します。

やはりというか、当然というべきなのか、土曜日と日曜日の撮影が多いという結果に。

スクリプト名:iPhotoで選択中の写真から曜日別の件数を個別にカウント
script spdUp
  property selList : missing value
  
property dList : {}
end script

–set t1 to current date

–データの初期化
set dList of spdUp to {}
set selList of spdUp to {}

–iPhotoからデータを取得する
tell application "iPhoto"
  set selList of spdUp to selection
  
  
if (selList of spdUp) = {} then
    display dialog "何も選択されていません。" buttons {"OK"} default button 1
    
return
  end if
  
  
repeat with i in (selList of spdUp)
    set the end of (dList of spdUp) to (date of i)
  end repeat
end tell

set {n1, n2, n3, n4, n5, n6, n7} to {0, 0, 0, 0, 0, 0, 0}

repeat with i in (dList of spdUp)
  set j to contents of i
  
set aWDnum to weekday of j as number
  
if aWDnum = 1 then
    set n1 to n1 + 1
  else if aWDnum = 2 then
    set n2 to n2 + 1
  else if aWDnum = 3 then
    set n3 to n3 + 1
  else if aWDnum = 4 then
    set n4 to n4 + 1
  else if aWDnum = 5 then
    set n5 to n5 + 1
  else if aWDnum = 6 then
    set n6 to n6 + 1
  else if aWDnum = 7 then
    set n7 to n7 + 1
  end if
end repeat

return {n1, n2, n3, n4, n5, n6, n7}

–> {309, 10, 9, 10, 17, 51, 375}
–{Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}

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

05/03 iPhotoで選択中の写真から各月の件数を個別にカウント

iPhotoで選択中の写真から、1か月単位で撮影枚数を個別にカウントするAppleScriptです。

iPhotoの「人々」を用いて任意の人物の写真をすべて選択しておき、

iphoto2.png

その状態で本AppleScriptを実行すると、各写真の日付情報を取得。日付情報をソートして開始月、終了月の情報を求め、各月にどの程度の写真撮影が行われたかを順次カウントして集計します。

iPhotoにいちいちアクセスするとスピードの低下が心配されるところですが、選択中の写真の日付情報を取得したらあとはiPhotoにアクセスせずに処理を行うため、781枚の写真を選択した状態で、93秒(MacBook Pro 2010 Core i7 2.66GHz、Mac OS X 10.6.8、8GB)で処理を終了しました。処理のほとんどは、iPhotoで選択中の写真の情報を取得する部分で、ソートや集計はほぼ一瞬です。

「○月には遊びに行ってなかったので1枚も撮っていなかったが、さすがに正月は撮影枚数が多いな」

などという、プログラムで分析するまでもない、予想どおりの傾向が分かります。

年ごとの集計、曜日ごとの集計などいろいろ集計のバリエーションを増やしてみると楽しいことでしょう。たぶん、「土日に撮影している枚数が多い」という結果が出るんでしょうけれども。期間で集計するほかに、撮影時の緯度・経度で集計するなど、さまざまな応用が考えられます。

スクリプト名:iPhotoで選択中の写真から各月の件数を個別にカウント
script spdUp
  property selList : missing value
  
property dList : {}
  
property eList : {}
  
property fList : {}
end script

–set t1 to current date

–データの初期化
set dList of spdUp to {}
set eList of spdUp to {}
set selList of spdUp to {}

–iPhotoからデータを取得する
tell application “iPhoto”
  set selList of spdUp to selection
  
  
if (selList of spdUp) = {} then
    display dialog “何も選択されていません。” buttons {“OK”} default button 1
    
return
  end if
  
  
repeat with i in (selList of spdUp)
    set the end of (dList of spdUp) to date of i
  end repeat
end tell

–ソート
set (dList of spdUp) to shellSort5(dList of spdUp) of me

–選択中の写真の最初の日付、最近の日付を求める
set minDate to contents of first item of (dList of spdUp)
set maxDate to contents of last item of (dList of spdUp)

–開始日、終了日の間の{年,月}のリストを取得する
set mmList to retYearMonthWithinPeriod(minDate, maxDate) of me

set aLen to length of (dList of spdUp)
set hitList to {}

set aCount to 0

set aPointer to 1
set hitF to false

repeat with i in mmList
  set {aYear, aMonth} to i
  
set sDate to (aYear as string) & “/” & (aMonth as string) & “/1″
  
set eDate to (aYear as string) & “/” & (aMonth as string) & “/” & getMlen(aYear, aMonth) of me as string
  
set sDateObj to date sDate
  
set eDateObj to date eDate
  
  
  
repeat
    set j to contents of item aPointer of (dList of spdUp)
    
if (sDateObj j) and (j eDateObj) then
      
      
if hitF = false then
        set hitF to true
        
set aCount to 1
        
        
set aPointer to aPointer + 1
        
      else
        set aCount to aCount + 1
        
set aPointer to aPointer + 1
      end if
      
    else
      
      
if hitF = true then
        set the end of hitList to {aYear, aMonth, aCount}
        
set aCount to 0
        
set hitF to false
        
exit repeat
      else
        set the end of hitList to {aYear, aMonth, 0}
        
exit repeat
      end if
      
    end if
    
    
if aPointer > aLen then
      exit repeat
    end if
    
  end repeat
  
  
if aPointer > aLen then
    exit repeat
  end if
  
end repeat

if hitF = true then
  set the end of hitList to {aYear, aMonth, aCount}
end if

–set t2 to current date
–set t3 to t2 -t1
–> 93 sec

hitList
–> {{2009, 4, 2}, {2009, 5, 0}, {2009, 6, 0}, {2009, 7, 25}, {2009, 8, 0}, {2009, 9, 0}, {2009, 10, 1}, {2009, 11, 2}, {2009, 12, 7}, {2010, 1, 38}, {2010, 2, 41}, {2010, 3, 4}, {2010, 4, 36}, {2010, 5, 5}, {2010, 6, 3}, {2010, 7, 36}, {2010, 8, 26}, {2010, 9, 49}, {2010, 10, 8}, {2010, 11, 61}, {2010, 12, 23}, {2011, 1, 32}, {2011, 2, 40}, {2011, 3, 0}, {2011, 4, 26}, {2011, 5, 44}, {2011, 6, 10}, {2011, 7, 32}, {2011, 8, 41}, {2011, 9, 0}, {2011, 10, 0}, {2011, 11, 39}, {2011, 12, 51}, {2012, 1, 25}, {2012, 2, 0}, {2012, 3, 51}, {2012, 4, 23}}

on retYearMonthWithinPeriod(sDate, eDate)
  
  
set sYear to year of sDate
  
set sMonth to month of sDate as number
  
  
set eYear to year of eDate
  
set eMonth to month of eDate as number
  
  
set mList to {}
  
  
if sYear is equal to eYear then
    
    
repeat with i from sMonth to eMonth
      set the end of mList to {sYear, i}
    end repeat
    
  else if sYear + 1 = eYear then
    
    
repeat with i from sMonth to 12
      set the end of mList to {sYear, i}
    end repeat
    
    
repeat with i from 1 to sMonth
      set the end of mList to {eYear, i}
    end repeat
    
  else
    
    
repeat with i from sMonth to 12
      set the end of mList to {sYear, i}
    end repeat
    
    
repeat with i from (sYear + 1) to (eYear - 1)
      repeat with ii from 1 to 12
        set the end of mList to {i, ii}
      end repeat
    end repeat
    
    
repeat with i from 1 to sMonth
      set the end of mList to {eYear, i}
    end repeat
    
  end if
  
  
return mList
end retYearMonthWithinPeriod

–最大値を取得する
on maximumFromList(nList)
  script o
    property nl : nList
  end script
  
  
set max to item 1 of o’s nl
  
repeat with i from 2 to (count nList)
    set n to item i of o’s nl
    
if n > max then set max to n
  end repeat
  
return max
  
end maximumFromList

–最小値を取得する
on minimumFromList(nList)
  script o
    property nl : nList
  end script
  
  
set min to item 1 of o’s nl
  
repeat with i from 2 to (count nList)
    set n to item i of o’s nl
    
if n < min then set min to n
  end repeat
  
return min
  
end minimumFromList

–指定月の長さを得る(日数)
on getMlen(aYear, aMonth)
  
  
set aDat to (aYear as text) & “/” & (aMonth as text) & “/1″
  
if aMonth is not equal to 12 then
    set eDat to ((aYear as text) & “/” & (aMonth + 1) as text) & “/1″
  else
    set eDat to ((aYear + 1) as text) & “/” & (1 as text) & “/1″
  end if
  
  
–set sDat to date aDat
  
set eDat to date eDat
  
set eDat to eDat - 1
  
  
set mLen to day of eDat
  
return mLen
  
end getMlen

–プレーンなAppleScriptベースでは最速ソート
on shellSort5(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 shellSort5

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

04/30 Microsoft Office v2011 14.2.1アップデートで辞書に変更なし

先日公開された「Microsoft Office 2011 14.2.1 更新プログラム」でアップデートされた、Excel、Outlook、PowerPoint、WordのAppleScript用語辞書にとくに変更がないことを確認しました。

04/29 QuickTime Xで録画して任意のファイル名で保存

QuickTime Xで録画して任意のファイル名で保存するAppleScriptです。

QuickTime Player 7+Mac OS X 10.7で動作するScriptを見て海外の友人が「QuickTime Player Xは機能が少ないよな〜」などとぼやいていたので、この程度ならできるだろうと考えてQuickTime Player X用に書き換えてみました。

相変わらず、時間計測にお手軽にdelayコマンドを使っていますが、録画開始時のGUIアニメーションで2秒近くロスがあります。このため、指定録画時間に2秒追加しています。

スクリプト名:QuickTime Xで録画
property recTime : 10 –録画時間(秒で指定する)

set aFilepath to choose file name with prompt "ムービーを保存するファイル名を入力"
set outFilepathPOSIX to POSIX path of aFilepath

tell application id "com.apple.QuickTimePlayerX"
  –確実に処理を行うためにムービーをすべてクローズ
  
close every document without saving
  
  
–録画開始
  
set recMov to (new movie recording)
  
tell recMov to start
end tell

–録画時間経過待ち
delay (recTime + 2) –秒単位でウェイト(UI系の動作により2秒程度ロスが発生するもよう)

tell application id "com.apple.QuickTimePlayerX"
  –録画停止
  
tell recMov to stop –この操作で、QT7の設定に従ってムービーが保存される
  
  
delay 1 –時間待ち
  
  
tell document 1
    –properties
    
set aProp to properties
    
close
  end tell
end tell

set originPath to file of aProp

–QT Player 7が自動保存したファイルを移動&リネーム
–ただし、自動保存したファイルのパスと移動先の衝突判定は省略
set movieOriginalFile to POSIX path of originPath
set sText to "mv " & quoted form of movieOriginalFile & " " & quoted form of outFilepathPOSIX
do shell script sText

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

04/29 Terminalで全Window中の全Tabのうち、指定タイトルのものをクローズする

Terminal.appの全Window内のTabのうち、指定タイトルのものをクローズするAppleScriptです。

Mac OS X 10.7上のTerminalの各ウィンドウには、任意のタイトルを指定できるようになっています。Mac OS X 10.6上では、カスタマイズしたタイトルについてはAppleScriptからは参照するだけでした。

term00.jpg

スクリプト名:teminal.appでtabを操作する
tell application "Terminal"
  tell window 1
    tell tab 1
      set custom title to "ぴよまる"
    end tell
    
    
tell tab 2
      set custom title to "ぴよぴよ"
    end tell
    
  end tell
end tell

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

term01.jpg

term3.jpg

Tabのクローズについては、Window内のTabを指定してclose命令を実行してみても、クローズはできない状況です。GUI Scriptingで行うのもアレなので(最前面に持ってこないと実行できないというのは、ちょっとめんどう)、別の方法でクローズを実現することに。

Terminal.appの環境設定で、シェル終了後にWindowをどうするか指定することができます。

term1.jpg

term4.jpg

「ウィンドウを閉じる」「シェルが正常終了した場合には閉じる」「ウィンドウを閉じない」の3つから選択可能。

term2.jpg

これを「ウィンドウを閉じる」に設定しておき、指定Tabに「exit」コマンド(シェルコマンド)を送ることで、指定Tabをクローズできるようになります。

まあそんなわけで、Tabのカスタム名称を文字列で指定して該当するTabをすべてクローズできるようになったわけですが、これがどの程度有用かは…………ちょっと分かりません。

スクリプト名:Terminalで全Window中の全Tabのうち、指定タイトルのものをクローズする
set targTabTitle to “ぴよまる”

tell application “Terminal”
  set tabRef to a reference to (every tab of every window whose custom title is equal to targTabTitle)
  
  
repeat with i in tabRef
    do script “exit” in i
  end repeat
  
end tell

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

04/29 QuickTime 7で録画して任意のファイル名で保存

QuickTime Player 7+Mac OS X 10.7で、所定の時間だけムービー録画(おそらく、内蔵のiSight/Face Time Cameraから取り込み)を行い、指定のパスにファイル保存を行うAppleScriptです。

コメント欄の質問から、試しに組んでみましたが……意外と試行錯誤が必要でした。

所定時間だけ録画する、というScriptは簡単です。しかし、録画内容を別のパスに保存するというのがなかなかやっかいです。本来のQuickTime Playerの機能にのっとって処理することを考えるなら、指定の(あらかじめ保存してある)ムービーファイルから保存形式などを読み取って、その内容に基づいてムービーを「export」することになります。

しかし、そこまでまじめに処理をやりだすと相当の長さになってしまうため、サンプルとしてはいささかおおげさです。

そこで、とりあえずQuickTime Playerの設定に基づいて「ムービー 1.mov」などといったファイル名で保存を行わせておき、そのフルパスを取得。ムービーをクローズしたのちに、一時保存したファイルを指定パスに移動させればよいだろう、と考えてこのようなものに。

録画時間の指定にdelay命令を用いていますが、あくまで説明を簡単に行うために用いているものであって、本来はidleハンドラを使ってタイマー割り込みで実装するべきです。

スクリプト名:QuickTime 7で録画
property recTime : 3 –録画時間(秒で指定する)

set aFilepath to choose file name with prompt "ムービーを保存するファイル名を入力"
set outFilepathPOSIX to POSIX path of aFilepath

tell application id "com.apple.quicktimeplayer"
  –確実に処理を行うためにムービーをすべてクローズ
  
close every document without saving
  
  
–録画開始
  
new movie recording
  
start recording 1
end tell

–録画時間経過待ち
delay recTime –秒単位でウェイト(ちょっとバカっぽい処理。本来はタイマー割り込みで時間待ちすべき)

tell application id "com.apple.quicktimeplayer"
  –録画停止
  
stop recording 1 –この操作で、QT7の設定に従ってムービーが保存される
  
  
delay 1 –時間待ち
  
  
tell document 1
    set originPath to original file –保存先のファイルのパスを取得しておく
    
close
  end tell
end tell

–QT Player 7が自動保存したファイルを移動&リネーム
–ただし、自動保存したファイルのパスと移動先の衝突判定は省略
set movieOriginalFile to POSIX path of originPath
set sText to "mv " & quoted form of movieOriginalFile & " " & quoted form of outFilepathPOSIX
do shell script sText

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

04/22 入れ子のリストから、指定フィールドでソートしてデータを返す

{{”Field1″, “Field2″, “Field3″}, {1,2,3}, {4,5,6}}のような入れ子のリストで、指定フィールド名のデータをキーとしてソートして返すAppleScriptです。

CSVファイルをparseしてリストに変換し、リストに対して指定フィールドの値でソートする場合に使用します。

返り値にはフィールドラベルを含んでいません。ソートルーチンは、中途半端な高速化を行ってあります。最速版のソートルーチンを入れ子のリストに対応させられていなかったので、shell sortの入れ子ルーチンをちょっとだけ高速化した状態です。

スクリプト名:入れ子のリストから、指定フィールドでソートしてデータを返す
set aList to {{“ID”, “名称”, “アドレス”, “URL”, “データ”}, {1, 2, 3, 4, 5}, {9, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}

set sortDirecionF to false –true: ascending, false: descending

set resList to getSpecifiedFieldData(aList, “ID”, sortDirecionF) of me
–> {{9, 3, 4, 5, 6}, {3, 4, 5, 6, 7}, {1, 2, 3, 4, 5}}

–入れ子のリストから、指定フィールドのデータを取得する
–1アイテム目はフィールドラベル。2アイテム目以降をデータと見なして指定フィールドのデータをリストで返す
on getSpecifiedFieldData(aData, fieldName, sortAscendF)
  –与えられたデータaDataの1アイテム目はフィールド名と見なす
  
set fieldList to contents of first item of aData
  
set dataList to contents of items 2 thru -1 of aData
  
  
set fNum to getNumberOfField(fieldList, fieldName) of me
  
if fNum = 0 then return false
  
  
if sortAscendF = true then
    –Ascending
    
set resList to shellSortListAscending(dataList, fNum) of me
  else
    –Descending
    
set resList to shellSortListDecending(dataList, fNum) of me
  end if
  
  
–フィールドラベルを先頭に付けておく???
  
–set beginning of dataList to fieldList
  
  
return resList
  
end getSpecifiedFieldData

–与えられたリスト中における任意テキスト要素の出現アイテム番号
on getNumberOfField(aList, targFieldName)
  set aCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j is equal to targFieldName then
      return aCount
    end if
    
    
set aCount to aCount + 1
  end repeat
  
  
return 0 – no hit
end getNumberOfField

–シェルソートで入れ子のリストを昇順ソート
on shellSortListAscending(aSortList, keyItem)
  script oBj
    property list : aSortList
  end script
  
  
set n to count oBj’s list’s items
  
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 list of oBj
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of list of oBj) > (item keyItem of v))
          set (item (j + 1) of list of oBj) to (item (j - h + 1) of list of oBj)
          
set j to j - h
        end repeat
        
set item (j + 1) of list of oBj to v
      end repeat
    end if
  end repeat
  
  
return aSortList
end shellSortListAscending

–シェルソートで入れ子のリストを降順ソート
on shellSortListDecending(aSortList, keyItem)
  script oBj
    property list : aSortList
  end script
  
  
set n to count oBj’s list’s items
  
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 list of oBj
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of list of oBj) < (item keyItem of v))
          set (item (j + 1) of list of oBj) to (item (j - h + 1) of list of oBj)
          
set j to j - h
        end repeat
        
set item (j + 1) of list of oBj to v
      end repeat
    end if
  end repeat
  
  
return aSortList
end shellSortListDecending

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

04/22 入れ子のリストから、指定フィールドのデータを取得する

{{”Field1″, “Field2″, “Field3″}, {1,2,3}, {4,5,6}}のような入れ子のリストで、指定フィールド名のデータを取得して返すAppleScriptです。

CSVファイルをparseしてリストに変換し、リストに対して指定フィールドの値を抽出する場合に使用します。

exceltable1.png

オリジナルのデータは、Excel(あるいはNumbersやFileMaker Pro)上で、上図のような状態になっていることを想定しています。1行目がフィールド名ラベルになっているというパターンです(緑色の箇所)。

入れ子のリストの各指定項目を取り出すような場合には、「何アイテム目を取り出す」と記述するケースが多いところですが、アイテム番号を決め打ちでは処理の柔軟性がないので、1アイテム目をフィールドラベルと見なし、そのフィールドラベルで該当する項目番号をサーチして、項目名で指定できるようにしてみました。

AppleScript中心の処理を追求すると、なるべく「テキストエディタやFileMaker Proに依存した処理を行わない」ことが重要になってきます。置換や検索、ソートなどをこれらのアプリケーションに依存していると、いつまでもそのアプリケーションが存在しない環境では自分の処理ができないことになります。

スクリプト名:入れ子のリストから、指定フィールドのデータを取得する
set aList to {{"ID", "名称", "アドレス", "URL", "データ"}, {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}

set resList to getSpecifiedFieldData(aList, "名称") of me
–> {2, 3, 4}

–入れ子のリストから、指定フィールドのデータを取得する
–1アイテム目はフィールドラベル。2アイテム目以降をデータと見なして指定フィールドのデータをリストで返す
on getSpecifiedFieldData(aData, fieldName)
  –与えられたデータaDataの1アイテム目はフィールド名と見なす
  
set fieldList to contents of first item of aData
  
set dataList to contents of items 2 thru -1 of aData
  
  
set fNum to getNumberOfField(fieldList, fieldName) of me
  
if fNum = 0 then return false
  
  
set resList to {}
  
repeat with i in dataList
    set the end of resList to contents of item fNum of i
  end repeat
  
  
return resList
  
end getSpecifiedFieldData

–与えられたリスト中における任意テキスト要素の出現アイテム番号
on getNumberOfField(aList, targFieldName)
  set aCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j is equal to targFieldName then
      return aCount
    end if
    
    
set aCount to aCount + 1
  end repeat
  
  
return 0 – no hit
end getNumberOfField

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

04/22 入れ子のリストで、1アイテム目のアイテム数が他と同じかどうかチェック

{{1,2,3}, {4,5,6}}のような入れ子のリストで、全アイテムのアイテム数が同じかどうかチェックするAppleScriptです。基準となるアイテム数は1アイテム目({1,2,3})を用います。

CSVファイルをparseしてリストに変換し、リストに対して処理を行ってふたたびCSVファイルに書き出すような場合に使用します。合っているとtureを、そうでないとfalseを返します。たいして大きなデータを処理していなかったので、高速化処理などは一切行っていませんが……データの規模によっては高速化処理を行うことがふさわしいところです。

parseした結果としてすべてのアイテムでアイテム数が同じかどうかをチェックすることが目的です。

exceltable.png

オリジナルのデータは、Excel(あるいはNumbersやFileMaker Pro)上で、上図のような状態になっていることを想定しています。1行目がフィールド名ラベルになっているというパターンです(緑色の箇所)。

CSVからparseしたリストの項目数が合っていないと、そのあとでさまざまな処理を行おうにも……処理の内容を保証できません。そこで、まずは項目数が合っているかを検証すると安全でしょう。もちろん、フィールド付加などを行ったあとに項目数をカウントしてチェックを行うことも重要です。

ExcelなどからCSVファイルに書き出して、AppleScript側でCSVファイルをもとにデータ処理を行うケースは非常に多いです。AppleScriptの初心者が陥りがちなポイントですが……そのままExcelに対して必要なデータに(行や列を指定して)アクセスすると、当然のことながらデータ数が増えるとものすごく時間がかかります。selectionからデータを取得する例もありますが、selectionから取得できるデータの大きさに上限が存在していたりで、万能とはいえません(とくに、Mac OS X初期に開発されたExcel v.Xあたりはselectionで取得できるデータサイズの上限が小さめです)。

大量のデータ通信をアプリケーションに対して行って、それをもって「AppleScriptの処理が遅い」と判断するのは間違いです。AppleScriptの内部データ表現形式であるリスト型変数やレコードに取り込んで、その上で処理を行うのがセオリーです。

同様の例はテキストエディタのデータを処理する場合などに多々見られ……テキストエディタの編集中のファイルに対して、テキストエディタ経由でアクセスすれば当然のように遅くなります。しかし、「どのファイルを編集中なのか」という情報をテキストエディタから取得して、AppleScriptで自前でテキストファイルにアクセスして処理すれば圧倒的に高速になります。

スクリプト名:入れ子のリストで、1アイテム目のアイテム数が他と同じかどうかチェック
set aList to {{“ID”, “名称”, “アドレス”, “URL”, “データ”}, {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}}

–リスト中のすべての項目(データ行)のフィールド数が同じかどうかチェック
set chRes to chkItemNumInEveryLine(aList) of me
–> true

–入れ子のリストで、1アイテム目のアイテム数が他と同じかどうかチェック
on chkItemNumInEveryLine(aList)
  set aList1 to contents of first item of aList
  
set aList2 to contents of items 2 thru -1 of aList
  
  
set aList1Len to length of aList1
  
repeat with i in aList2
    set tmpLen to length of i
    
if tmpLen is not equal to aList1Len then
      return false
    end if
  end repeat
  
  
return true
end chkItemNumInEveryLine

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

04/21 Safariの最前面のWindowの内容を1枚もののPDFにレンダリングしてデスクトップへ

Safariでオープン中の最前面のWindowの内容をデスクトップにPDF出力するAppleScriptです。

Web関係の仕事をすると、地味に必要になってくる「指定ページを1枚ものの画像にまとめた」PDF。Keynoteの書類などに貼り付けてページ遷移の説明を行ったりするのは、よくある話です。

Safariの最前面のWindowでオープン中のURLを取得し、CLI Webレンダラー「Wkpdf」でPDFにレンダリング出力します。

safario.png

その際に、ファイル名は日付をもとに生成。1枚ものの「長いPDF」として出力するためにWkpdfのレンダリングオプションを指定してページネーションを抑止したり、背景画像の表示をイネーブルにしたりと、見所はそのぐらいで、あとはたいしたことのないあっさりとした処理ばかりです。

Script Menuに入れて使うと便利です。ただ、使用頻度は人によって個人差がありそうなので、実用性がきわめて高い便利なScriptの割には忘れ去られそうな可能性も(自分でも、作っていたことを忘れていました)。

Wkpdf自体のインストールについては、Terminalからコマンドを叩いて行っておく必要がありますが、たいして難しくないので大丈夫でしょう。

スクリプト名:最前面のWindowの内容を1枚もののPDFにレンダリングしてデスクトップへ
tell application “Safari”
  set wCount to count every window
  
if wCount < 1 then
    display dialog “Windowが存在しません” buttons {“OK”} default button 1
    
return
  end if
  
  
tell window 1
    set aInfo to properties
  end tell
  
  
set aDoc to document of aInfo
  
  
tell aDoc
    set aURL to URL
  end tell
  
end tell

set aFileName to “webOut” & (do shell script “date +%Y%m%d%H%M%S”)
renderURLtoPDF(aURL, aFileName) of me

on renderURLtoPDF(aURL, aFileName)
  set s1Text to “cd ~/Desktop && “
  
set outPath to POSIX path of (path to desktop) & aFileName & “.pdf”
  
  
set s2Text to “wkpdf –source “ & aURL & ” –paginate false –print-background –output “ & outPath
  
  
set sAll to s1Text & s2Text
  
do shell script sAll
end renderURLtoPDF

(*
–指定URLの内容をレンダリングしてPDFに書き出す
on renderURLtoPDF(aURL, aFileName)
  set dDir to POSIX path of (path to desktop from user domain)
  set outFile to dDir & aFileName & “.pdf”
  try
    do shell script “cd /usr/local/bin && /usr/local/bin/coral -f PDF -o ” & quoted form of outFile & ” ” & aURL & ” &”
  on error
    return false
  end try
  –return outFile
end renderURLtoPDF
*)

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

04/21 iPhoto, iTunes, Safariアップデートについて

iPhoto 9.2.3、iTunes 10.6.1、Safari 5.1.5の各アップデートが提供されていますが、これらのアプリケーションのAppleScript用語辞書に変更はありません。

04/21 Office v.2011 Service Pack 2による変更点

2012/4/13にOffice v.2011 サービスパック2(バージョン14.2.0)がオンライン配布開始となりました。

例によって、アップデート後にAppleScript用語辞書をAS Dictionaryで書き出して,前バージョンの用語辞書の内容と比較を行ってみました。

 Excel 14.1.2→14.2 変更なし
 Outlook 14.1.2→14.2 変更あり(大規模な変更)
 PowerPoint 14.1.4→14.2 変更あり(「player」関連のオブジェクト、命令が追加に)
 Word 14.1.4→14.2 変更なし

……Outlookについては、日常的に使用していないので用語辞書変更の影響度合いはなんとも言えないのですが、SP2で加えるレベルではないほどの追加(あくまで個人的な意見)が行われています。

04/13 AppleScriptObjCアプリをAppleScript対応に(3)

AppleScriptObjCのアプリをスクリプタブルにする話の続きです。

とりあえず、30分もかからずにASOCアプリをスクリプタブルにできました。超簡単です。

そこで、以前から疑問に思っていたことをテストしてみました。

Xcode上では、プロジェクトを構成するさまざまなファイルをローカライズすることが可能です。つまり、各国語環境用に個別にファイルを用意しておいて、対応する言語環境で別々の内容を表示させることができるようになっています。

そこで、sdefファイルをローカライズして、日本語環境下では日本語の解説文が入ったAppleScript用語辞書を表示させられるかを試してみました。

asocs8.png

▲ローカライズされたAppleScript用語辞書。日本のユーザーしか使わないようなツールに英語だけの用語辞書を付けておくことはナンセンス。このようにして分かりやすくできる

結果はばっちり大成功。日本語環境では、日本語で説明の入ったAppleScript用語辞書がオープンされることが確認できました。こうして英語の用語辞書のほかに日本語の用語辞書を用意しておけばよいのではないか? と思われました。

→ プロジェクトのダウンロード(90Kバイト)

※記事掲載当初はアーカイブのダウンロードリンクが切れていました。2012/4/15現在は修正してあります

■総評

正直、AppleScriptで書かれたプログラムをAppleScriptから呼び出すのだから、処理内容自体を呼び出し側に書けばよいようにも思えますし、速度の面でもあまりメリットが感じられません。

リスト要素のソートなど、Cocoaの機能を用いると高速化できるものもありますが、Mac OS X 10.7以降であればAppleScriptエディタ上で直接AppleScriptObjCのプログラムが記述でき、Cocoaの機能も呼び出すことができます。わざわざ、操作が繁雑なXcode上でそれを行うメリットが大きいとも思えません。

AppleScriptでOSAX(のようなもの……つまり、Invisible Processでウィンドウとかメニューなどを持たないアプリ)に近いものが作れるわけで、それについてはなかなか便利でしょう(ライブラリを整備するのと自前OSAX作成とどちらが労力が少なくて済むかは、判断つきかねます)。

ですが……単純にやってみて「おもしろい」と感じられました。もっと高度な命令も実装できることが確認できれば、応用範囲がいろいろと広がるのではないかと思われました。

AppleScript用語辞書の(言語環境に応じた)ローカライズや、一部のAppleのアプリケーションで試行されているサンプルスクリプトの用語辞書への同梱など、「こうできた方が便利では?」というアイデアを試す場として利用できる、とは思っています。

スクリプタブルなアプリケーションを作るのがここまで簡単だとは思わなかったので、そのことが分かったことが最大の成果だと感じました。他人のプログラムを見ながら試して、動くようになるのに30分もかかりませんでした。

04/13 AppleScriptObjCアプリをAppleScript対応に(2)

AppleScriptObjCアプリをスクリプタブルにした話の続きです。

r/oの属性ばかりではなく、書き換えできる属性値を用意し、これをGUIにつないで書き換えが目で見て分かるようにしてみました。

さきほどの用語辞書の属性値「message」はsdefファイルの定義によりAppleScriptObjCプログラム中の「theMessage」プロパティに対応。さらに、Xcode上でtheMessageプロパティをNSTextFieldのvalueにバインドしてみました。

AppleScriptObjCファイル名:asoc1AppDelegate.applescript

– asoc1AppDelegate.applescript
– asoc1

– Created by 長野谷 隆昌 on 12/04/12.
– Copyright 2012 Piyomaru Software. All rights reserved.


– http://macscripter.net/viewtopic.php?id=36000

– MacScripters Meetingの投稿をもとに、いらない部分をそぎ落として分かりやすいように整理したもの
– Original post by akader

script asoc1AppDelegate
  
  
property parent : class “NSObject”
  
  
property tF : missing value –bind to NSTextField
  
  
property theMessage : missing value –bind to tF’s value
  
  
  
  
  
on applicationWillFinishLaunching_(aNotification)
    
– Insert code here to initialize your application before any files are opened
  
end applicationWillFinishLaunching_
  
  
  
on applicationShouldTerminate_(sender)
    
– Insert code here to do any housekeeping before your application quits
    
return current application’s NSTerminateNow
  
end applicationShouldTerminate_
  
  
  
  
on application_delegateHandlesKey_(sender, theKey)
    
    
return theKey is in {“theMessage”}
    
  
end application_delegateHandlesKey_
  
  
end script

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

アプリケーション「asoc1」を実行すると、こんな感じです。

asocs5.png

スクリプト名:asoc1のメッセージを書き換える
tell application “asoc1″
  set message to “ぴよまるソフトウェア”
end tell

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

上記のAppleScriptを実行すると、

asocs6.png

のようにテキストフィールドの内容が変化します。逆に、テキストフィールドの内容を「message」属性を介して取得することもできます。

ただし、テキストフィールドに文字入力中の内容を取得しようとした場合、すぐにテキストフィールドの内容に対する変更が値に反映されないなどの現象が見られました。そこで、Xcode上でテキストフィールドのvalueをバインドしているところで、

asocs7.png

「Continuously Updates Value」のチェック項目があるので、チェックを入れると……入力した内容がすぐに属性「message」に反映されるようにはなるのですが、今度はアプリケーションの動作が若干もたつく感じがしました。外すとそのようなことはなくなったので、少しひかえめな連動を行うべきなのかもしれません。

「ひかえめな連動」というのは、GUI上で入力中のフィールド内容を即座に求めるのではなく、内容が確定して環境設定に書き込んだ内容に対してアクセスするような連動、ということです。キーボード入力された内容をすぐ取得するのは避けたほうがよいでしょう。

04/12 AppleScriptObjCアプリをAppleScript対応に(1)

MacScripter.netで探してAppleScriptObjCアプリをAppleScript対応(スクリプタブル)にする方法を確認してみました。貴重な情報を提供してくれている投稿者の方々には深く感謝しています。

実際の投稿記事はこちら。この一連の記事は同投稿を精査して、より単純化して資料を加え解説するものです。

■Info.plistを編集

まずはXcode上でAppleScriptObjCのプロジェクトを1つ作成してみましょう。サンプルでは、「asoc1」という名前のプロジェクトを作成しました。

asocs2.png

最初に、Info.plist(各Xcodeプロジェクト内でのファイル名は異なります。上の画面では「asoc1-info.plist」が該当)を編集し、キーが「Scriptable」値が「Yes」(Boolean)、キーが「Scripting Definition file name」値が「myApp.sdef」のエントリ(合計2つ)を作成します。

asocs1.png

■sdefファイルをプロジェクトに追加する

sdef(Scripting DEFinition)ファイルをプロジェクトに追加します。Xcodeで「New File」を実行し、「empty file」をプロジェクトに追加。追加ファイルのファイル名を「myApp.sdef」とします。

内容はこんな感じ。画像では内容が見えない場合には、あとでアーカイブ中の実物を見てください。

asocs3.png

なお、sdefファイルの記述がもっと簡単にできる、Shadow Labの「Sdef Editor」というフリー・ソフトウェアが存在します。もっと込み入ったAppleScript用語辞書を作成する場合には、同ソフトウェアを併用するとよいでしょう。

asocs9.png
▲Shadow LabのSdef Editor

■AppleScriptObjCプログラム側にハンドラ追加

on application_delegateHandlesKey_(sender, theKey) ハンドラを追加。予約語messageに対応する「theMessage」を受信したときにtrueを返します。ただ、それだけ。

■AppleScriptObjCプログラム側にプロパティ追加

property theMessage : missing value

これだけ追加しておきましょう。

■ためしに、ビルド

これでXcode上でCommand-Rでビルド&実行するだけで、スクリプタブルなアプリケーション(AppleScript用語辞書つき)になります。アプリケーションに対してプロパティを取得すると、これだけでアプリ名やバージョン番号などの最低限の情報を取得できます。

AppleScript用語辞書をAppleScriptエディタでオープンして内容を確認することも可能です。

スクリプト名:asoc1でアプリのプロパティを取得する
tell application “asoc1″
  properties
end tell

–> {message:missing value, frontmost:false, class:application, name:”asoc1″, version:”1.0″}

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

04/03 AppleScriptObjC Explorer2で編集中のScriptをすべて保存してXcodeでビルド実行 v2

AppleScriptObjC Exlorer 2で、編集中のScriptをすべて保存してXcodeの最前面の(アクティブな)プロジェクトをビルドして実行するAppleScriptです。

……なるべく1行で内容が分かるように心がけていますが、さすがになんのことやら分かりにくいので、説明します。

本ScriptはMac OS X 10.6.8+Xcode 4.1+AppleScriptObjC Explorer 2 2.2.1で作成。Mac OS X 10.7上でも動くはずです。

Xcode 4.0以降でAppleScriptObjCのサポートは「悲惨」のひとことに尽きます。インデントは狂うわ、構文確認時の各種ブロックの補完は行われないわ、AppleScript構文色分けがまったくきかないわで、生産性がダダ下り。AppleScript Studioの頃には、Mac OS X 10.5の頃まではなんとか構文色分けは維持されてきたものですが……。

asoc1.png

さすがに、Xcode 4.x内蔵のテキストエディタに愛想が尽きて、AppleScriptObjC専用外部エディタである「AppleScriptObjC Explorer 2」をお買い上げ。Xcode上のファイル一覧でAppleScriptを選択した状態でコンテクストメニューを表示。「Open With External Editor」を実行してAppleScriptObjC Explorer 2でAppleScriptのテキストを編集させるようにしています。

asoc2.png

これで、構文確認時の各種ブロック(ifブロックとかrepeatブロックとかtellブロックとか)の入力補完や、AppleScript構文色分けなどが利用できます。

ただ……Mac OS X 10.6/10.7ともに、Xcodeの外部エディタとして使用した場合に、AppleScriptObjC Explorer 2からはXcodeプロジェクトのビルドやら実行やらはできません(Mac OS X 10.7上でAppleScriptエディタ上と同様にAppleScriptObjCプログラムを単体で作成・実行できます)。

そこで、AppleScriptObjC Explorer 2側からXcodeのプロジェクトをビルド&実行するようにしてみたのが本AppleScriptです。

ただし、Xcode 4.x系のAppleScript対応機能は、いまだ実装の発展途上というか、工事中で機能不全な状態なので……まともにXcodeのオブジェクト階層をたどっていく気になりません。なげやりに、GUI Scripting経由でメニュー項目を呼び出したりしています。また、ビルド&実行対象は最前面のXcodeプロジェクトであって……複数のプロジェクトをXcode上でオープンしている場合に、外部エディタで編集しているScriptの所属するプロジェクトが、Xcode上でかならずしも最前面になっているわけではありませんので……そのへんは割り切って使っています。

Xcode 4でビルドを行わせる前に、AppleScriptObjC Explorer 2側でオープン中のScriptをすべてファイル保存させています。未保存のAppleScriptが存在した場合には保存をスキップします。

AppleScriptObjC Explorer 2用のScript Menuに入れて呼び出して使っています。

スクリプト名:AppleScriptObjC Explorer2で編集中のScriptをすべて保存してXcodeでビルド実行 v2
tell application “AppleScriptObjC Explorer 2″
  set dList to every document
  
  
set sList to {}
  
repeat with i in dList
    –documentのfile属性を取得(未保存だとmissing valueになっている)  
    
set tmpFile to file of i
    
    
if tmpFile is not equal to missing value then
      tell i –保存済みのdocumentのみ保存を実行
        save
        
set the end of sList to tmpFile
      end tell
    end if
  end repeat
end tell

–複数のプロジェクトをオープンしている場合にはアクティブ(最前面)のプロジェクトを対象に
–すごい投げやりな処理……
tell application “Xcode”
  set curPrjDoc to active workspace document
  
  
–最前面のプロジェクトが、必ずしもAppleScriptObjC Explorer 2で編集中のScriptが所属するプロジェクトとは限らない
  
–編集中のScriptのファイルパスから上位フォルダに存在するXcode書類を探索して、プロジェクトを特定してもいいかも
  
–ただ、Xcodeのタコさ加減にめまいがして、真剣にXcodeをAppleScriptからつっつく気になれない……
  
  
tell curPrjDoc
    build –ビルド
  end tell
  
end tell

–GUI Scriptingから実行指定
activate application “Xcode”
tell application “System Events”
  tell process “Xcode”
    click menu item “Run” of menu 1 of menu bar item “Product” of menu bar 1
  end tell
end tell

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

03/27 Safari 5.1.5でAppleScript用語辞書に変更なし

本日ソフトウェア・アップデートから配布されたSafari 5.1.5のAppleScript用語辞書を確認したところ、とくに前バージョンとの差異は検出されませんでした。

………………………………Safari 5.1.5で「www.apple.co.jp」を表示させると、ちゃんと「www.apple.com/jp」にリダイレクトされるようですが……目の錯覚でしょうか。それとも、サーバーの転送設定が修正されたのでしょうか。

03/24 AppleScriptレコードのラベルのリストを作成

レコードのラベルのリストを作成するAppleScriptです。

穴だらけで冗談みたいな処理内容ですが、とりあえず10分ぐらいで作ってみました。

AppleScriptのレコードを、とりあえず「卑怯な手段」でテキストに変換(技術もへったくりもない)。変換したら、適当にデリミタで分解して、分解されて出来上がったリストから、適宜内容を取り出すというものです。

レコードのデータの中に「:」を含む文字列が入っていない場合にのみ利用が可能です。そんな状況を保証できるわけもないので、ものすごく利用できるシーンは少ないと思います。

あとは、AppleScriptエディタ自体を操作して、AppleScriptエディタの構文色分け機能を使って超強引にラベル要素を取り出すという手段が残されていますが………………何もそこまで頑張る必要はないでしょう。

海外でその手のOSAXは存在していそうな感じはしますし、AppleScriptObjCを併用すると、もっとスマートに実現できるかもしれません。

昔、高校生の頃にアセンブラで組んでいたプログラムが本棚の奥から出てきましたが、もっとまともで真面目なプログラムを組んでいたような気が……。少なくとも、こんな極悪プログラムを組んではいなかったと思います。

ただ、できることは今の方が数万倍多いので、そういうものなのでしょう。

スクリプト名:AppleScript レコードのラベルのリストを作成
–AppleScript レコードのラベルのリストを作成

set aRec to {hogehoge:“abc”, hugahuga:“def”}
set aText to recToStr(aRec) of me

set a1Text to repChar(aText, “{”, “, “) of me
set a2Text to repChar(a1Text, “}”, “,”) of me

set curDelim to AppleScript’s text item delimiters
set AppleScript’s text item delimiters to {“, “, “:”}
set aList to text items of a2Text
set AppleScript’s text item delimiters to curDelim

set bList to items 2 thru -2 of aList
set aLen to length of bList

set resList to {}

repeat with i from 1 to aLen by 2
  set the end of resList to contents of item i of bList
end repeat

resList
–> {”hogehoge”, “hugahuga”}

–レコードをテキストに変換
on recToStr(aRec)
  –エラートラップを使って、わざとエラーを発生させ、エラーメッセージからレコードをstringに変換する
  
try
    set a to aRec as string
  on error aMsg
    set a to aMsg
  end try
  
  
set b to repChar(a, “のタイプを string に変換できません。”, “”)
  
–> “{aData:1, bData:2} “
  
return b
  
end recToStr

–文字置換ルーチン
on repChar(origText, targStr, repStr)
  set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end repChar

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

03/24 インストールされているアプリのAS辞書を書き出すv2

インストールされているアプリのAppleScript用語辞書をHTMLに一括書き出しするAppleScriptです。

実行したMac上にインストールされている/Applicationsフォルダ、ユーティリティーフォルダ、/Library/CoreServicesフォルダ、/Library/ScriptingAdditionsフォルダなどのアプリケーションのうち、Scriptableなもの(通常のAppleScriptからコントロール可能なもの。GUI Scriptingを経由ではなく)をピックアップして、AppleScript用語辞書をHTML書き出し、それを指定フォルダに出力します。

# 最新のXcodeは/Applicationsフォルダに入るので、一部の処理は無駄になっています

本バージョンでは、AppleScript用語辞書のHTML書き出しに伴い、プロセスが起動されてメモリーが圧迫されるのを防ぐために、対象アプリケーションのプロセスが起動したら、つぶさにkillコマンドで強制終了させています。ただし、killするのはvisible process(Dockにアイコンが表示されるプログラム)だけであり、Help Viewerのような「Dockには表示されないが、表示ウィンドウを持つ」ものはkillできていません。

また、実際に使ってみたところ……光学ドライブが内蔵されていないMacBook Air上でDVD PlayerのAppleScript用語辞書をHTML書き出ししようとして、DVDドライブが装備されていない旨エラーのダイアログが表示され……肝心のAppleScript用語辞書は書き出されていませんでした。実行時には外付けのDVDドライブでもつないでおくとよいでしょう。

OSのアップデートや、β版のOSの新版がリリースされたときに、AppleはAppleScript関連の用語辞書の修正などはアナウンスしないため、ユーザー側でマイナーバージョンごとに用語辞書をHTML書き出ししておき、前バージョンとの差分をFileMergeなどのdiffツールで比較。どこに修正が加わったかをチェックする必要があります。

ただし、このようなツールで分るのはAppleが用語辞書に対して加えた修正点だけであり、構文レベルでバグが潜んでいるとか(Mac OS X 10.3のときには「is in」演算子がバグっていて死ぬほどひどい目に遭わされました、、)、アプリケーションやOSそのものにバグがある場合には検出できません。

本気で、Appleが作るバグを検出するために数百とか数千本のAppleScriptを各種言語(英語、仏語、独語、日本語、中国語、韓国語など)環境でチェックを行うシステムを作ることを考えないではないですが、それはApple自身がとっくの昔にやっておくべき話であって、ユーザーがやる種類のものではないような気が…………。

スクリプト名:インストールされているアプリのAS辞書を書き出すv2

set outPath to choose folder with prompt “AS辞書HTML書き出し先フォルダを選択”

tell application “ASDictionary” to launch

set apList to getScriptableAppPathList() of me

set ngList to {} –処理実行時にエラーになったアプリケーション/OSAXのパスが入る

–メインループ
repeat with i in apList
  set j to contents of i
  
if j is not equal to {} or j is not equal to “” then
    
    
–アプリケーションファイルの情報を取得
    
tell application “Finder”
      set aInfo to info for j
      
set sVer to short version of aInfo
      
set sVer2 to prepareShortVersionStringNum(sVer) of me
      
set sVerStr to replaceText(sVer2 as string, “.”, “”) of me
      
set prodName to displayed name of aInfo
    end tell
    
    
set outPathStr to (outPath as string) & prodName & ” “ & sVerStr & “.html”
    
    
    
–HTML書き出し前のプロセス一覧を取得
    
tell application “System Events”
      set beforeList to (unix id of every process whose visible is true)
      
log beforeList
    end tell
    
    
    
–HTML書き出し(実行すると、当該のアプリケーションが起動するケース多し)
    
set htmlRes to false
    
tell application “ASDictionary”
      set exRes to export j to outPath using file formats {single file HTML} using styles {AppleScript} with showing hidden items without compacting classes
      
      
if success of (first item of exRes) = false then
        –Export失敗時
        
set the end of ngList to j
      else
        
        
–Export成功時
        
set htmlRes to true
        
set outFile to destination of (first item of exRes)
      end if
      
    end tell
    
    
    
–HTML書き出し後のプロセス一覧を取得(書き出し時に起動されたアプリケーションが増えている)
    
tell application “System Events”
      set afterList to (unix id of every process whose visible is true and name of it is not equal to “AppleScript Editor”)
      
–set afterList to (unix id of every process whose visible is true)
      
log afterList
    end tell
    
    
    
–プロセスIDの差分を取得する
    
set dRes to getListDiff(beforeList, afterList) of me
    
    
if dRes is not equal to {} then
      repeat with tmpUNIXID in dRes
        set anUNIXID to (contents of tmpUNIXID) as string
        
set killRes to killProcessByID(anUNIXID) of me
        
log killRes
      end repeat
    end if
    
    
    
–書き出したHTMLのリネーム
    
if htmlRes = true then
      tell application “Finder”
        tell file outFile
          set aName to name
          
          
–書き出したHTMLファイルにバージョン番号を反映させる
          
set bName to replaceText(aName, “-AS.html”, (” “ & sVerStr & “.html”)) of me
          
set name to bName
        end tell
      end tell
    end if
  end if
end repeat

ngList –書き出せなかったアプリの一覧

–デフォルトでインストールされているAS対応アプリケーションのリストを取得する
on getScriptableAppPathList()
  set apFol to path to applications folder
  
set utilFol to path to utilities folder
  
set sysFol to ((path to system folder) as string) & “Library:CoreServices:”
  
set sysLibFol to ((path to system folder) as string) & “Library:ScriptingAdditions:”
  
set devFol to ((path to startup disk) as string) & “Developer:Applications:”
  
set libPath to (path to library folder) & “ScriptingAdditins:”
  
  
tell application “Finder”
    –一般アプリケーション
    
tell folder apFol
      set ap1List to (every application file whose accepts high level events is true and has scripting terminology of it is true) as alias list
    end tell
    
    
–ユーティリティーフォルダー
    
tell folder utilFol
      set ap2List to (every application file whose accepts high level events is true and has scripting terminology of it is true) as alias list
    end tell
    
    
–SystemのCoreServicesフォルダ
    
tell folder sysFol
      set ap3List to (every application file whose has scripting terminology is true) as alias list
    end tell
    
    
–Developerフォルダ
    
try
      tell folder devFol
        set ap4List to (every application file whose has scripting terminology is true) as alias list
      end tell
    on error
      set ap4List to {}
    end try
    
    
–/System/Library/ScriptingAdditions フォルダのOSAX
    
try
      tell folder sysLibFol
        set osax1List to (every file whose name ends with “.osax”) as alias list
      end tell
    on error
      set osax1List to {}
    end try
    
    
    
–/Library/ScriptingAdditions フォルダのOSAX
    
try
      tell folder libPath
        set osax2List to (every file whose name ends with “.osax”) as alias list
      end tell
    on error
      set osax2List to {}
    end try
    
    
    
set appList to ap1List & ap2List & ap3List & ap4List & osax1List & osax2List
    
  end tell
  
  
return appList
  
end getScriptableAppPathList

–任意のデータから特定の文字列を置換
on replaceText(origData, origText, repText)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to {origText}
  
set origData to text items of origData
  
set AppleScript’s text item delimiters to {repText}
  
set origData to origData as text
  
set AppleScript’s text item delimiters to curDelim
  
–set b to origData as text
  
return origData
end replaceText

–short version文字列をよろしく処理する(major.minor1.minor2)
–short versionに英語や日本語の文字をズラズラ並べていた場合には所期の動作を行えない
on prepareShortVersionStringNum(sVer)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to {“.”}
  
set sList to text items of sVer
  
set AppleScript’s text item delimiters to curDelim
  
  
set sLen to length of sList
  
if sLen = 2 then
    set sVer to sVer & “.0″
  else if sLen = 1 then
    set sVer to sVer & “.0.0″
  end if
  
  
return sVer
end prepareShortVersionStringNum

–リスト間のdiffを取る
–aList: before List, bList: after List
on getListDiff(aList, bList)
  set diffList to {}
  
  
repeat with i in bList
    set j to contents of i
    
if j is not in aList then
      set the end of diffList to j
    end if
  end repeat
  
  
return diffList
end getListDiff

–指定プロセスをしつこくkillする
on killProcessByID(anID)
  set anID to anID as string
  
  
set errorCount to 20 –10 seconds to wait
  
  
repeat
    
    
tell application “System Events”
      set dID to every process whose unix id is equal to (anID as number)
    end tell
    
    
if dID = {} then return true
    
    
do shell script “kill -9 “ & anID
    
    
set errorCount to errorCount - 1
    
if errorCount = 0 then return false
    
    
delay 0.5
    
  end repeat
  
end killProcessByID

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

03/14 Safari 5.1.4 AppleScript用語辞書に変更なし

Safariの5.1.4アップデートがリリースされましたが、AppleScript用語辞書に変更はありませんでした。

03/12 Find same file name with different extension in same directory

This is a simple AppleScript to find same file name with different extension in same directory.

Sample Data:
finder1.jpg

Output Data:
–> {{”D120_a.jpg”, “D120_a.png”}, {”D120_c.gif”, “D120_c.jpg”, “D120_c.png”, “D120_c.txt”}}

スクリプト名:find same file name with different extension in same directory
–Speed up technic
script aRef
  property aList : {} –every file name of user selected folder ( work)
  
property bList : {} – every file name list (without duplicates) (work)
  
property cList : {} –duplicated file name list (work)
  
property dList : {} –output duplicated file names with different extensions
end script

–Initialize variables
set aList of aRef to {}
set bList of aRef to {}
set cList of aRef to {}
set dList of aRef to {}

set a to choose folder with prompt "Choose check Folder"

tell application "Finder"
  tell folder a
    set aList of aRef to name of every file
  end tell
end tell

–make pure file name list(without extension) to cList
repeat with i in aList of aRef
  set j to contents of i
  
set jj to retFileNameWithoutExt(j) of me
  
  
if jj is in bList of aRef then
    –duplicated file name & does not exist in cList
    
if jj is not in cList of aRef then
      set the end of cList of aRef to jj
    end if
    
    
set tmpName to jj & "."
    
    
tell application "Finder"
      tell folder a
        –set dList of aRef to dList of aRef & (name of every file whose name starts with tmpName)
        
set the end of dList of aRef to (name of every file whose name starts with tmpName)
      end tell
    end tell
    
  end if
  
  
set the end of bList of aRef to jj
end repeat

–Remove duplicates from List
set aRes to removeDuplicates(dList of aRef) of me

–> {{"D120_a.jpg", "D120_a.png"}, {"D120_c.gif", "D120_c.jpg", "D120_c.png", "D120_c.txt"}}

–Remove Duplicated Item from List
on removeDuplicates(aList)
  set newList to {}
  
repeat with i from 1 to (length of aList)
    set anItem to item 1 of aList
    
set aList to rest of aList
    
if {anItem} is not in aList then set end of newList to anItem
  end repeat
  
return newList
end removeDuplicates

–delete extension from file name
on retFileNameWithoutExt(fileNameStr)
  set fLen to length of fileNameStr
  
set revText to (reverse of (characters of fileNameStr)) as string –make reversed string
  
set anOffset to offset of "." in revText
  
set fRes to text 1 thru (fLen - anOffset) of fileNameStr
  
return fRes
end retFileNameWithoutExt

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

03/11 NSTextViewからのテキストデータ取得、テキスト設定サンプル

NSTextViewへのデータ設定、データ取得を行うAppleScriptObjCのサンプルです。

AppleScript Studioで作ってあったOpenSSLのユーティリティをAppleScriptObjCで作り直そうとしたときに、どうも一定文字以上のデータのエンコード時に文字化けが発生。その問題検証のために作成したサンプルです。

実際に試してみて、NSTextViewからの値の取得/設定時にはとくに問題がないことがはっきりしました。やはり、実際に組んで試してみないと。

tv1.jpg

→ プロジェクトのダウンロード(Xcode 4.1 on Mac OS X 10.6.8で検証)

AppleScriptObjCファイル名:textview1AppDelegate.applescript

– textview1AppDelegate.applescript
– textview1

– Created by 長野谷 隆昌 on 12/01/13.
– Copyright 2012 Piyomaru Software. All rights reserved.

script textview1AppDelegate
  
property parent : class “NSObject”
  
  
property aTV : missing value
  
property bTV : missing value
  
  
on applicationWillFinishLaunching_(aNotification)
    
– Insert code here to initialize your application before any files are opened
  
end applicationWillFinishLaunching_
  
  
on applicationShouldTerminate_(sender)
    
– Insert code here to do any housekeeping before your application quits
    
return current application’s NSTerminateNow
  
end applicationShouldTerminate_
  
  
on clicked_(sender)
    
    
set textStorage to (my aTV’s textStorage())
    
set theText to textStorage’s |string|()
    
display dialog (theText as string)
    
  
end clicked_
  
  
on clicked2_(sender)
    
    
    
set textStorage to (my aTV’s textStorage())
    
set theText to textStorage’s |string|()
    
    
set theText to theText as Unicode text
    
    
my bTV’s setString_(theText)
    
  
end clicked2_
  
  
on clicked3_(sender)
    
    
set textStorage to (my aTV’s textStorage())
    
set theTextA to textStorage’s |string|()
    
set theTextA to theTextA as Unicode text
    
    
set textStorage to (my bTV’s textStorage())
    
set theTextB to textStorage’s |string|()
    
set theTextB to theTextB as Unicode text
    
    
if theTextA = theTextB then
      
display dialog “左右のテキスト内容はイコールです”
    
else
      
display dialog “左右のテキスト内容は合っていません”
    
end if
    
  
end clicked3_
  
end script

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

03/10 シェルソート高速化比較(さらに改良)

先日投稿していただいたshell sortに、若干の改良を加えてさらに高速化したAppleScriptです。

tsuki3さんから投稿していただいたshell sortのコメントに、

ちなみに私の経験上、リストの要素数を得る場合は “lenth of ~” を使うよりも
“count ~” を用いた方が速いはずです。また、私のスクリプトでは 複数の要素を纏めて set しておりますが、
これを別個にすればより速くなるはずです。

とあったので、すべて試してみました。

sort2.jpg

MacBook Pro 2010 Core i7 2.66GHz, RAM 8GB, Mac OS X 10.6.8の環境で改良版の「shellSort5」を試してみたところ、shellSort4で5秒かかっていた3万件のソートが4秒に。正直、こんなに速くなるとは思ってもみませんでした。ループ回数が多いので、わずかなスピードアップの集積が大きいんでしょうね。

Classic Mac OSの時代には、サードパーティのOSAX(スクリプティング機能拡張)の機能を呼び出してハードウェア制御(キーボードLEDの点灯制御、シリアルポート経由の機器制御)を行ったり、高度な命令を利用することを目指していました。最終的には200個ぐらいのOSAXをシステムにインストールして使っていたほど。

ソートに関しても、サードパーティのOSAXで提供されるソート命令を利用することが多かったのですが、Mac OS Xの時代になってMac OS X対応できない(or しない)OSAXが多数あったため、OSAXへの依存度を下げることが必要になってきました。のちに、Universal Binary対応や64ビット対応など、さまざまなハードルがあってOSAX側の対応が後手後手に回る中、Mac OS Xへの移行当初からOSAXへの依存度を下げる努力をしてきたため、自分はあまり影響を受けませんでした。また、このOSAX依存度軽減の努力は、AppleScript StudioによるGUIベースのアプリケーション開発時にもおおいに役立ちました(アプリケーションと一緒に再配布できないライセンス条件のOSAXなどもあったため)。

正直なところ、ソートルーチンなんて一度作ってそこそこ満足できれば、見直すことなどほとんどない部品。最初のバージョンからここまでいろいろと高速化ができたのは、多くのScripterの方々の努力と経験によるところが大きく、1人でやっているだけでは、なかなか手が回らないところだと思います。

そういう意味では、こういうBlogを立ち上げて地道に活動してきた意義というのはあるのではないかと。この3月でスタートして4年が経過。掲載Scriptも1,000本を超えました。当初の、「サブルーチン資産に対するMac OS Xバージョンアップの影響検証」というねらいを超えて、技術的な交流の場として機能していけたらいいですね。

スクリプト名:(数値ソート) シェルソート_v2.scpt
set lst to {}
set lstr to (a reference to lst)
repeat 30000 times
  set lstr’s end to (random number 999)
end repeat
set t to (current date)

set res to my shellSort5(lst)

– 正誤確認用
(*set resr to (a reference to res)
repeat with i from 2 to (count resr)
  if (resr’s item (i - 1) > resr’s item i) then return false
end repeat*)

return {“” & ((current date) - t) & “秒”}

on shellSort1(lst) – 配列を使用した基本形
  set len to (count lst)
  
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, j} to {lst’s item (i + 1), i}
        
repeat while ((j gap) and (lst’s item (j - gap + 1) > temp))
          set {lst’s item (j + 1), j} to {lst’s item (j - gap + 1), j - gap}
        end repeat
        
set lst’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return lst
end shellSort1

on shellSort2(lst) – 参照 (a reference to…) を使用
  set {lstr, len, gap} to {a reference to lst, count lst, 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, j} to {lstr’s item (i + 1), i}
        
repeat while ((j gap) and (lstr’s item (j - gap + 1) > temp))
          set {lstr’s item (j + 1), j} to {lstr’s item (j - gap + 1), j - gap}
        end repeat
        
set lstr’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return lst
end shellSort2

on shellSort3(lst) – レコードと参照、グローバル変数を併用
  global rec
  
set rec to {list:lst}
  
set {recr, len, gap} to {a reference to rec, count lst, 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, j} to {recr’s list’s item (i + 1), i}
        
repeat while ((j gap) and (recr’s list’s item (j - gap + 1) > temp))
          set {recr’s list’s item (j + 1), j} to {recr’s list’s item (j - gap + 1), j - gap}
        end repeat
        
set recr’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return recr’s list
end shellSort3

on shellSort4(lst) – スクリプトオブジェクトを使用
  script o
    property list : lst
  end script
  
set {len, gap} to {count o’s list’s items, 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, j} to {o’s list’s item (i + 1), i}
        
repeat while ((j gap) and (o’s list’s item (j - gap + 1) > temp))
          set {o’s list’s item (j + 1), j} to {o’s list’s item (j - gap + 1), j - gap}
        end repeat
        
set o’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return o’s list
end shellSort4

–プレーンなAppleScriptベースでは最速ソート
on shellSort5(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 shellSort5

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

03/07 シェルソート高速化比較

AppleScriptにおけるシェルソートの高速化について、それぞれ検証するScriptをtsuki3さんから投稿していただきました。

3/3 の記事 “100倍速いシェルソートでリストをソート” 内、解説の項にて
高速化の計測を話題にされておりましたが、数年前に私が書いたスクリプトが
それを目的としたものでしたので、投稿いたします。

なお、拙作のスプリクトオブジェクトを用いたものの方が誤差程度ですが、
edama2さんのものより(当方の環境では)速いようです。
ただ、レギュレーションが違うので比較自体に意味が無いのかもしれませんが。

ちなみに私の経験上、リストの要素数を得る場合は “lenth of ~” を使うよりも
“count ~” を用いた方が速いはずです。また、私のスクリプトでは 複数の要素を纏めて set しておりますが、
これを別個にすればより速くなるはずです。

一度、きちんと比較しておかないと……とは思ってはいたのですが、ついついめんどくさくて手つかずの状態。ちょうどいいところに投稿していただけたので、実測値を計ってみることにしました。

計測するのは、以下の4つ。

 shell sort 1……高速化を一切行っていない、通常のシェルソート
 shell sort 2……オーソドックスな「a reference to」を使用して高速化
 shell sort 3……レコードと参照、グローバル変数を併用
 shell sort 4……スクリプトオブジェクトを使用

……shell sort 3は、ちょっとお目にかかったことのないタイプ。

ひととおり、MacBook Pro 2010 Core i7 2.66GHz, RAM 8GB, Mac OS X 10.6.8の環境でテストしてみると……

sort.jpg

スクリプトオブジェクトを使用するものが一番高速(AppleScriptObjCを使うと、そちらの方が速いですが)ということが客観的によく分りました。

スクリプト名:(数値ソート) シェルソート.scpt
set lst to {}
set lstr to (a reference to lst)
repeat 30000 times
  set lstr’s end to (random number 999)
end repeat
set t to (current date)

set res to my shellSort4(lst)

– 正誤確認用
(*set resr to (a reference to res)
repeat with i from 2 to (count resr)
  if (resr’s item (i - 1) > resr’s item i) then return false
end repeat*)

return {“” & ((current date) - t) & “秒”}

on shellSort1(lst) – 配列を使用した基本形
  set len to (count lst)
  
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, j} to {lst’s item (i + 1), i}
        
repeat while ((j gap) and (lst’s item (j - gap + 1) > temp))
          set {lst’s item (j + 1), j} to {lst’s item (j - gap + 1), j - gap}
        end repeat
        
set lst’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return lst
end shellSort1

on shellSort2(lst) – 参照 (a reference to…) を使用
  set {lstr, len, gap} to {a reference to lst, count lst, 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, j} to {lstr’s item (i + 1), i}
        
repeat while ((j gap) and (lstr’s item (j - gap + 1) > temp))
          set {lstr’s item (j + 1), j} to {lstr’s item (j - gap + 1), j - gap}
        end repeat
        
set lstr’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return lst
end shellSort2

on shellSort3(lst) – レコードと参照、グローバル変数を併用
  global rec
  
set rec to {list:lst}
  
set {recr, len, gap} to {a reference to rec, count lst, 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, j} to {recr’s list’s item (i + 1), i}
        
repeat while ((j gap) and (recr’s list’s item (j - gap + 1) > temp))
          set {recr’s list’s item (j + 1), j} to {recr’s list’s item (j - gap + 1), j - gap}
        end repeat
        
set recr’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return recr’s list
end shellSort3

on shellSort4(lst) – スクリプトオブジェクトを使用
  script o
    property list : lst
  end script
  
set {len, gap} to {count o’s list’s items, 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, j} to {o’s list’s item (i + 1), i}
        
repeat while ((j gap) and (o’s list’s item (j - gap + 1) > temp))
          set {o’s list’s item (j + 1), j} to {o’s list’s item (j - gap + 1), j - gap}
        end repeat
        
set o’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return o’s list
end shellSort4

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

03/07 InDesign CS3で指定のオプジェクトを、JPEG書き出ししてiPhoneへ

InDesign CS3上で指定したオブジェクトをJPEG書き出ししてiPhoneに転送するAppleScriptです。

ことのはじまりは、目下開発中のiPhoneアプリ(私はプロジェクト管理やら仕様書書きやらを)で、Welcome画面の表示を行うのに「文字詰めや行送りをいろいろ指定できないと!」という話になったこと。

……そもそも、iPhoneの文字表示部品に、そんな気の利いたものはありません。そこで、InDesignやらIllustrator上で気の済むまで行間や文字間にこだわっていただいて、それをそのまま「画像」として書き出して表示しようではないか、と。

ただし、その指定内容をすぐにiPhoneの実機で見たいという話に。

その場で10分もかけずにInDesign書類上の指定レイヤー(「コンテンツ」)上に存在する指定名称(スクリプトラベル「screen」)のグループをJPEG書き出しして、書き出したJPEG書類をiPhotoにインポートして、iTunesに命令して接続しているすべてのiOSデバイスにシンクロを行うようAppleScriptで指令を出すようにしてみました。

できた瞬間はかなりガッツポーズでしたが、冷静に考えるとInDesignから書き出されるJPEG画像のクオリティが「残念なレベル」です。また、シンクロが終わるまでにちょっと時間がかかるので、実用性がいまひとつ。

結局、Good Reader for iPhoneをインストールして、iTunes経由でGood Readerに対してInDesignから書き出したPDFを渡す、という方法に落ち着きました。本Scriptは何らかの「可能性」を感じさせてくれはしたのですが、結局おクラ入りに。

スクリプト名:InDesign CS3で指定のオプジェクトを、JPEG書き出ししてiPhoneへ
–v1 InDesign CS3から直接JPEG書き出し。画像クオリティが低くて難あり

set dtPath to (path to desktop) as string
set fName to do shell script "date +%Y%m%d%H%M%S"

set fullPath to dtPath & fName & ".jpg"
set fullPathPOSIX to POSIX path of fullPath

tell application "Adobe InDesign CS3"
  tell document 1
    tell layer "コンテンツ"
      set aList to every group whose label is equal to "screen"
    end tell
    
    
set expItem to contents of first item of aList
    
export expItem format JPG to fullPath without with grids
    
  end tell
end tell

–iPhotoに書き出したJPEGファイルをインポートする
tell application "iPhoto"
  import from fullPathPOSIX
end tell

–アップデート可能なiOSデバイスをアップデートする
tell application "iTunes"
  repeat with i in sources
    if (kind of i is iPod) then update i
  end repeat
end tell

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

03/07 テキストへの色指定サンプル

テキストに色指定を行うサンプルのAppleScriptObjCプログラムです。

textcol.jpg

AppleScriptObjCファイル名:textcolorTestAppDelegate.applescript

– textcolorTestAppDelegate.applescript
– textcolorTest

– Created by 長野谷 隆昌 on 12/02/13.
– Copyright 2012 Piyomaru Software. All rights reserved.

script textcolorTestAppDelegate
  
property parent : class "NSObject"
  
  
property nsc : class "NSColor"
  
property tf : missing value
  
property aTextField : missing value
  
  
on applicationWillFinishLaunching_(aNotification)
    
    
–ひとつめのNSTextField(入力フィールド)
    
set aString to current application’s NSMutableAttributedString’s alloc()’s initWithString_("これはテスト用の文字列ですよー")
    
    
set colorArray to current application’s NSArray’s arrayWithObjects_(nsc’s redColor(), nsc’s orangeColor(), nsc’s yellowColor(), nsc’s greenColor(), nsc’s blueColor, nsc’s purpleColor(), missing value)
    
    
repeat with i from 0 to (aString’s |length|()) - 1
      
aString’s addAttribute_value_range_(current application’s NSForegroundColorAttributeName, colorArray’s objectAtIndex_(i mod 6), {i, 1})
    
end repeat
    
    
tf’s setAttributedStringValue_(aString)
    
    
    
–ふたつめのNSTextField(Label)
    
set bString to current application’s NSMutableAttributedString’s alloc()’s initWithString_("●私のユーザー名")
    
    
set colorArray to current application’s NSArray’s arrayWithObjects_(nsc’s redColor(), nsc’s blackColor())
    
    
set bLen to bString’s |length|()
    
    
bString’s addAttribute_value_range_(current application’s NSForegroundColorAttributeName, colorArray’s objectAtIndex_(0), {0, 1})
    
bString’s addAttribute_value_range_(current application’s NSForegroundColorAttributeName, colorArray’s objectAtIndex_(1), {1, (bLen - 1)})
    
    
aTextField’s setAttributedStringValue_(bString)
    
  
end applicationWillFinishLaunching_
  
  
  
  
on applicationShouldTerminate_(sender)
    
return current application’s NSTerminateNow
  
end applicationShouldTerminate_
  
  
end script

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