Archive for 2月, 2015

2015/02/25 Conversion between UTCTime String and NSDate

Conversion ASOC script between UTCTime String (with milli-seconds) and NSDate.

In US Apple’s AppleScript Users ML, I saw the thread “UTC time with milliseconds”.

They talk about only current date -> UTCTime string (with milli-seconds) . And there is no care of each time zone.

So, I added time zone consideration and reverse conversion handler.

Reverse conversion (UTCTime string -> NSDate) does not need to get users time zone, I think.

Ole Begemann’s Working with Date and Time in Cocoa was very useful and helpful for me.

Shane Stanley taught me this is not right. Oh! my Buddha! (something like God..)

I misunderstood the definition of the word “UTCTime”. I meant it as a time string. So, I’ll fix this later. Hmm..I don’t need to get “UTCTime” string….Is there any need to get it?

AppleScript名:UTCTime StringとNSDateの相互変換
– Created 2015-02-24 by Shane Stanley
– Changed 2015-02-25 By Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to retUTCTimeString()
–>   “2015-02-25T13:49:55.713″

set aNSDate to retNSDateFromUTCString(aStr)
–>  (NSDate) 2015-02-25 13:49:55 +0000 –ASObjC Explorer
–> «class ocid» id «data optr0000000030F7400000600000» –Apple’s Script Editor

–Current Date -> UTCTime String
on retUTCTimeString()
  –There is need to get Current Calendar in my Time Zone
  
set aCalendar to current application’s NSCalendar’s currentCalendar()
  
set aTimeZone to (aCalendar’s timeZone)
  
set tDiff to (aTimeZone’s secondsFromGMT())
  
  
set theNSDateFormatter to current application’s NSDateFormatter’s alloc()’s init()
  
  
theNSDateFormatter’s setDateFormat:“yyyy-MM-dd’T’HH:mm:ss.SSS”
  
theNSDateFormatter’s setTimeZone:(current application’s NSTimeZone’s timeZoneForSecondsFromGMT:tDiff)
  
  
return (theNSDateFormatter’s stringFromDate:(current application’s NSDate’s |date|())) as text
end retUTCTimeString

–UTCTime String -> NSDate
on retNSDateFromUTCString(aText)
  set aStr to current application’s NSString’s stringWithString:aText
  
  
set theNSDateFormatter to current application’s NSDateFormatter’s alloc()’s init()
  
theNSDateFormatter’s setDateFormat:“yyyy-MM-dd’T’HH:mm:ss.SSS”
  
theNSDateFormatter’s setTimeZone:(current application’s NSTimeZone’s timeZoneForSecondsFromGMT:0)
  
  
return theNSDateFormatter’s dateFromString:aStr
end retNSDateFromUTCString

★Click Here to Open This Script 

2015/02/24 ASObjCExtras Scripting Guide v1.0 Erratta

I uploaded ASObjC Extras.framework Scripting Guide v1.0 Erratta.

The description and sample of

subarraysFrom: usingKeys: outKeys: error:

was wrong in Scripting Guide v1.0. So, I rewrote it.

New v1.1 document is in progress. It contains “Embedding” chapter. My work stops due to the business of some works…So I post the errata at first.

erratta.png

2015/02/23 GUI ScriptingでTeam Viewer10の画面上の情報を取得する

TeamViewer経由で遠隔地のMacを動かすために作ったAppleScriptです。PC/Mac/タブレット上で動作するリモート操作アプリケーション「TeamViewer」の画面上のIDとパスワードを取得します。

tv1.png

危ない用途のために作ったものではなく、自分で管理しているMacの遠隔操作のために作成したものです。

離れた場所にあるMacを遠隔操作する際には、OS X標準搭載のMessage.app(旧称:iChat)経由で画面共有を行うことが多いですが、うまくつながらないケースもあります。そうしたケースではTeamViewerを使うことがままあります。

ただ、TeamViewerは遠隔接続のためのIDとPasswordが(起動するたびに)変更になり、お客様にIDとPasswordを教えていただく必要が(汗)。なので、このやりとりをAppleScriptで自動化してしまおう、という話です(以前からやっていました。セキュリティが強化されたYosemiteで効くかどうか実験中)。

tv2.png

Message.appごしに自動応答でAppleScriptを実行させ、TeamViewerを起動してGUI Scriptingを用いてTeamViewerのIDとPasswordを取得。さらにこれをMessage.app経由でテキストチャットで返させる・・・という処理のための部品です。

IDとパスワードの検出のために、ルールにもとづく文字列検出ルーチンを使っています。このあたり、逆にASOCを使ったほうが短く書けそうな気がとてもしています、、、

調べてみたら、YosemiteのMessage.appにバグがあって、AppleScriptによる自動応答ができないことが判明しました(ーー;; バグレポートしておきましたが、Appleは純粋に「数」でバグ対処の優先順位を決めるので、多くの方々のAppleへのバグレポートをお願いしたいです。

mes1.png

mes2.png

AppleScript名:GUI ScirptingでTeam Viewer10の画面上の情報を取得する
activate application “TeamViewer”
delay 1
tell application “Sy stem Events”
  tell process “TeamViewer”
    tell group 2 of group 1 of window 1
      set tList to value of every static text
    end tell
  end tell
end tell

set ruleList to {“999 999 999″, “9999″} –ID, Pass Pattern
set hitList to {}
repeat with i in ruleList
  set j to contents of i
  
  
repeat with ii in tList
    set jj to contents of ii
    
set aRes to chkCode(jj, j) of me
    
set bRes to chkAllTrue(aRes) of me
    
if bRes = true then
      set the end of hitList to jj
      
exit repeat
    end if
  end repeat
end repeat

hitList
–> {”123 456 789″, “1234″}

–超簡易マクロ展開つきのコードチェック
–[99999]の展開を実装(1箇所のみ)。[99999]の場合には、0〜99999の可変桁チェックを実施
on chkCode(aCode, ruleCode)
  set macroPos1 to offset of “[” in ruleCode
  
set macroPos2 to offset of “]” in ruleCode
  
set ruleLen to length of ruleCode
  
  
if {macroPos1, macroPos2} = {0, 0} then
    set aRes to chkCodeSub(aCode, ruleCode) of me
    
  else if (macroPos1 * macroPos2) is not equal to 0 and (macroPos1 < macroPos2) then
    if macroPos1 > 1 then
      –1文字目以外の場合
      
set baseRule1 to text 1 thru (macroPos1 - 1) of ruleCode
    else
      –1文字目に存在する場合
      
set baseRule1 to “”
    end if
    
    
if macroPos2 is not equal to ruleLen then
      –末尾以外の場合
      
set baseRule2 to text (macroPos2 + 1) thru -1 of ruleCode
    else
      –末尾に存在した場合
      
set baseRule2 to “”
    end if
    
    
set paramLen to macroPos2 - macroPos1 - 1
    
set tmpStr to “9″
    
set hitF to false
    
    
repeat with i from 1 to paramLen
      set tmpRule to baseRule1 & tmpStr & baseRule2
      
set aRes to chkCodeSub(aCode, tmpRule) of me
      
if aRes is not equal to false then
        set hitF to true
        
exit repeat
      end if
      
set tmpStr to tmpStr & “9″
    end repeat
    
    
if hitF = false then return false
    
    
return aRes
    
  end if
end chkCode

–コードのチェックを行う(処理本体)
on chkCodeSub(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 chkCodeSub

–文字レンジを返す
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

on chkAllTrue(aList as list)
  if false is in aList then return false
  
return true
end chkAllTrue

★Click Here to Open This Script 

2015/02/15 バレンタインデーのお礼

奥方様がバレンタインデーのチョコレートをくれたので、その場で20分ぐらいで即興で作ったAppleScriptです。

ちなみに、チョコレートは二人で分けて食べました。

参考にしたのは、Chipmunk Basic用のlove.basで、プログラムリストを見たら動作原理がよくわかったので(フォントデータをOSから取得しているのではなく、出力パターンそのものをデータで持っている)、数値データ部分のみ使いまわしてフルスクラッチで書き直しました。

love_mode_e.jpg
▲Mode=”E” Output

love_mode_j.jpg
▲Mode=”J” Output

AppleScript名:love
– Created 2015-02-15 by Takaaki Naganoya
– 2015 Piyomaru Software

(*
20 rem PRINT “THANKS TO ROBERT INDIANA AND DAVID AHL”
21 rem print “contributed by ATStarr@Amherst.Edu 1996″
http://www.nicholson.com/rhn/basic/classic_programs/love.bas
*)

set charMode to “E” –J or E
set aTargChar to “makiko”
set outText to “”
set aSpc to returnSpc(charMode) of me

set aList to retLoveList()

set aPos to 0
set charFlag to true –false:space char

repeat with i in aList
  set j to contents of i
  
if charFlag = true then
    set aText to retCirculateStrs(aTargChar, j) of me
  else
    set aText to retCirculateStrs(aSpc, j) of me
  end if
  
  
set outText to outText & aText
  
  
set charFlag to not charFlag
  
  
set aPos to aPos + j
  
if aPos 60 then
    set aPos to 0
    
set outText to outText & return
    
set charFlag to true
  end if
end repeat

return outText

on retLoveList()
  return {60, 1, 12, 26, 9, 12, 3, 8, 24, 17, 8, 4, 6, 23, 21, 6, 4, 6, 22, 12, 5, 6, 5, 4, 6, 21, 11, 8, 6, 4, 4, 6, 21, 10, 10, 5, 4, 4, 6, 21, 9, 11, 5, 4, 4, 6, 21, 8, 11, 6, 4, 4, 6, 21, 7, 11, 7, 4, 4, 6, 21, 6, 11, 8, 4, 4, 6, 19, 1, 1, 5, 11, 9, 4, 4, 6, 19, 1, 1, 5, 10, 10, 4, 4, 6, 18, 2, 1, 6, 8, 11, 4, 4, 6, 17, 3, 1, 7, 5, 13, 4, 4, 6, 15, 5, 2, 23, 5, 1, 29, 5, 17, 8, 1, 29, 9, 9, 12, 1, 13, 5, 40, 1, 1, 13, 5, 40, 1, 4, 6, 13, 3, 10, 6, 12, 5, 1, 5, 6, 11, 3, 11, 6, 14, 3, 1, 5, 6, 11, 3, 11, 6, 15, 2, 1, 6, 6, 9, 3, 12, 6, 16, 1, 1, 6, 6, 9, 3, 12, 6, 7, 1, 10, 7, 6, 7, 3, 13, 6, 6, 2, 10, 7, 6, 7, 3, 13, 14, 10, 8, 6, 5, 3, 14, 6, 6, 2, 10, 8, 6, 5, 3, 14, 6, 7, 1, 10, 9, 6, 3, 3, 15, 6, 16, 1, 1, 9, 6, 3, 3, 15, 6, 15, 2, 1, 10, 6, 1, 3, 16, 6, 14, 3, 1, 10, 10, 16, 6, 12, 5, 1, 11, 8, 13, 27, 1, 11, 8, 13, 27, 1, 60}
end retLoveList

–Return Space (English Char/Japanese Char)
on returnSpc(aMode)
  if aMode = “E” then return ” “ –Single character Space
  
if aMode = “J” then return “ ” –Japanese Double Width Space
end returnSpc

on retCirculateStrs(aText, repCharTimes)
  set aLen to length of aText
  
set aTimes to repCharTimes div aLen
  
set aMod to repCharTimes mod aLen
  
set outStr to “”
  
  
repeat aTimes times
    set outStr to outStr & aText
  end repeat
  
  
if aMod is not equal to 0 then
    set aModStr to text 1 thru aMod of aText
    
set outStr to outStr & aModStr
  end if
  
  
return outStr
end retCirculateStrs

★Click Here to Open This Script 

2015/02/09 Photoshop Action Setをインストールする(ASOC)

PhotoshopのAction SetをインストールするAppleScriptのASOC版です。

AppleScript名:指定ファイルを指定Bundle IDのアプリでオープンする(ASOC)
– Created 2015-02-08 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AppKit”

set a to (choose file)
openFileWithApp(a, “com.adobe.photoshop”)

on openFileWithApp(aFileAlias as alias, aBundleID as string)
  set aURL to current application’s SMSFord’s URLFrom:aFileAlias
  
set aURLarray to current application’s NSArray’s arrayWithObject:aURL
  
set sharedWS to current application’s NSWorkspace’s sharedWorkspace()
  
  
sharedWS’s openURLs:aURLarray withAppBundleIdentifier:aBundleID options:(current application’s NSWorkspaceLaunchDefault) additionalEventParamDescriptor:(missing value) launchIdentifiers:(missing value)
end openFileWithApp

★Click Here to Open This Script 

2015/02/09 ダウンロードできないplist

Appleが「OS X:Mavericks でアクセシビリティとセキュリティ機能を使った AppleScript の使用方法」なるドキュメントをWeb上で公開していました。

saf1.png

Mavericks上のアクセシビリティについての説明と対策ドキュメントであり、けっこう重要です。

試してみたいと考え、掲載されているplistファイルをダウンロードしようとして・・・

saf2.png

一向にダウンロードできません(2015/2/9)。ページのHTMLソースコードを確認してみたところ・・・

saf3.png

何もリンクされていませんでした。そこでオリジナルの英文のページを探し出して、

saf4.png

ダウンロードしようとしたら、またできませんでした。

saf5.png

こちらもソースを確認してみたところ・・・

saf6.png

リンク先が存在していませんでした。英文のオリジナルがダメだったので、それを翻訳した文章もそのままになっていたということで、フィードバックしておきましたが・・・直るものやら。

→ こちらから直接ダウンロードできるそーです

2015/02/08 Photoshop Action Setをインストールする

Photoshop Action SetをインストールするAppleScriptです。

・・・どこにもPhotoshopのコントロール部分がありませんが、これでいいんです。

Photoshopの自動化を行うにあたって、Script側に提供されている機能が決して多くはないため、Photoshop ActionでGUI上の操作を記録して、Actionを併用しつつScriptで処理するというスタイルが一般的です。つまり、Actionの有無や内容が期待した通りになっていることがたいへんに重要です。

以前に、海外の仕事で調査を行っていたときに、Photoshopがサポートしているファイルタイプ(=Photoshopがオープンできるファイルタイプ)に、Photoshop Action Setが存在していることに気づきました。

Photoshop Action Setはファイルに書き出すことができるのですが、これを各環境にインストールするあたりの操作を自動で行うことができずに、なかなかスマートな解決策がないもんだなぁ、と思っていました。

actionset.png

ところが、単にPhotoshop Action SetのファイルをPhotoshopのアプリケーションアイコンにドラッグ&ドロップすればPhotoshopにインストールできることがわかりました(盲点)。

そこで、その操作をAppleScriptで再現してみたら・・・問題なくインストールできました。アプリケーションバンドル中にPhotoshop Action Setのファイルを仕込んでおいて、Photoshop上に該当のAction Setが存在していなかったら、インストール・・・ということをやってみました。SandBox化したアプリ内でも実行できたので、割といい感じです。

Photoshop Actionの存在確認は(JavaScriptをまじえつつ)行えるので、存在していない場合にはAction Setを自動でインストールさせることが可能です。

AppleScript側で関知しないところでユーザーがAction Setの内容を改ざんするなどして、処理内容が維持できなくなる事態を防ぐべく、Action SetをScriptからインストール/アンインストールすることは重要です。

AppleScript名:Photoshop Actionをインストールする
set anActionFile to choose file with prompt “Choose Photoshop Action File”

tell application “Finder”
  set appFile to application file id “com.adobe.photoshop”
  
open anActionFile with appFile
end tell

★Click Here to Open This Script 

2015/02/08 起動中のプロセスの存在確認(ASOC)v2

Bundle IDで指定したアプリケーションプロセスが起動中かどうかを調べるAppleScriptです。

v1は「とりあえずこんなもんで動くだろー。でももっと短く書けそう」というものでしたが、やっぱり短く書けました。Shane Stanleyからチェックが入って(汗)、もっと短く書けるよ、とのこと。感謝です(^ー^)

AppleScript名:指定Bundle IDのプロセス存在確認(ASOC)v2
– Created 2015-02-08 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AppKit”

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshop”)
–> true

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshopoo!”)
–> false

on chkAppProcesByBundleID(aBundleID)
  set appArray to current application’s NSRunningApplication’s runningApplicationsWithBundleIdentifier:aBundleID
  
return (appArray’s |count|() > 0)
end chkAppProcesByBundleID

★Click Here to Open This Script 

2015/02/08 起動中のプロセスの存在確認

Bundle IDで指定したプロセスが起動しているかどうかを確認するAppleScriptです。

AppleScript的には、System Eventsを経由してプロセスの起動中確認を行います。

AppleScript名:指定Bundle IDのプロセス存在確認(AS)
use AppleScript version “2.4″
use scripting additions

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshop”)
–> true

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshopoo!”)
–> false

on chkAppProcesByBundleID(aBundleID as string)
  tell application “System Events”
    set a to every process whose bundle identifier is aBundleID
    
if length of a 1 then
      return true
    else
      return false
    end if
  end tell
end chkAppProcesByBundleID

★Click Here to Open This Script 

しかし、この方法ではもしもMac App Storeに申請するアプリケーションで、System Eventsへのアクセスが封じられたらおしまいです。一応、shell script経由でpsコマンドを実行するという手段もありますが・・・

そこで、ASOCでCocoaの機能を用いてプロセスの存在確認を行ってみました。もうちょっと短く書けそうな気配もするのですが、とりあえず。

AppleScript名:指定Bundle IDのプロセス存在確認(ASOC)
– Created 2015-02-08 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AppKit”

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshop”)
–> true

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshopoo!”)
–> false

on chkAppProcesByBundleID(aBundleID as string)
  set appArray to current application’s NSWorkspace’s sharedWorkspace’s runningApplications()
  
set appList to appArray’s ASify() as list
  
  
repeat with i in appList
    set anID to (i’s bundleIdentifier()) as string
    
if anID = aBundleID then
      return true
    end if
  end repeat
  
  
return false
end chkAppProcesByBundleID

★Click Here to Open This Script 

2015/02/08 指定アプリケーションの存在確認

Bundle IDで指定したアプリケーションの存在確認を行うAppleScriptです。

古くから(Classic Mac OSの時代から)、AppleScriptにはFinder経由でアプリケーションの存在確認を行う手段が用意されてきました。いまはBunde IDで、Classic Mac OS時代にはcreator codeで存在確認を行ってきましたが、同様にFinderに対して問い合わせを行っていました。

AppleScript名:指定アプリケーションの存在確認(AS)
use AppleScript version “2.4″
use scripting additions

set aRes to chkAppIsInstalled(“com.adobe.photoshop”)
–> true

on chkAppIsInstalled(aBundleID as string)
  try
    tell application “Finder”
      set a to application file id “com.adobe.photoshop”
    end tell
    
return true
  on error
    return false
  end try
end chkAppIsInstalled

★Click Here to Open This Script 

そのため、一般的にはFinderで確認する方法で問題はないのですが、もしもMac AppStoreに申請を行うASOCのアプリケーションでFinder経由の確認を行っていた場合に、唐突に「この方法ではダメ」と言われかねません。

そこで、Cocoaの機能を利用して確認を行う方法について調査しておきました。速度面でとくに通常のASにくらべて有利ということはありませんが、SandBox内での実行が制限されづらいだろうということで書いておきました。

実際、有名なところではログイン時に自動起動されるStartup ItemsにAppleScript経由で登録する方法を採用すると、Mac App Store申請時にリジェクトされるという制限が知られています。

AppleScript名:指定アプリケーションの存在確認(ASOC)
– Created 2014-12-23 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

set aRes to chkAppIsInstalled(“com.adobe.photoshop”)
–>  true

on chkAppIsInstalled(aBundleID as string)
  set aRes to current application’s NSWorkspace’s sharedWorkspace()’s URLForApplicationWithBundleIdentifier:aBundleID
  
set bRes to (aRes is not equal to missing value)
  
return bRes
end chkAppIsInstalled

★Click Here to Open This Script 

2015/02/06 Xcodeでstringsファイルを取得して、行数をかぞえる

Xcode 6.1.1でASOCのプログラムを作っていて、ローカライズ情報を編集していたら、各言語のローカライズ情報に不整合が出てしまい・・・それを検出するためにためしに作ってみたAppleScriptです。

Xcode Project内のファイル一覧のselectionが取得できないかどうか試してみたものの、Xcodeのselectionはテキストエディタ上で選択したテキストのみであり、(いまさら言うまでもありませんが)それほど(XcodeをAppleScriptからコントロールして)凝った処理ができるわけではありません。

本来であれば、selected fileといった「ファイル一覧で選択中のファイル」を取得できるようにして、それらに対して処理を行うといったScriptが書けるべきです。しかし、現状のXcodeの用語辞書はそういう処理が書けるようにはなっていません。

そこで、用途をおもいっきり狭くして機能を限定的にして、

「Xcode Projectからproject directory(POSIX path)を取得して、mdfindで”MainMenu.strings”ファイルを検索して行数をかぞえる」

というScriptに仕様変更。行数をかぞえて各言語ごとの整合性を比較。行数をかぞえて見当をつけて、最終的にはFileMergeで各言語ごとの差異をチェックすることに。ほぼ作り捨てレベルのScriptです。

xcode10.png

せっかくXcodeのAppleScript用語辞書から、AppleScript Studio関連の膨大な用語を追い出したんですから、もっとまともな用語辞書を備えてもいいはずなんですが、XcodeのAppleScript用語辞書は「現場の開発作業」を何も便利にしないので、もう一回用語辞書を1から作り直したほうがいいと思います。

まるで、やらされ仕事というか、とりあえず仕事をしたという風に見せかけて実質何もよくなっていないという、一番よくない見本です。

Xcodeには文句しか言ったことはありませんが、最近になって、アプリケーションのテスト実行時の言語環境が指定できる機能がつきました。これはいいと思います。

xcode11.png

AppleScript名:Xcodeでstringsファイルを取得して、行数をかぞえる
tell application “Xcode”
  tell workspace document 1
    tell project 1
      set proDir to project directory
      
–> “/Users/me/Documents/Projects/ThisApp/”
    end tell
  end tell
end tell

set strList to getFileListWithSpotLight(“kMDItemFSName”, “MainMenu.strings”, proDir)
set sLenList to {}

repeat with i in strList
  set j to contents of i
  
set sRes to do shell script “wc -l “ & quoted form of POSIX path of j
  
  
set the end of sLenList to sRes
  
end repeat

sLenList
–> {” 161 /Users/me/Documents/Projects/ThisApp/v31__scripting_Final_Finishing/ASDiff/fr.lproj/MainMenu.strings”, ” 161 /Users/me/Documents/Projects/ThisApp/v31__scripting_Final_Finishing/ASDiff/en.lproj/MainMenu.strings”, ” 161 /Users/me/Documents/Projects/ThisApp/v31__scripting_Final_Finishing/ASDiff/ja.lproj/MainMenu.strings”}

–指定階層下で、指定メタデータが指定パラメータであるファイルを取得(alias list)
on getFileListWithSpotLight(aMetaDataItem as string, aParam as string, startDir as {alias, string})
  set sDirText to quoted form of POSIX path of startDir
  
set shellText to “/usr/bin/mdfind ’” & aMetaDataItem & ” == \”" & aParam & “\”’ -onlyin “ & sDirText
  
try
    set aRes to do shell script shellText
  on error
    return {}
  end try
  
set pList to paragraphs of aRes
  
set aList to {}
  
repeat with i in pList
    set aPath to POSIX file i
    
set aPath to aPath as alias
    
set the end of aList to aPath
  end repeat
  
return aList
end getFileListWithSpotLight

★Click Here to Open This Script 

2015/02/04 AppleScript用語辞書(sdef)にサンプルを掲載

いくつかのアプリケーションで、AppleScript用語辞書に書き方(サンプルScript)を掲載しています。この書き方について説明しておきます。

アプリケーションのsdefファイル中に、


<documentation>
    <html>
        < ![CDATA[

        --HTMLコードを直接書ける

        ]]>
    </html>
</documentation>

と、documentationタグを書いておき、その中にHTMLコンテンツを書けば、Script Editor上で表示されます。

asd1.png

asd2.png

asd3.png

現行のNumbers 3.5.2では、

numbers.png

のような資料がAppleScript用語辞書に記述されています。

このため、本Blogに掲載しているようなサンプルScript(Script Editorに内容を転送できるScriptリンクつき)をそのまま突っ込めます。

ただ、こういうのはAppleScript用語辞書が小さいツールに向いた方法であり、普通に展開すると数万行に及ぶような巨大なAppleScript用語辞書(InDesignとか)では、やらないでしょう。

巨大なAppleScript用語辞書はsdefファイルをテキストエディタでオープンすると、なぜか改行コードが削除してあり・・・おそらく、改行コードを削除することで(parseの)処理速度の向上を図るような(Classic Mac OS/PowerPC時代の、過去の)ノウハウだと思うのですが、現在ではどうなんでしょう。

AppleScript用語辞書にサンプルが掲載されていると便利だと思うのですが・・・

asdic.png

OSA言語を切り替えたときに、サンプルの内容(documentationタグの内容)も切り替えられるようになっているとよいのですが・・・現状ではそのような機構がないので、Script Editor上でOSA言語を切り替えると違和感がものすごくあります。

asdic2.png

2015/02/02 カレンダー.app(旧称iCal)で選択中のイベントの情報を取得

海外のBlogで、カレンダー.app(Calendar.app)の画面上で選択中のイベントの情報を取得するAppleScriptが公開されていました。

→ Reference selected Calendar events in AppleScript

カレンダー.appのAppleScript用語辞書にはselectionとかselected eventなどの予約語が用意されておらず、そのうえ「リマインダー.app」とイベントが共有されており区別できないなど、Apple純正アプリとしてはかなり「残念な出来」になっている筆頭残念アプリケーションです。

同Blogによれば、AppleScript用語辞書にselectionなどのプロパティは存在しないのに、設定ファイル”com.apple.ical.plist”には「SelectedEvents」のエントリが存在しているので、それをdefaults readコマンドで読み取って、parseして、sqlite3経由でsqlを発行して該当するイベントの情報を取得するのだとか。

・・・ここまでしないと取得できないことを嘆くべきか、存在しない機能を実現した努力を讃えるべきなのか、そもそも最初からselectionの機能があるべきという主張をすべきなのか、なかなか悩ましいところです。

カレンダー.app上でイベントを選択して同AppleScriptを実行してみたところ、どうも「選択中のイベントを取得できる」ときと「できない」ときがあります。ソース中にもそのように書いてあり、いろいろ試行錯誤してみたところ・・・

数行追加したら、バッチリ取れるようになりました。ただ、フィードバックするにも同Blogはコメント読んでないようで・・・修正版を掲載しておきます。オリジナルのScriptでは、取得した「選択中のイベント」の発生15分前にアラームを鳴らすようにしてありましたが、変更されるのがいやだったのでコメントアウトしてあります。

AppleScript名:カレンダー.app上で選択中のイベントを取得する
–https://www.johneday.com/1086/reference-selected-calendar-events-applescript
– Title: Reference selected Calendar events in AppleScript
– Created 2015-02-02 by @johneday on Twitter
– Modified 2015-02-02 by Takaaki Naganoya

use AppleScript version "2.4"
use scripting additions

——————————
– ABOUT
——————————
– Written and tested in Yosemite, Calendar Version 8.0
– The plist may take several seconds to update after a new event has been selected.
– Only the first instance of a recurring event will be referenced.

——————————-
– Modify by Takaaki Naganoya
——————————
tell application "Calendar"
  if running = false then return
  
activate
end tell
do shell script "sync"

——————————-
– MAIN CODE
——————————
set defaultsReply to (do shell script "defaults read com.apple.ical SelectedEvents")
set selectedEvents to parseDefaults(defaultsReply)

if selectedEvents = {} then
  display notification "Please try again" with title "No Calendar Event Selected"
  
return
end if

set eventReferenceList to {}
repeat with sEvent in selectedEvents
  set {eventID, calendarID} to sqlQuery(sEvent)
  
tell application "Calendar"
    set eventReference to event id eventID of calendar id calendarID
    
– INSERT YOUR CODE TO PROCESS EACH EVENT
    
    – Example of "Alert 15 minutes before start"
    
–my addDisplayAlarm(eventReference, -15)
    
    – OR BUILD A LIST OF EVENTS
    
set end of eventReferenceList to eventReference
  end tell
end repeat
return eventReferenceList

——————————
– HANDLERS
——————————
on parseDefaults(resultText)
  set localUIDs to {}
  
set {TID, text item delimiters} to {text item delimiters, quote}
  
set resultItems to text items of resultText
  
set text item delimiters to TID
  
repeat with i from 1 to (count resultItems)
    if i mod 2 = 0 then set end of localUIDs to resultItems’s item i
  end repeat
  
return localUIDs
end parseDefaults

on sqlQuery(localUID)
  local dateString, localUID
  
if localUID contains "/" then
    set {TID, text item delimiters} to {text item delimiters, "/"}
    
set {dateString, localUID} to text items of localUID
    
set text item delimiters to TID
  end if
  
  set sqlText to "
SELECT DISTINCT zcalendaritem.zshareduid AS eventID
, znode.zuid as calID
FROM zcalendaritem
JOIN znode
ON znode.z_pk = zcalendaritem.zcalendar
AND zcalendaritem.zlocaluid = ’" & localUID & "’
;"
  
  set sqlPath to POSIX path of (path to library folder from user domain) & "Calendars/Calendar Cache"
  
set {TID, text item delimiters} to {text item delimiters, "|"}
  
set {eID, cID} to text items of (do shell script "echo " & quoted form of sqlText & " | sqlite3 " & quoted form of sqlPath)
  
set text item delimiters to TID
  
return {eID, cID}
end sqlQuery

on addDisplayAlarm(myEvent, triggerInterval)
  tell application "Calendar"
    tell myEvent
      if not (exists (display alarms whose trigger interval = triggerInterval)) then
        set myAlarm to make new display alarm at end of display alarms with properties {trigger interval:triggerInterval}
      end if
    end tell
  end tell
end addDisplayAlarm

★Click Here to Open This Script 

2015/02/02 2月1日が日曜日でうるう年ではないかチェック(ASOC)

先日の「2月1日が日曜日でうるう年ではない(1日から28日までびっしり数字が入っている)」年を検出する(技術の無駄遣い的な ^ー^;)AppleScriptのASOC版です。Shane Stanleyが送ってくれました(こんなくだらないScriptに付き合わせて申し訳ない、、、)。

3,000年分計算して、だいたい0.8秒ぐらい。Pure AppleScript版より2倍以上高速ですが、3,000年程度の計算ではASOCの真の力が発揮できません。

AppleScript名:2月1日が日曜日でうるう年ではないかチェック(ASOC)
– Created 2015-02-02 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set resYList to {} –hit year

set theNSCalendar to current application’s NSCalendar’s currentCalendar() – do *not* use initWithCalendarIdentifier:

repeat with y from 0 to 3000
  set aMlen to getMlenAndVerifyFirstDaysNum(y, 2, theNSCalendar, 28, 1) of me
  
if aMlen = true then
    set the end of resYList to y
  end if
  
–set the end of resYList to aMlen
end repeat

return resYList

on getMlenAndVerifyFirstDaysNum(aYear, aMonth, theNSCalendar, dMax, fdayNum)
  
  
–現在のLocaleのCalendarで指定年月の日数をかぞえる
  
set theDate to theNSCalendar’s dateWithEra:1 |year|:aYear |month|:aMonth |day|:1 hour:0 minute:0 |second|:0 nanosecond:0
  
set theResult to theNSCalendar’s rangeOfUnit:(current application’s NSDayCalendarUnit) inUnit:(current application’s NSMonthCalendarUnit) forDate:theDate
  
–>  {location:1, length:31}
  
set mLen to |length| of theResult
  
  
–指定年月の1日を取得
  
set theDay to theNSCalendar’s components:((current application’s NSWeekdayCalendarUnit) + (current application’s NSMonthCalendarUnit as integer)) fromDate:theDate
  
  
–指定年月の1日の曜日が指定曜日で、うるう月でなく、指定年月の日数が指定日数(dMax)であるか判定
  
if (theDay’s |weekday|() = fdayNum) and (theDay’s isLeapMonth() is false) and (mLen = dMax) then
    return true
  else
    return false
  end if
  
end getMlenAndVerifyFirstDaysNum

★Click Here to Open This Script 

2015/02/02 国際化対応Mlen ASOC版

Shane Stanleyが送ってくれた、国際化対応MlenのASOC版(を、いろいろ検証して修正したもの)です。

そもそも、「Mlen」とは何か? 指定月(例:2012年2月)の日数を求めるMonth Length計算ルーチンです。そして、日本語環境だけではなく他の言語環境でも動くように配慮したものを「国際化対応Mlen」と呼んでいます。

Cocoaの機能を呼び出すようにした本バージョンでは、スピードアップが図られてはいますが、さすがに極小データの処理だけなので数千回ループで回してようやく「速い」とわかるぐらいです。数万件とか数十万件のオーダーになってくると、3倍以上高速かな? という感じです。

AppleScript名:getMlenInternational_ASOC
– Created 2015-02-02 by Shane Stanley
– Modified 2015-02-02 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set mList to {}
repeat with m from 1 to 12
  set the end of mList to getMlenInternational(2012, m) of me –2012 is a Leap Year–2012年はうるう年
end repeat
mList
–>  {​​​​​31, ​​​​​29, ​​​​​31, ​​​​​30, ​​​​​31, ​​​​​30, ​​​​​31, ​​​​​31, ​​​​​30, ​​​​​31, ​​​​​30, ​​​​​31​​​}

–現在のカレンダーで指定年月の日数を返す
on getMlenInternational(aYear, aMonth)
  –From Shane’s getMlenInternational(ASOC) v1
  
set theNSCalendar to current application’s NSCalendar’s currentCalendar() – do *not* use initWithCalendarIdentifier:
  
set theDate to theNSCalendar’s dateWithEra:1 |year|:aYear |month|:aMonth |day|:1 hour:0 minute:0 |second|:0 nanosecond:0
  
set theResult to theNSCalendar’s rangeOfUnit:(current application’s NSDayCalendarUnit) inUnit:(current application’s NSMonthCalendarUnit) forDate:theDate
  
–>  {location:1, length:31}
  
return |length| of theResult
end getMlenInternational

★Click Here to Open This Script 

2015/02/01 うるう年ではない年の2月で日曜日はじまりのものを返す

Twitter上で拡散されたデマ「日曜日から土曜日まで当てはまるのは今月が823年ぶり」に対して、実際にプログラムを組んで「そんなことはない」と反論されている人がいるのを見て、感心して同様のプログラムをAppleScriptでも作ってみました。

cal823.png

3,000年分を計算してMacBook Pro Retina 2012で1.7秒程度。サブルーチン部分は作り置きしていたものをそのまま再利用しただけなので、書くのに5分かかっていませんが、びみょーに見た目が長く見えるのは・・・そもそもAppleScriptの処理系にこういう計算を行う機能がないからです。

せっかくなので、AppleScriptObjCでCocoaの機能を呼び出して高速化してみようかとも思いましたが、こんなに手軽には書けず、しかもスピードアップの度合いが低い(3,000件ではデータが少なすぎて話にならない)ので途中で投げてしまいました。

注意:本Script中の「retDateOwithParam」はcurrent dateからdate objectを取得してパラメータを書き換えていますが、日付がズレる可能性があるのでこのやりかたは使わないでください。詳細については書籍「AppleScript最新リファレンス」にて解説しています。

AppleScript名:2月1日が日曜日でうるう年ではないかチェック
– Created 2015-02-01 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions

set resYList to {} –hit year

repeat with y from 0 to 3000
  
  
set aMlen to getMlenInternational(y, 2) of me
  
  
if aMlen = 28 then
    set aDatO to retDateOwithParam(y, 2, 1, 0, 0, 0)
    
set aWkd to (weekday of aDatO as number)
    
    
–y/2/1 = Sunday
    
if aWkd = 1 then
      set the end of resYList to y
    end if
  end if
end repeat

return resYList
–>  {9, 15, 26, 37, 43, 54, 65, 71, 82, 93, 99, 105, 111, 122, 133, 139, 150, 161, 167, 178, 189, 195, 201, 207, 218, 229, 235, 246, 257, 263, 274, 285, 291, 303, 314, 325, 331, 342, 353, 359, 370, 381, 387, 398, 409, 415, 426, 437, 443, 454, 465, 471, 482, 493, 499, 505, 511, 522, 533, 539, 550, 561, 567, 578, 589, 595, 601, 607, 618, 629, 635, 646, 657, 663, 674, 685, 691, 703, 714, 725, 731, 742, 753, 759, 770, 781, 787, 798, 809, 815, 826, 837, 843, 854, 865, 871, 882, 893, 899, 905, 911, 922, 933, 939, 950, 961, 967, 978, 989, 995, 1001, 1007, 1018, 1029, 1035, 1046, 1057, 1063, 1074, 1085, 1091, 1103, 1114, 1125, 1131, 1142, 1153, 1159, 1170, 1181, 1187, 1198, 1209, 1215, 1226, 1237, 1243, 1254, 1265, 1271, 1282, 1293, 1299, 1305, 1311, 1322, 1333, 1339, 1350, 1361, 1367, 1378, 1389, 1395, 1401, 1407, 1418, 1429, 1435, 1446, 1457, 1463, 1474, 1485, 1491, 1503, 1514, 1525, 1531, 1542, 1553, 1559, 1570, 1581, 1587, 1598, 1609, 1615, 1626, 1637, 1643, 1654, 1665, 1671, 1682, 1693, 1699, 1705, 1711, 1722, 1733, 1739, 1750, 1761, 1767, 1778, 1789, 1795, 1801, 1807, 1818, 1829, 1835, 1846, 1857, 1863, 1874, 1885, 1891, 1903, 1914, 1925, 1931, 1942, 1953, 1959, 1970, 1981, 1987, 1998, 2009, 2015, 2026, 2037, 2043, 2054, 2065, 2071, 2082, 2093, 2099, 2105, 2111, 2122, 2133, 2139, 2150, 2161, 2167, 2178, 2189, 2195, 2201, 2207, 2218, 2229, 2235, 2246, 2257, 2263, 2274, 2285, 2291, 2303, 2314, 2325, <
>}

on getMlenInternational(aYear, aMonth)
  set sDat to retDateOwithParam(aYear, aMonth, 1, 0, 0, 0) of me
  
if aMonth is not equal to 12 then
    set eDat to retDateOwithParam(aYear, aMonth + 1, 1, 0, 0, 0) of me
  else
    set eDat to retDateOwithParam(aYear + 1, 1, 1, 0, 0, 0) of me
  end if
  
  
set eDat to eDat - 1
  
  
set mLen to day of eDat
  
  
return mLen
end getMlenInternational

–国際化対応 パラメータによるdateオブジェクト作成ルーチン
on retDateOwithParam(yearNum, monthNum, dateNum, hourNum, minuteNum, secondNum)
  set aDateO to current date
  
  
set year of aDateO to yearNum
  
set month of aDateO to monthNum
  
set day of aDateO to dateNum
  
set hours of aDateO to hourNum
  
set minutes of aDateO to minuteNum
  
set seconds of aDateO to secondNum
  
  
return aDateO
  
end retDateOwithParam

★Click Here to Open This Script 

2015/02/01 badcharanさんがXcode 6.1.1のASOC用ファイルテンプレートを公開

Xcodeの新しいバージョンが公開されるごとに、ASOCのプロジェクトテンプレート「Cocoa-AppleScript」がどこに配置されているかを確認してしまうところ。Cocoa-AppleScriptが「Application」ではなく「Other」に分類されているのはいやな感じです(カスタマイズしたいな〜)。

sc1.png

さらに、XcodeプロジェクトにFileを追加しようとしても、ファイルテンプレートとして「AppleScirpt Class」が存在しておらず、「Other」の「Empty」を選択させられるのが微妙に「大きなScriptアプリケーションを作らせない」ようにクパティーノから嫌がらせを受けているような気がしていました(QuartzComposerみたいに、A社のエンジニアから露骨に「もうバグも直さないし機能追加もしません」宣言をされるよりはマシですけれども)。

sc2.png

そう思っていたのが自分だけではなかったのか、badcharanさんがXcodeプロジェクトでAppleScript(ASOC)ファイルの追加を楽にできるよう、ファイルテンプレートを公開されました

ダウンロードしてきて、内容を確認して・・・内容が理解できたのでさっそく自分でも追加してみました。

sc4.png

sc6.png

sc5.png

自分的にマイブームがきている「ASOCアプリのScriptable化」でよく作る、NSScriptCommandをparent classに設定してある.applescriptファイル。ついでに、最初から「on performDefaultImplementation()」ハンドラを宣言しておきます。my directParameter()にアクセスするコードも書いておきましょう。

sc3.png

Xcode上のASOCについては、いまひとつ生産性の向上が図りづらかったことがありますが、このあたりから生産性をガンガン向上させていきたいところです。

ASOCでCustom Viewを手軽に作れるようになるとよいのですが、、、

2015/02/01 ScriptableなASOCアプリからrecord型の値を返す

Xcode上でGUIベースのASOCアプリを開発できるわけですが、これをAppleScriptからコントロール可能な「Scriptableな」アプリケーションにすることは可能です。具体的な作業については、Shane Stanleyの電子ブック「AppleScriptObjC Explored fifth edition」に詳しく書かれています。

同書ではAppleScript側に返す「返り値」について、stringとfile型を扱っていました。

ASOCからrecord型のデータを返す

ASOCアプリからrecord型でAppleScriptに値を返すのは「大変」と聞いていたのですが、追加調査を行ったところ3つ方法があることがわかりました。

タイプ1:resultのtypeをsdef上で自前で定義

1つは、sdefファイル中でコマンドの返り値を「record」ではなく「別のもの」として宣言すること。「別のもの」を「record type」として宣言。それだけ。AppleScriptへの返り値としてrecordっぽく値が返りますが、ラベルについているのは一般的なrecordのラベルではなく「アプリケーション予約語」。該当するアプリケーション(ASOCアプリ)へのtellブロック外でrecord内の要素にアクセスするとエラーになります。
scriptable1.png

タイプ2:Objective-Cの併用による強制型変換。sdef上でresult typeをrecordと記述

もう1つは、Ron ReuterがDirect Mailで教えてくれたもので、Objective-CでNSDictionaryを拡張しておいて、sdefファイルにはコマンドの返り値として「record」を記述するというもの。普通にASOCアプリ側からrecordを返せました。返り値(record)内の要素にASOCアプリへのtellブロック外でアクセスしてもエラーになりませんでした。

タイプ3:ASOCらしくASOCで強制型変換。sdef上でresult typeをanyと記述

最後のものも、Ron ReuterがAppleScriptObjC Dev ML上で公開したもので、ASOC側から値を返すさいにCocoaの機能を呼び出して加工するもの。普通にrecordがAppleScript側に返ってくるので、ASOCアプリへのtellブロック外で返り値の要素にアクセスしてもエラーになりません。sdef上ではresultのtypeを「any」と書いておくのがポイントです。
scriptable2.png

ただ、返り値に「any」って書いてあったら、AppleScriptを書く側からするとかなり困惑します。ここが、このタイプ3を選ぶかどうかのポイントになるでしょう。

scriptable100.png

これらのやりかたで、

–> {aLabel:”test1″}
–> {aLabel:{”test1″, “test2″, “test3″}}

といった値を返せるようになりました。ここ2・3日で「できない」ことが「手軽にできる」ようになったわけで、実にけっこうなことです。

これら3つの方法について、すべてサンプルProjectを作ってひととおり試してみました。

難易度でいえば、タイプ1が一番簡単です。返り値の取り扱いの簡単さ(普通のレコードとして扱える)でいえばタイプ2とタイプ3が簡単。タイプ1かタイプ3を選択して使ってみるといいかも、というところかと。タイプ3も、badcharanさんのXcode Projectテンプレートの中に変換用ハンドラごとテンプレート化しておけば実装コストもほぼゼロに

まだ返せないデータ形式

ひととおりデータを返せるようになってきた気がしますが、

–> {{”test”, “test2″}, {”test3″, “test4″}}

といった入れ子のlist(2D list)は返せていませんし、

–> {aLabel:{bLabel:”test1″, cLabel:”test2″, dLabel:”test3″}}

といった入れ子のrecordも返せていません。

ただ、一応recordは返せるようになっているので、複雑な構造のデータは返さないようにすれば、問題はないでしょう。