Archive for the '10.09 savvy' Category

2017/05/18 簡単な素因数分解v3

簡単な素因数分解を行うAppleScriptです。

これまで、素数のチェックを行う手間を省いて素数リストをデータに持って素因数分解を行ったりはしていましたが、素数のチェック自体はそれほど難しくないことに気づきまして(汗)

  「素数=1とそれ自体以外で割り切れない数」

ということで、普通に2から対象の数-1までループして、途中で割り切れたら素数ではない、というだけでチェックが可能でした。

素数のチェックさえ計算できてしまえば、応用で素因数分解も簡単なので、すぐに素因数分解までできました。

本格的に使うのであれば、素数計算の結果を「計算キャッシュ」としてファイルにキャッシュしておき、同じ数の素数判定演算を2回目以降は行わないようにするとよさそうです。ループで計算を行うコスト(時間)と、計算キャッシュの内容を取り出すコスト(時間)を実際に計測して釣り合いが取れるかどうか確認する必要はあるでしょう。

ループして割り切れないかどうかチェックしているだけなので、チェック対象の数値が大きくなると純粋に処理時間が伸びてしまいます。75777773の素数チェックを行ってみたら、本Scriptでは35秒。Objective-Cの素数チェックルーチンをFramework化して呼び出してみたら0.23秒でした。キャッシュするよりも、Objective-CのFrameworkを呼び出してみたほうが速度的にメリットが大きいかもしれません。

ただ、チェック対象の数が小さいと、Framework呼び出しの方が遅くなる傾向がありました。複数の手段をミックスして、チェック対象の数の大きさで場合分けするのがいいのかも。

AppleScript名:与えられた数が素数かどうかチェック
– Created 2017-05-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
–http://piyocast.com/as/archives/4644

set pRes to checkPrimeNumber(24) of me
–> false

set pRes to checkPrimeNumber(1021) of me
–> true

set pRes to checkPrimeNumber(311) of me
–> true

–素数チェック
on checkPrimeNumber(aNum)
  repeat with i from 2 to aNum - 1
    if aNum mod i is equal to 0 then
      return false
    end if
  end repeat
  
return true
end checkPrimeNumber

★Click Here to Open This Script 

AppleScript名:簡単な素因数分解v3
– Created 2017-05-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
–http://piyocast.com/as/archives/4644

set aRes to getPrimeFactor(18) of me
–> {2, 3, 3}

set aRes to getPrimeFactor(254) of me
–> {2, 127}

set aRes to getPrimeFactor(1021) of me
–> {1021} –素数

–与えられた数を素因数分解する
on getPrimeFactor(aTargNum)
  copy aTargNum to a
  
set pFactList to {}
  
  
repeat
    set pRes to checkPrimeNumber(a) of me
    
if pRes = true then
      set the end of pFactList to a
      
return pFactList
    end if
    
    
set aFactor to checkFactor(a) of me
    
set the end of pFactList to aFactor
    
set a to a div aFactor
  end repeat
end getPrimeFactor

–素数チェック
on checkPrimeNumber(aNum)
  repeat with i from 2 to aNum - 1
    if aNum mod i is equal to 0 then
      return false
    end if
  end repeat
  
return true
end checkPrimeNumber

–素因数チェック
on checkFactor(aNum)
  repeat with i from 2 to aNum - 1
    if aNum mod i is equal to 0 then
      return i
    end if
  end repeat
end checkFactor

★Click Here to Open This Script 

2017/04/17 リストをクラス名でフィルタする v0

リストをクラス名でフィルタするAppleScriptです。指定クラスに合致するデータをリスト中から抽出します。

Charanさんから先日掲載したリストに対して「もっと簡単に書けるよ」と指摘があって、試してみたらそのとおり! ひととおり動作確認して掲載しておくことにしました。

一応、対応OSを10.9からと記載してありますが、この内容だとおそらくもっと古いOSでも問題なく動作することでしょう。

as137.png
▲Classic Mac OS 8.6上のAppleScript J1-1.3.7で動作することを確認

AppleScript名:リストをクラス名でフィルタする v0
–http://piyocast.com/as/archives/4588

set theList to {1, 2, 2.0, “aaa”, “bbb”, {a:1}, {1, 3, 4, 5, 6}, false, true}

lists of theList
–>{{1, 3, 4, 5, 6}}
text of theList
–> {”aaa”, “bbb”}
strings of theList
–> {”aaa”, “bbb”}
–unicode texts of theList
–> error
records of theList
–> {{a:1}}
booleans of theList
–> {false, true}
reals of theList
–> {2.0}
integers of theList
–> {1, 2}
numbers of theList
–> {1, 2, 2.0}

★Click Here to Open This Script 

「integers」ということは「every integer」と書き換えることが可能なので、そのとおりに書き換えてみたら、予想通り動きました。

AppleScript名:リストをクラス名でフィルタする v0a
–http://piyocast.com/as/archives/4588

set theList to {1, 2, 2.0, "aaa", "bbb", {a:1}, {1, 3, 4, 5, 6}, false, true}

set bList to every integer of theList
–> {1, 2}
set cList to every real of theList
–> {2.0}
set dList to every string of theList
–> {"aaa", "bbb"}
set eList to every list of theList
–> {{1, 3, 4, 5, 6}}
set fList to every record of theList
–> {{a:1}}
set gList to every boolean of theList
–> {false, true}

★Click Here to Open This Script 

2017/03/24 はじめてresultが役に立った

これまで、「result」が役に立ったことはなく、AppleのサンプルScript中で多用されているのを苦々しく思っておりました。

ところが、クラウド経由で遠隔地のMacにAppleScriptを実行させる仕組みを作っていたとき、たまたま処理結果を返してこないScriptを「run script」コマンドで実行したところ・・・・結果を(なんでもテキスト化して)返す処理部分でエラー。

つまり、run scriptコマンドの結果を代入している変数には何も入っていないので(beepコマンドだし)その変数自体が定義されていないというエラーに。

そこで、直接run scriptコマンドの結果を変数に代入せずに、実行結果をresultから取得。resultから取得する部分にエラートラップを仕掛けて「コマンドの実行結果が、何も値を返さない」パターンに対処するようにしてみました。

これで、無事対応でき、遠隔地の複数のMacにクラウド経由でコマンドを送って結果を取得できるようになりました。実に、はじめてresultが役に立ちました。

Siriみたいな自然言語処理コマンドインタフェースや、音声認識インタフェース、果ては全国規模の分散プリントを実現するGUIアプリケーションまでAppleScriptで作りましたが、resultが役に立ったのはこれがはじめてです。

AppleScript名:はじめてresultが役に立った
run script “beep 1″
try
  set aTmp to result
  
set a to aTmp
on error
  set a to “”
end try

return a

★Click Here to Open This Script 

2017/02/08 指定文字列一括消去

複数の文字列を一括で消去するAppleScriptです。

unicodechecker_65532.png

ASOCでどうしても置換できないゴミ文字列(string id 65532)に遭遇し、Cocoaベースの置換ルーチンからPure AppleScriptの置換ルーチンに置き換えることになりました。

その折、複数回の置換処理を行なっている部分が気になったので、複数の文字を一括で置換するようにしてみたのが本ルーチンです。消去対象の文字列同士のコンフリクトなどは一切チェックしていません。

当初、一括置換ルーチンも掲載していたのですが、動作内容に納得がいかなかったので削除しました。

AppleScript名:指定文字列一括消去
– Created 2017-02-08 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4445

set aText to “Gundam, Guncannon, Guntank, Zak, Gufu, Gogg, Gergog, Zeong, Neo-Zeong”
set deleteTargList to {“Gun”, “Zeon”}

set aRes to removeChars(aText, deleteTargList) of me
–> “dam, cannon, tank, Zak, Gufu, Gogg, Gergog, g, Neo-g”

–指定文字列一括消去
on removeChars(origText as string, targStr as list)
  set aLen to length of targStr
  
set repStr to {}
  
repeat with i from 1 to aLen
    set the end of repStr to “”
  end repeat
  
  
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 removeChars

★Click Here to Open This Script 

2016/12/01 現在位置の住所を取得する

Location Helperを利用して(Assisted GPS)現在のGPS座標を取得し、GPS座標をもとに住所を取得するAppleScriptです。

実行時にはWiFiによるネットワーク接続を行なっていることが必要です。Location HelperはMac AppStoreで無料で配布されています。

現在位置の取得についてはAppleScriptだけで(Location Helperを使わずに)行うこともできるわけですが、手軽ということでいえば圧倒的にLocation Helperです。

ただし、Location HelperはGoogleのWebサービスを呼び出しているため、連続して何度も(短時間に)呼び出すと、ペナルティが発生する可能性があります。また、Location Helper自体を勝手に再配布できないため、個人的にはなるべくこれに依存しないように書いています(客先に納品するという概念が存在しない場所では関係のない話です)。

とはいうものの、緯度経度情報から住所を取得する「逆住所ジオコーディング」についてはLocation Helperに頼る必要があるので、とりあえず一番簡単な記述を行なってみました。

ちなみに、Location Helperは不可視プロセスで実行されるため、Dock上にアイコンが表示されたりしません。AppleScriptから呼び出すための専用ツールです。

問い合わせを行なった結果は、同じ住所情報が異なる表現方法で11パターンほど返ってきました。ここに掲載できないほどズバリの住所が返ってくるので、実際に役に立つものと思われます。

AppleScript名:現在位置の住所を取得する
– Created 2016-12-01 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
–http://piyocast.com/as/archives/4338

tell application “Location Helper”
  set {aLat, aLong} to get location coordinates
  
set aRes to reverse geocode location {aLat, aLong}
end tell

★Click Here to Open This Script 

2016/10/04 SDカードと思われるドライブを抽出

Finderで、マウント中のドライブのうちSDカードと思われるものを抽出するAppleScriptです。

抽出条件は、

 ディスクフォーマット:MS-DOS
 イジェクト可能なドライブか?:はい
 起動可能なドライブか?:いいえ
 ローカルボリュームか?:はい

finder2.jpg

というだけで、それがSDカードかどうかという確実な判定は行なっていません。

「多分こういう条件を満たすドライブはSDカードなんだろー」

という方法によるものです(この条件だと、USBメモリーとかDOSフォーマットした外付けHDDも該当しますね)。Finder上ではSDカードのアイコンがついているので、OS的にはそれがSDカードであることは認識されています。

Finder上で抽出したドライブのpropertiesを取得すると、

{class:disk, name:”JVCCAM_SD”, index:5, displayed name:”JVCCAM_SD”, name extension:”", extension hidden:false, container:computer container of application “Finder”, disk:disk “JVCCAM_SD” of application “Finder”, position:{-1, -1}, desktop position:{-1, -1}, bounds:{-33, -33, 31, 31}, kind:”ボリューム”, label index:0, locked:false, description:missing value, comment:”", size:1.2359368704E+10, physical size:1.2359368704E+10, creation date:date “2014年2月14日金曜日 5:39:40″, modification date:date “2016年9月15日木曜日 12:26:26″, icon:missing value, URL:”file:///Volumes/JVCCAM_SD/”, owner:”maro”, group:”(不明)”, owner privileges:read write, group privileges:read write, everyones privileges:read write, container window:container window of disk “JVCCAM_SD” of application “Finder”, id:-105, capacity:3.3179041792E+10, free space:2.0819673088E+10, ejectable:true, startup:false, format:MSDOS format, journaling enabled:false, local volume:true, ignore privileges:true}

のように結果が帰ってきます。この中にはディスク種別に関する情報は含まれていないため、別途何かのOS内部のサービスを利用して判定できるのでしょう。

AppleScript名:SDカードと思われるドライブを抽出
– Created 2016-10-04 by Takaaki Naganoya
– 2016 Piyomaru Software
–http://piyocast.com/as/archives/4247
tell application “Finder”
  set aDrive to every disk whose format is (MSDOS format) and ejectable is true and startup is false and local volume is true
end tell
–> {disk “JVCCAM_SD” of application “Finder”}

★Click Here to Open This Script 

2016/10/04 Finderの最前面のフォルダを取得して、choose folderの始点フォルダに指定する

Finderの最前面のWindowで表示しているフォルダ(target)を取得して、後続のchoose folderコマンドで表示するデフォルトフォルダに指定するAppleScriptです。

targwin1_resized.png
▲最前面のWindowの内容

targwin2_resized.png
▲最前面のWindowの対象フォルダ(target)を取得してchoose folder

こういうのは、技術的にすごいとかどーのとかいうことは全然なくて、「おもてなし度が向上する」ワザ。過剰にやりすぎると嫌われることもあるので、用法・容量を守って過剰に使いすぎない程度に使ってみるといいのでしょう。

既存のPDFに対して指定フォルダ内のJPEG画像を末尾に追加するAppleScriptを書いたときに、「指定フォルダ」が最前面にあるのになんでこれを毎回指定しないとダメなんだろうか、と思って作ってみたものです。

AppleScript名:Finderの最前面のフォルダを取得して、choose folderの始点フォルダに指定する
– Created 2016-10-04 by Takaaki Naganoya
– 2016 Piyomaru Software
–http://piyocast.com/as/archives/4244

set targAlias to retFrontFinderWindowsTargetIfExits(path to desktop) of me
set aFol to choose folder default location targAlias

on retFrontFinderWindowsTargetIfExits(aDefaultLocation)
  tell application “Finder”
    set wCount to count every window
    
if wCount 1 then
      tell front window
        set aTarg to target as alias
      end tell
      
return aTarg
    else
      return aDefaultLocation
    end if
  end tell
end retFrontFinderWindowsTargetIfExits

★Click Here to Open This Script 

2016/07/16 as item?

US Appleが主催しているML「AppleScript-Users」において、珍しいものを見つけました。MLの内容についてはつねに分類・評価・分析を行い、分類については自動で仕分けを行うロボットScriptが稼働しています。

そこまでやっても、有用な情報は人間の目で見て判別する必要があるわけですが……なにげないやりとりの中に強い違和感が。

list1.png

こう書いて、思い通りにSafariの各Windowのタイトルが文字列で帰ってこないよ的な、初心者レベルな質問がありました。これについては、

list2.png

のように、contents ofで実際の内容を取得してから、nameをとってくるのが「定石」です。

ところが、

list3.png

のように、「as item」といった書き方ができるようで。これはcontents ofと同じ挙動を行う書き方のようです。こんな書き方ができるとは思ってもみませんでした。このケースでは「as text item」も通ります(同じ実行結果に)。

list4.png

実行速度を計測してみたところ、誤差範囲内でとくに速度は変わりません。

2016/06/16 技術書典で販売する本、目下大詰め(のはず)

2016年6月25日に秋葉原・通運会館にておこなわれる技術書オンリーイベント「技術書典」にB-01ブース(地下1F)にて参加いたしますが、そこで販売する書籍の表紙が(だいたい)出来上がりました。

ぜひ、会場にてお買い求めください(値段は検討中)。あらかじめメールで申し込んでいただければ、そのぶんDVDをご用意しておきます。

▼クリックで画像拡大
books1.png

books2.png

あと、これに加えて、AppleScriptObjC(on Xcode)のサンプルを大量に入れて、かつRadikoの録音を行うAppleScriptObjCアプリケーション「Radirec」のプロジェクトソース全部入りのDVDも発売します。

radirec1_resized.png

なお、DVDが売り切れた場合には、QRコードによるダウンロード販売を行います。

追記:

リクエストがあるようなので、会場での販売以外にもオンライン販売を行います。ただし、即売会後のフィードバックを受けたアップデート版(v1.1)をその対象とします。送金手段については、まだ検討中です。

2016/06/13 異なるバージョンのアプリケーションを区別して指定

IllustratorやPhotoshop、InDesignなどでよくある話ですが、これらのアプリケーションは1つの環境に異なるバージョンのアプリケーションが同時に存在するケースが多いものです。

普通にアプリケーション名やBundle IDを指定しただけでは、最新のアプリケーションが起動します。これは、ずーーっと昔からのMac OS/Mac OS X/OS X(もしかして10.12からmacOS?)の伝統的な挙動です。

しかし、古いバージョンのアプリケーションを指定してコントロールしたい場合もあります(そういうケース多し)。こうした場合にいは、アプリケーションのバージョンを区別して呼び出すことが必要になります。

aicss.png

一番簡単な対処方法は、各バージョンのアプリケーションをフルパスで、POSIX pathで指定するというものです。

2016-06-13-15_14_50.gif

AppleScript名:異なるバージョンのアプリケーションを区別して指定
tell application "/Applications/Adobe Illustrator CS3/Adobe Illustrator.app"
  activate
end tell

delay 3

tell application "/Applications/Adobe Illustrator CS5/Adobe Illustrator.app"
  activate
end tell

delay 3

tell application "/Applications/Adobe Illustrator CC 2015/Adobe Illustrator.app"
  activate
end tell

★Click Here to Open This Script 

ただし、上記のリストは私個人の環境の状態を反映させたものであり、こう書けばどこでも同じように動くわけではありません。各環境のアプリケーションのインストール状態に依存します。

そのため、バンドルIDを指定して実行環境にインストールされている指定アプリケーションのバージョンをすべて検出し、それらへのパスを個別に取得してtellするといった方法が(はるかかなた昔から)編み出され、利用されています。

そして、各バージョン用の処理Scriptをバンドル内に入れておいて、実行ターゲット・バージョンのアプリケーション用Scriptを動的にload scriptして読み込み、実行するというのも非常にオーソドックスなやり方です。

1つのScript内に、異なるバージョンの同名のアプリケーションへの操作を混在させることはできないため、対応バージョンごとにファイルを分割しておき、必要に応じて読み込んで実行。実行が終わったらメモリー上から消去することになります。

2016/05/04 Contacts(旧称:Address Book)のプラグインScriptのフォルダが変わった

最近利用していなかったので、気づかなかったのですが・・・Contacts(旧称:Address Book)のプラグインScriptを入れておくフォルダが(OS X 10.9で)変更になったとのこと。

# Release Notesに書いておくべき内容だと思うのですが(ーー;;

旧:~/Library/Address Book Plug-Ins/
新:~/Library/Application Scripts/com.apple.AddressBook/

plugin_resized.png
以前にアドレスブック用に作成したプラグインScriptも、問題なくContacts用に利用できています

2015/09/16 指定の画像をアイコン表示してdisplay dialog

外部の画像ファイルをアイコンに指定してダイアログ表示を行うAppleScriptです。

custom_icon_dialog.png

大昔から指定できることはわかっていたのですが、まったく必要がなくてやったことがなく・・・初めて指定してみました。

大きな画像を指定しても、ダイアログに表示できる大きさに自動でリサイズされます。

Animation Gifを指定するとアイコン表示エリアでアニメーションする、というのがちょっといい小ネタです。PDFも指定できますが、リンクをクリックできたりするかどうかは・・・。

AppleScript名:外部の画像をアイコン表示してdisplay dialog
set a to choose file of type {“public.image”}

display dialog “This is a simple test” with icon a

★Click Here to Open This Script 

Script Bundle内のContents/Resourcesフォルダ内に画像を入れて、アイコン名で指定するやり方もあり、こちらの方が(Xcode上で書くAppleScriptアプリケーションでも)みかけますが、通常のAppleScriptでやったことがなかったので試してみました。

resources.png

このテストのためにわざわざIconComposerでicnsリソースを作成してScriptバンドル内に入れて実験してみました。このScriptをアプリケーション形式(Applet)に保存して(同様に画像やICNSファイルを入れて)実行するとアイコン表示されます。

バンドル内のicnsファイルをアイコンとして表示できましたし、普通の画像も問題なく表示できました(Appletとして保存していれば)。

AppleScript名:バンドル内の画像を指定してdisplay dialog_app
set aPath to path to resource “bImg.icns”
display dialog “This is a simple test–1″ with icon aPath

set aPath to path to resource “aImg.png”
display dialog “This is a simple test–2″ with icon aPath

★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/01/15 セキュリティ上、リスキーな技術

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

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

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

(1)GUI Scripting

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

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

fig1.png

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

fig2.png

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

(2)Custom URL Protocol

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

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

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

fig4.png

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

(3)AppleScript Libraries

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

fig3.png

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

ちなみに、これを・・・

fig2.png

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

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

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

2014/10/23 編集中のScript BundleあるいはApplet全体の行数をかぞえる

Script Editor(10.9まではAppleScriptエディタ。以下、エディタと略)で編集中のアプレットやバンドル形式のAppleScriptの、バンドル内に入れてあるサブScriptすべての行数をカウントするAppleScriptです。

バンドル形式のAppleScript、あるいはアプレットに入れてあるサブScript(共有機能を突っ込んであったり、個別に動作確認する必要があるモジュールなど)すべての行数をカウントしようとすると、わりとやっかいで面倒です。

scripts.png

そこで、専用のScriptを組んでカウントすることにしました。

実行すると、エディタ上でオープンしているAppleScriptの一覧が出るので、

asdiaog.png

任意のScript(バンドルScript、アプレット)を選択すると、バンドル内のすべてのScriptの名称と行数、さらに総合計の行数をTAB区切りテキストに(エディタの「結果」欄に)出力します。

エディタで直接バンドル内のAppleScriptを(AppleScript側から指令して)オープンさせるとエラーになるので、shellのosadecompileコマンドを使ってAppleScriptを(中間言語形式から)テキストに逆変換し、行数をカウントしています。

行数のカウントまでshell側で「wc -l」で行わせた方が速そうでしたが、AppleScript側でテキストを取得できれば、改行のみの行を除外するといった処理もできるので、今後の発展性を考慮してshell側でカウントしないようにしました。

ちなみに、バンドル形式のAppleScript、あるいはアプレットについては、バンドル内に入れたAppleScriptをエディタ上から直接オープンすると、変更したり保存したりする際にエディタが暴走してしまいます(10.9、10.10で確認)。バンドル内のAppleScriptを編集したい場合には、Finder上でバンドル内容を表示させたうえで、Finder側でバンドル内のAppleScriptをダブルクリックして編集するとエディタが暴走せずに編集、保存が可能です。

スクリプト名:編集中のScript BundleあるいはApplet全体の行数をかぞえる
set resList to {}
set resTotal to 0

–編集中のAppleScriptのうち、行数カウント対象を選択する
tell application "Script Editor"
  set dList to name of every document
  
  
set dRes to choose from list dList
  
if dRes = false then return –キャンセル時の対応
  
  
set ddRes to first item of dRes
  
  
tell document ddRes
    set aPath to path
  end tell
  
end tell

–バンドル中のScriptをすべて取得
set realPath to aPath & "/Contents/Resources/Scripts/"
set sRes to do shell script "ls " & (quoted form of realPath)
set sList to paragraphs of sRes

repeat with i in sList
  
  
set j to contents of i
  
set jj to realPath & j
  
  
–osadecompileコマンドで中間言語形式のAppleScriptをテキストに逆変換して行数をかぞえる
  
set tempAS to do shell script "/usr/bin/osadecompile " & quoted form of jj
  
set tempList to paragraphs of tempAS
  
set asCount to length of tempList –wc- lでカウントしてもいいが、本文の内容を取り出せておけたほうが便利
  
  
–出力リストに名称、行数のペアで追加
  
if j = "main.scpt" then
    set the end of resList to {ddRes & " (" & j & ")", asCount}
  else
    set the end of resList to {j, asCount}
  end if
  
  
–合計行数の算出
  
set resTotal to resTotal + asCount
  
end repeat

–総合計を出力リストの末尾に追加
set the end of resList to {"Total:", resTotal}

–2D ListをTAB区切りテキストに変換して結果に出力
set resText to retItemDelimedAndParagraphDelimedText(resList, tab, return) of me

–>
(*
"A1.scpt  264
A2.scpt  360
A3.scpt  358
A4.scpt  298
B1.scpt  298
B2.scpt  243
B3.scpt  294
B4.scpt  258

(中略)

設定ファイルから実行年月日&時のリストを展開.scpt  299
Total:  3570"
*)

–入れ子のリストを、アイテム間のデリミタとパラグラフ間のデリミタを指定してテキスト化
–というか、入れ子のリストをタブ区切りテキストにするのが目的
on retItemDelimedAndParagraphDelimedText(aList, itemDelim, paragraphDelim)
  set aText to ""
  
  
repeat with i in aList
    set aStr to retDelimedText(i, itemDelim) of me
    
set aText to aText & aStr & paragraphDelim
  end repeat
  
  
return aText
end retItemDelimedAndParagraphDelimedText

on retDelimedText(aList, aDelim)
  set aText to ""
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retDelimedText

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

2014/10/06 最前面のQuickTimeムービーの現在のコマをコピーしてPhotoshopでJPEG保存

オープン中のQuickTimeムービーの現在のコマを静止画としてコピーして、PhotoshopでWeb用のJPEG(サイズが小さい)として書き出すAppleScriptです。

QuickTimeムービーの見どころだけをかいつまんでJPEGに書き出していたのですが、画面キャプチャをとったりなんだりで、3ステップぐらい手作業が必要だったので、いっぱつで実行できるようにAppleScriptを書いてみました。

各所にdelayを入れて時間待ちしているところがミソです。入れないとAppleScriptの実行が速すぎて、コピーなどの動作が完了しなかったためです。

また、QuickTime Player(X)の機能がおっそろしく少ないので、仕方なくGUI Scriptingを併用(現在のコマの内容をコピーする)しています。実行には、GUI Scriptingを許可しておく必要があります。

4d31d0bd-9459-478a-8fd5-f941a13f5453.jpg
▲ムービーから1コマだけ抜き出してPhotoshopでWeb用JPEG(サイズが小さい)に書き出した結果

スクリプト名:最前面のQuickTimeムービーの現在のコマをコピーしてPhotoshopでJPEG保存
set dPath to path to desktop
set dPathStr to dPath as string
set fPath to dPathStr & (do shell script “uuidgen”) & “.jpg”

–QuickTime書類の縦横ドットサイズを取得
tell application “QuickTime Player”
  
  
–ムービーを開いていないかチェック
  
set dCount to count every document
  
if dCount < 1 then
    display dialog “ムービーを開いた状態で実行してください” with icon 2 with title “エラー:書き出し対象のムービーが存在しません” buttons {“OK”} default button 1
    
return
  end if
  
  
tell document 1
    set {xWidth, yHeight} to natural dimensions
  end tell
  
end tell

–QuickTime Playerの書類の現在の位置の内容(静止画)をコピー
activate application “QuickTime Player”

delay 0.1

tell application “System Events”
  tell process “QuickTime Player”
    click menu item 5 of menu 1 of menu bar item 4 of menu bar 1 –編集>コピー
  end tell
end tell

delay 0.1

–Photoshopで新規書類を作成してペースト
tell application id “com.adobe.photoshop”
  activate
  
  
delay 0.1
  
  
make new document with properties {width:xWidth, height:yHeight}
  
paste
  
  
delay 0.1
  
  
–Resize & Save
  
tell current document
    
    
set aH to height
    
set aW to width
    
    
set aH1 to aH * 0.25
    
set aW1 to aW * 0.25
    
    
–resize image width aW1 height aH1
    
    
export in file fPath as save for web
    
    
close without saving
    
  end tell
  
  
end tell

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

2014/09/30 vim上でAppleScriptは書けないのか?

Twitter上での「こまったちゃんのつぶやき」(本人に悪意はないが、明らかに内容が間違っている)を拾っていると、新鮮な驚きに遭遇することがあります。

vim(unix上のテキストエディタ。元になったviはSunのビル・ジョイが作った)上でAppleScriptを書けないからどーのという内容でした。

では、実際にvim上でAppleScriptを書いてみましょう。

484d53c0-ad3a-4250-940c-fb2d176a1d6b.jpg

はい。ふつーに書けました。

f4ed6a94-228b-4244-b5fd-18dd6c8744ce.jpg

書いたテキストファイル「test.applescript」の内容をTerminal上でcatしてみて確認し、osascriptコマンドで実行。あらかじめFinder上で選択しておいたファイルの数を音声でかぞえます。

問題なく実行できました(10.9までは、osascript経由のAppleScript実行時には、display dialogとかchoose folderとかのユーザー操作を伴う命令は実行を拒否する仕様になっています。このため、display dialogを使っていません)。

ただ、AppleScript用語辞書の内容をTerminal上から参照できるわけではありませんし、アプリケーションの用語辞書の内容も参照できません。

ase1.jpg

さらに、オートインデントや省略表記を自動展開する機能(end tellやend ifなどを「end」だけ書いておけば構文確認(画面上に「コンパイル」と書いてあるが、一般的なコンパイルではなく実際には中間言語への翻訳。AppleScriptはインタプリタ型言語)時に自動展開してくれる、appと書いておくとapplicationと展開してくれる)も使えません。

構文要素ごとに色分け表示してくれる機能がないと、そのままではとても可読性がよくないです。

AppleScriptエディタ上で利用可能な、エディタ自体と他のツールを連携させたスクリプトアシスタントなど、超便利機能が一切使えないので、生産性はガタ落ちです。

できないわけではないけれど、やると不便。そういうことです。

2014/09/29 record型で結果が返ってくる内容の取り出し方【超基礎】

【ご注意】本エントリは、分かっていない方向けの説明用です。ふつーに分かっている方々にはあらためて参考になるような情報は含まれていません。

Twitter上で「OS X 10.9にしたらAppleScriptの命令の実行結果の返り順が変わった。バグだ」という投稿を見かけました。

よくよく聞いてみると、それはdisplay dialogコマンド(default answer指定時の、文字入力ダイアログ)の結果を(record形式データ)てきとーに取り出して「順番が違う、バグだ」と言っているのでした。

AppleがOSに添付しているAppleScriptをそのまま使ったようで……

apple_sample.png
▲こういう記述は恥ずかしいです、的な記述のオンパレード。読みにくいうえに恥ずかしい。部分的に書いてはダメな記述もある(recordをlistにcastすんな!)。メーカーがOSに添付しているScriptにこれはないだろー(ーー;

そうした(OSについてくる)コードの品質はいまひとつなのと、どーせ英語環境でしかチェックしていないのと、Apple弁はなまりが強い(theとかresultとかやたらと使いまくる)ので、あれを読んで理解しろとかいう方が無茶な感じではあります。

# Adobeのアプリケーションのように、同じオブジェクトの属性値を求めて、返り値内のラベルの順番が変わるとかいうのは、かなり問題ですが、この(↑)程度なら問題はないレベルです。

そこで、実際に解説を。

dsp.png

ダイアログを表示して文字入力を行うのは、だいたいこんな感じです。ダイアログのタイトルとか、ダイアログに対するアイコン表示指定なんかもできますが、それは省略しています。

スクリプト名:dialog0
set a to display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”

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

ここ(↑)に掲載しているScriptは、WebブラウザからAppleScriptエディタ(10.10からはスクリプトエディタに改称)に直接コード内容を転送するURLリンクを含んでいます(そのまま実行する機能はないのでセキュリティホールにはならないはず)。

MacのWebブラウザで「▼新規書類に」をクリックすると、AppleScriptエディタが立ち上がって(セキュリティ設定次第では、確認ダイアログが出る場合も)新規Scriptが作成されてそこに内容が転送されます(iOSやWindowsなど他のOSではURLリンクは無効です)。

ここで返ってくる値は、

スクリプト名:dialog1
set a to display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”
–> {button returned:”OK”, text returned:”あああああ”}

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

のようになります。{button returned:”OK”, text returned:”ああああ”}といった内容です。

この内容が何なのかを調べると……

スクリプト名:dialog2
set a to display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”
class of a
–> record

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

recordと返ってきます。recordはラベルのついた値の組み合せで構成されるデータで、ここでは「button returned」と「text returned」の2つのラベルがついたデータのセット(record)が返ってきていることが分かります。

recordから値を取り出すには、ラベルを指定すればOKです。また、record型のデータ内では、ラベルの順番などはとくに意識しないで処理できます。

で、正しくデータを取り出すにはそれぞれラベルを指定してあげる必要があるので、このような処理になります。

スクリプト名:dialog3
set a to (display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”)
–> {button returned:”OK”, text returned:”あああああ”}
set bText to button returned of a –押されたボタン上のラベルが文字列で入る
–> “OK”
set iText to text returned of a –ダイアログに入力されたテキストが入る
–> “ああああ”

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

2014/09/12 選択中のスクリーンショットから必要なもの以外削除

Shift-Command-3で作られる画面のスクリーンショットの画像ファイルを選択しておき、複数ディスプレイ接続時にできる「本来は必要ではない別のディスプレイ上のスクリーンショット」を削除するために作ったAppleScriptです。

Mac OS XではShift-Command-3で画面表示内容のスクリーンショットを画像に保存できます(常識レベルのお話)。これが、1つのディスプレイだけだと、

スクリーンショット 2014-09-12 13.01.59

といったファイル名で、デスクトップに日時を反映させたファイル名で作成されるわけですが、

bad_disp_arrange.png

といったように、複数台のディスプレイを接続している場合には、

・プライマリディスプレイ
スクリーンショット 2014-09-12 13.01.59

・セカンダリディスプレイ
スクリーンショット 2014-09-12 13.01.59(2)

といったように、プライマリ「以外の」ディスプレイのスクリーンショットにはその番号が末尾に付属します。OS Xでは6台までのディスプレイを認識するため、この数字の最大数は6ということになります。

きょうび、たいていのアプリケーションの表示は1台のディスプレイ内で完結していますので(昔みたいに、複数台のディスプレイにまたがった表示はしないので)、特定のアプリケーションのスクリーンショットをとっておきたい場合には、余計なディスプレイのスクリーンショットは「ゴミとして捨てる」場合がほとんどです。

そこで、デスクトップ上のスクリーンショットのファイルを選択しておいて、

screnshot1.png

本AppleScriptを実行。

screenshot2.png

どのディスプレイ用のスクリーンショットを残すか聞いてきますので、選択すると指定したディスプレイ以外のスクリーンショットはゴミ箱に移動されます。

Finder上で選択したファイルが「画面のスクリーンショットかどうか」の判定まではしていないので、不安であればそうした処理も追加するとよいでしょう。

スクリプト名:選択中のスクリーンショットから必要なもの以外削除
–選択中のファイル一覧をalias listで取得
tell application “Finder”
  activate
  
set aList to selection as alias list
end tell

–接続中のディスプレイをかぞえる
tell application “System Events”
  set dCount to count every desktop
end tell

–選択肢の作成
set dnList to {}
repeat with i from 1 to dCount
  set the end of dnList to (“(” & i as string) & “)” –全角カッコを指定「(」、[)」
end repeat

–残す項目を選ぶ
set aRes to choose from list dnList with prompt “どの画面のスクリーンショットを残しますか?” default items {contents of first item of dnList} with title “不要な画像をゴミ箱へ” without empty selection allowed
if aRes = false then
  display dialog “処理を中止します。” buttons {“OK”} giving up after 10 with title “キャンセルされました” with icon 2
  
return
end if

–選択された項目が何アイテム目かをサーチ(そういう命令があったかも、、、)
set aaRes to contents of first item of aRes

set aResNum to 1
repeat with i in dnList
  set j to contents of i
  
if j = aaRes then
    exit repeat
  end if
  
set aResNum to aResNum + 1
end repeat

–log {”aResNum”, aResNum}

if aResNum = 1 then
  –プライマリディスプレイ上のスクリーンショットを残すことを選択
  
set delList to (rest of dnList)
  
  
repeat with i in aList
    
    
tell application “Finder”
      set j to name of i
    end tell
    
    
–ファイル名に削除対象文字を含んでいるかどうかチェック
    
repeat with ii in delList
      set jj to (contents of ii) as string
      
–log {jj, j}
      
      
if j contains jj then
        tell application “Finder”
          delete i
        end tell
      end if
      
    end repeat
    
  end repeat
  
else
  –セカンダリディスプレイ上のスクリーンショットを残すことを選択
  
repeat with i in aList
    tell application “Finder”
      set j to name of i
    end tell
    
    
if j does not contain aaRes then
      tell application “Finder”
        delete i
      end tell
    end if
  end repeat
end if

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

2014/09/10 InDesign CC 2014がアップデート、AppleScriptのバグ直らず

InDesign CC 2014がアップデート(10.0.0.70ビルド)しましたが、オブジェクトのpropertiesを取得できないバグは直っていません。

indd2014_buggy_yet.png

2014/09/09 各画面でムービーを全画面再生 v2

複数のQuickTimeムービーを、Macに接続した複数の外部モニターで各画面に全画面表示させつつ同時再生させるAppleScriptの試作品(実用性を追求したものではなく、趣味のプログラム)です。

あらかじめQuickTime Playerでムービーを開いておくと、Macに接続している各モニタの座標にムービーのウィンドウを移動させたうえで、各ムービー同時に全画面再生を行います。

r0016652.JPG

Macに複数のディスプレイが接続されている場合に、それらがどういった並びになっているのかは、AppleScriptからは分かりにくい状態です。そのため、すべての画面が横一列に近い状態で並んでいる場合のみを想定します(他の場合にはエラー)。

system_profiler経由で接続されているディスプレイの解像度一覧を取得し、横幅の総和を求めます。

別途、Finder経由でデスクトップの大きさを取得し(Finder経由では各モニタの解像度は分からない)、system_profiler経由で取得した横幅の総和と比較。これらが同じ場合(重複なく横に並んでいる場合)には処理を実施します。

disparrange.png
▲OK 各ディスプレイ上でのフルスクリーン再生を許容する場合のディスプレイの並び例

bad_disp_arrange.png
▲NG 各ディスプレイ上でのフルスクリーン再生を許容しない場合のディスプレイの並び例

各QuickTimeムービーのウィンドウを、各モニタに移動させ、各ムービー冒頭からフルスクリーンで再生を行います。

ところどころ、いまひとつな部分はありますが……想定していた機能はすべて(無理矢理)詰め込みました。本当は、ディスプレイの名称と座標をセットにしたリストを作りたかったのですが……これに、意外と苦戦。「とりあえずできればいい」程度に割り切りを行ったので形になりました。

スクリプト名:各画面でムービーを全画面再生 v2
–各ディスプレイの解像度を取得する
set aRes to retDisplayResolution() of me
–> {{1920, 1200}, {1920, 1200}, {1920, 1080}}
set monNum to length of aRes
–> 3

tell application “QuickTime Player”
  set dCount to count every document
end tell

–Error検出
if monNum < dCount then
  display dialog “ムービーをフル画面再生させるモニターの数が不足しています。”
  
return
end if

–トータルのデスクトップの大きさを取得
tell application “Finder”
  set {dX1, dY1, dX2, dY2} to get bounds of window of desktop
  
–> {-1920, 0, 3840, 1200}
end tell

–デスクトップの横方向のサイズを求める
set dX11 to absNum(dX1) of me
set dX21 to absNum(dX2) of me
set dXtotal to dX11 + dX21

–デスクトップの縦方向のサイズを求める
set dY11 to absNum(dY1) of me
set dY21 to absNum(dY2) of me
set dYtotal to dY11 + dY21

–モニター解像度を縦、横ともに合計する
set eX to 0
set eY to 0
repeat with i in aRes
  copy i to {aX, aY}
  
set eX to eX + aX
  
set eY to eY + aY
end repeat

—モニター配置パターンは、横に並んでいる場合のみを考慮するものとする(よわ、、、)
if eX is not equal to dXtotal then
  display dialog “モニタが想定どおりに並んでいないため、処理を終了します”
  
return
end if

–QuickTime Playerの各ウィンドウの移動
set mCount to 1
set startX to dX1

repeat with i in aRes
  copy i to {iX, iY}
  
  
–ウィンドウ移動
  
set eRes to changeBoundsOfQTX(startX, 32, mCount) of me
  
if eRes = false then
    display dialog “QuickTime Playerのウィンドウ操作でエラー”
    
return
  end if
  
  
set startX to startX + (absNum(iX) of me)
  
set mCount to mCount + 1
  
end repeat

–オープン中の全ムービーの同時、フル画面表示再生
tell application “QuickTime Player”
  activate
  
set current time of every document to 0
  
present every document
end tell

delay 2 –ムービー再生ウィンドウの全画面化UIアニメーションが終わるまで時間待ち

tell application “QuickTime Player”
  play every document
end tell

–指定のQuickTime Playerのウィンドウを指定の座標に移動
on changeBoundsOfQTX(aStartX, aStartY, aNum)
  tell application “QuickTime Player”
    set maxW to count every window
    
if aNum > maxW then return false –Error
    
    
tell window aNum
      set {mX1, mY1, mX2, mY2} to bounds
    end tell
    
    
set mXdiff to (mX2 - mX1)
    
set mYdiff to (mY2 - mY1)
    
    
tell window aNum
      set bounds to {aStartX, aStartY, aStartX + mXdiff, aStartY + mYdiff}
    end tell
  end tell
  
  
return true –OK
  
end changeBoundsOfQTX

–モニタ解像度を取得。複数モニタ接続時にはリストで列挙
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

–絶対値を求める
on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

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

2014/09/09 各画面でムービーを全画面再生

複数のQuickTimeムービーを、Macに接続した複数の外部モニターで各画面に全画面表示させつつ同時再生させるAppleScriptの試作品です。

同じオンライン対戦ゲーム(戦場の絆ポータブル)の内容をプレイヤーごとに視点を変えたムービーを(PSPエミュレータPPSSPP上でリプレイ再生させながらQuickTimeプレイヤーで画面キャプチャしつつ)作り、1つのディスプレイ上で同時再生させて見ていました。

c04dd845-6381-4b59-beb5-92f7157bd02b.jpg

ただ、せっかく外部モニタが3台つながっているので、3つのムービーを各ディスプレイに全画面表示させ同時に再生させようと考え、ちょっとAppleScriptを書いて試してみました。

r0016647.JPG

現状では、このAppleScriptだけでは動作は完結していません。

disparrange.png

ここでは、3つのムービーを各ディスプレイに1個ずつウィンドウを(手で)移動させる操作が必要で……すべてAppleScriptで動作を完結させるためには、接続されている各ディスプレイの解像度と位置関係をAppleScript側から把握する必要があります。

そこはまだ作っていないので、必要があれば調べる……といったところでしょうか。

スクリプト名:各画面でムービーを全画面再生
tell application “QuickTime Player”
  activate
  
set current time of every document to 0
  
present every document
  
play every document
end tell

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

2014/09/03 ファイル名の冒頭部分でフォルダを作成してフォルダ分け

ファイル名が「xxxxx_yyyyyyyy.xxx」などとなっている場合に、ファイル名の冒頭部分の「xxxxx」でフォルダを新規作成し、ファイル名冒頭が同じファイルを作成したフォルダに移動させるAppleScriptです。

大量の画像を含むフリー画像素材集をダウンロードして、そのままだと整理しづらかったのでささっとScriptを書いて整理してみました。

finder_file_sep.png

……ささっと書いたのはよかったのですが、なぜか実行にやたらと時間がかかり…………OSのバグか? などと思ってみたものの、DiskUtility.appを実行してSSDの権限修復などをいったん行ってから実行したら、すぐに処理が終わるようになりました。

もしも、AppleScriptでファイル処理を行ってみて、異様なほど時間がかかる場合には(処理にもよりますが)、ディスクユーティリティーを実行してSSD/HDDのチェックを行ってみてください。

こういうScriptを書いて走らせていると、つくづくHDDの時代は終わったんだなーと感じます。

スクリプト名:ファイル名の冒頭部分でフォルダを作成してフォルダ分け
script spd
  
  
property aList : missing value –ファイルを入れる(alias list)
  
property cList : missing value –フォルダ名を入れる
  
end script

set aList of spd to {}
set cList of spd to {}

set aFol to choose folder with prompt “フォルダを選択してください”
set aFolStr to aFol as string

tell application “Finder”
  tell folder aFolStr
    set aList of spd to every file as alias list
  end tell
  
  
repeat with i in (aList of spd)
    
    
set j to contents of i
    
set aName to name of j
    
set fName to getFolNameBeforeSeparator(aName, “_”) of me
    
    
set tFol to aFolStr & fName & “:”
    
    
if fName is in (cList of spd) then
      –フォルダが存在する場合
      
ignoring application responses
        move j to folder tFol
      end ignoring
      
    else
      –フォルダが存在しない場合
      
set the end of (cList of spd) to fName
      
      
if not (exists of (folder tFol)) then
        set newFol to make new folder with properties {name:fName} at folder aFolStr
        
do shell script “sync”
      end if
      
      
ignoring application responses
        move j to folder tFol
      end ignoring
      
    end if
    
  end repeat
  
  
end tell

–与えられた文字列(たぶんファイル名)のうち、先頭から指定セパレータまでの間をフォルダ名として返す
on getFolNameBeforeSeparator(aName, aSeparator)
  set aLoc to offset of aSeparator in aName
  
if aLoc = 0 then
    return “”
  else
    set aStr to text 1 thru (aLoc - 1) of aName
    
return aStr
  end if
end getFolNameBeforeSeparator

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

2014/08/27 ログイン中のユーザーのPDFWriterのPDF出力フォルダを求める

仮想プリンタを提供し、印刷結果をPDFとして出力する「PDFWriter」がPDFを出力するフォルダのパスを求めるAppleScriptです。

ただし、正常に動作するためにはいくつかの前提条件をクリアする必要があります。

 (1)マシンにPDFWriterがインストールしてあること
 (2)少なくとも一度はPDFWriter経由でPDF出力したことがあること

(1)はともかく、インストール直後の一度もPDFWriter経由でPDF出力していない環境では、PDF出力フォルダが作成されていない可能性があります。

スクリプト名:ログイン中のユーザーのPDFWriterのPDF出力フォルダを求める
set aPath to retPDFWriterOutputFolder()
–> “/var/spool/pdfwriter/me/”

–ログイン中のユーザーのPDFWriterのPDF出力フォルダを求める
on retPDFWriterOutputFolder()
  
  
–ログイン中のユーザーアカウント名を取得する
  
tell application “System Events”
    set aProp to properties of current user
    
set aName to name of aProp
  end tell
  
  
–pdfwriterのPDF出力フォルダのパスを組み立てる
  
set printCuePath to “/var/spool/pdfwriter/” & aName & “/”
  
  
–PDF出力フォルダが存在するかどうか確認
  
try
    do shell script “cd “ & quoted form of printCuePath
  on error
    return false –パスが存在しない場合にはfalseを返す
  end try
  
  
return printCuePath
  
end retPDFWriterOutputFolder

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

2014/08/25 Adobe Photoshopの巨大なバグ「日本語環境下でファイルオープンエラー」への対処

Adobe Creative Suites/Creative Cloudの巨大なバグは多々あれど、その中で最大にしてなかなか直らないものが、

「Photoshopは、日本語環境下でAppleScript経由のファイルのオープンがまともにできない」

というものです。

AppleとAdobeの間で問題のなすりつけ合いを行っていることは想像に難くありませんが、Photoshopでエラーの出るファイルを他のアプリでは問題なくオープンできていることから、明らかにAdobe側の問題です。

スクリプト名:Photoshopでファイルをオープンできないバグを簡単に再現するコード
set a to choose file

tell application id “com.adobe.photoshop”
  open a
end tell
–> error “Adobe Photoshop CC 2014 でエラーが起きました:ファイル 不特定のオブジェクト が見つかりませんでした。” number -43

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

本問題に対しては、浅いフォルダにファイルを配置(外部HDD上など)するとか、JavaScriptを経由してファイルオープンするとかいったさまざまな対策が行われてきました。

どの対処方法がよいのか、というのはいろいろ評価が分かれるところですが……大げさになりすぎないことから、「POSIX pathに変換してオープン」がけっこういいと思います。

ただしここで、一般的なアプリケーションでは「念のために」ファイルパス中にスペースキャラクターなどが入ることに備え、quoted form of でクォート処理を行うのが一般的ですが、ここでquoted form ofは使ってはいけません。エラーになってしまいます。

スクリプト名:Photoshopでファイルをオープンするために
set a to choose file

set aPosix to POSIX path of a

tell application id “com.adobe.photoshop”
  open file aPosix
end tell

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

2014/08/22 iWork各アプリがアップデート、AS用語辞書に変更なし

Mac App Store経由でiWorkアプリの、

 Keynote 6.2.2
 Pages 5.2.2アップデート
 Numbers 3.2.2アップデート

が配布されていますが、AppleScript用語辞書に変更はありません。

2014/04/09に掲載されたAppleの「iWorkのアップデートにより旧バージョンにあった機能を復活させる」というリリースをいま確認したのですが、「半年以内に」といった表現は見られません(当初はあったのかもしれない)。

この調子だと、OS X 10.10のリリースと同時かやや遅れるぐらいの時期にASの機能が復旧する、と見るべきでしょうか。

あらためて各iWorkアプリケーションの用語辞書を見ると、「風呂敷を広げすぎて本当に完成させる気なのか心配」な部分もあるため……少し気長に待つ必要があるのかも?

2014/07/29 変数に入っているデータの「型」を求める

変数の中に入っているデータの「型」を求めるAppleScriptです。

  class of 変数

で求められます。

スクリプト名:変数に入っているデータの「型」を求める
set a to 1
class of a
–> integer

set b to 1.0
class of b
–> real

set c to "1"
class of c
–> text

set d to selection of application "Finder"
class of d
–> list

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

2014/07/29 dateオブジェクトの加減算

dateオブジェクトの加減算の記述例です。

たとえば、現在時刻(current date)の1時間後を求めるのに……

スクリプト名:現在の1時間後を求める(ダメな例)
set a to current date

set tmpH to hours of a
set tmpH to tmpH + 1
set hours of a to tmpH

a
–> date "2014年7月29日火曜日 10:07:46"

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

と書いている例に見当たりましたが……この調子で3時間20分3秒後を計算しようとすると……

スクリプト名:現在の3時間20分3秒後を求める(ダメな例)
set a to current date

set tmpH to hours of a
set tmpH to tmpH + 3
set hours of a to tmpH

set tmpM to minutes of a
set tmpM to tmpM + 20
set minutes of a to tmpM

set a to a + 3

a
–> date "2014年7月29日火曜日 22:54:10"

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

のようになるので、たいへんです。

dateオブジェクトに対しての加減算は、秒単位で数値を足したり引いたりすることで計算できます。

そのさいに、分(minutes=60)、時(hours=3600)、日(days=86400)、週(weeks=604800)などの定数があらかじめ定義されているため、これらを用いることになります。

スクリプト名:現在の1時間後を求める
set a to current date

set a to a + (1 * hours)

–> date "2014年7月29日火曜日 10:07:46"

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

スクリプト名:現在の1時間前を求める
set a to current date

set a to a - (1 * hours)

–> date "2014年7月29日火曜日 10:07:46"

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

スクリプト名:現在の1日後を求める
set a to current date

set a to a + (1 * days)
–> date "2014年7月30日水曜日 11:18:23"

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

スクリプト名:現在の1日前を求める
set a to current date

set a to a - (1 * days)
–> date "2014年7月28日月曜日 19:28:52"

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

スクリプト名:現在の3時間20分3秒後を求める
set a to current date

set a to a + (3 * hours) + (20 * minutes) + (3)
–> date "2014年7月29日火曜日 12:29:41"

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

スクリプト名:現在の3時間20分3秒前を求める
set a to current date

set a to a - ((3 * hours) + (20 * minutes) + (3))
–> date "2014年7月29日火曜日 12:29:41"

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

2014/07/23 Script Editorで最前面のアプリの用語辞書を表示する v3

AppleScriptエディタ/Script Editorで、最前面のアプリの用語辞書を表示するAppleScriptです。

バージョンアップして、最前面のアプリがAppleScript等のOSA(Open Scripting Architecture)言語によるScriptingに対応しているかどうかを判定したのちに、辞書のオープンを行います。

Script Menuに入れて、メニューから呼び出すことを前提としています。

前バージョンでは、とくに対象アプリケーションのScripting対応については調べていなかったため、非対応アプリを指定すると、Script Editorで対象アプリをバイナリモードでオープンしてしまうなどの問題があり、それに対処したものです。

本AppleScriptは、各アプリケーションのバンドル中にあるInfo.plistを走査してOSA対応の判定を行っていますが、これは「NSAppleScriptEnabled」のエントリがInfo.plist上に存在した場合、慣習的に「true」しか指定されていない(そもそも非対応である場合にはNSAppleScriptEnabledのエントリそのものを作らない)ことから、「NSAppleScriptEnabled」が入っていれば対応……という「手抜き判定」をやっています。

真剣に作るのであれば、NSAppleScriptEnabledのエントリを取得して属性値がどうなっているか判定すべきです。

スクリプト名:Script Editorで最前面のアプリの用語辞書を表示する v3
–最前面のプロセスのファイルシステム上の位置を取得してaliasに変換する
tell application “System Events”
  set frontProc to every process whose frontmost is true and visible is true
  
set aProc to contents of first item of frontProc
  
set aFile to file of aProc
  
set aBundleID to bundle identifier of aProc
end tell

set aFile to aFile as alias

–バンドルパッケージ中のInfo.plistファイルを走査してAppleScript対応かどうかを調べる
set aFilePosix to quoted form of POSIX path of aFile
set infoPath to aFilePosix & “Contents/Info.plist”
set aRec to extractInfoPlistAndFindString(infoPath, “NSAppleScriptEnabled”)

–スクリプト用語辞書をScript Editorでオープンする手順
if aRec = true then
  –OS X 10.10でScript Editor側からアプリをオープンしてもダメだったので、Finder側からScript Editorで指定アプリをオープンすることに
  
tell application “Finder”
    set apFile to (application file id “com.apple.scripteditor2″) as alias
    
open aFile using application file apFile
  end tell
else
  tell application id aBundleID
    display dialog “本アプリケーションは、各種OSA言語によるコントロールに対応していません。” buttons {“OK”} default button 1 with icon 2 with title “Scripting非対応”
  end tell
end if

–指定のInfoPlist(たぶん)をextractして指定文字列を含んでいるかチェック
on extractInfoPlistAndFindString(aPosix, findStr)
  
  
try
    set a to (do shell script “/usr/bin/plutil -p “ & aPosix)
  on error
    return 0 –エラー(指定のパスにファイルが存在しない)
  end try
  
  
set b to (a contains findStr)
  
return b
  
end extractInfoPlistAndFindString

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

2014/07/22 AS対応、非対応のアプリ数をカウントする v2

Macのローカルに存在するアプリケーションをすべてリストアップし、AppleScript対応のもの、AppleScript非対応のもの、Info.plistが所定の位置にないものをカウントして返すAppleScriptです。

Finder経由でentire contentsを取得するものを試していたのですが……結果が返ってこない。

以前はもうちょっとentire contentsは「使える」気がしたのですが、実際に試した10.9上ではおかしな動作になっていました(ディスクのチェックが必要かも)。

そこで、mdfind経由でアプリケーションファイルを/Applications以下から取得するようにしてみたところ……数秒で結果が返ってきました。結局、mdfind(spotlight)系の(いつもの)ルーチンを使用することに。

「Info.plistが見つからない」とレポートされたアプリケーションの一覧を調べてみると、以前にWeb CGIとして作成した拡張子「.acgi」のAppleScriptのアプリとか、むか〜しのバンドル形式ではないフォーマットの(10.7以降では実行することができない)アプリケーションとか、あろうことかClassic形式のアプリケーションなどでした。

結局これを、アプリケーションのstringsファイルのローカライズ情報の取り出し(&データベースへの格納)のために使いたかった、という話で……けっこういろいろ寄り道してしまいました。

なお、プログラムリストの途中にシングルクォートが入っていたのでうまくWordPressに投稿できず(全角クォートとかに置き換える必要アリ?)、末尾の既出のサブルーチンはリストからは割愛してあります。

AppleScriptのHTML書き出し用AppleScriptを修正し、バックスラッシュとシングルクォートを全角文字に置き換えて出力するようにしました(記事が全部バックスラッシュになる怪奇現象に遭遇していましたが、これで解決)。

いつもどおり、プログラムリスト末尾のリンクをクリックすると、プログラム全体がAppleScriptエディタ/スクリプトエディタに転送されるようになっています。

スクリプト名:AS対応、非対応のアプリ数をカウントする v2
script spd
  property origList : {} –/Applications フォルダ以下のアプリケーションファイル一覧
  
  
property asEnable : {} –AS対応のアプリ一覧
  
property asDisable : {} –AS非対応のアプリ一覧
  
property infoNotPresent : {} –Info.plistが所定の場所に指定のファイル名で存在しないもの一覧
end script

set origList of spd to {}

set asEnable of spd to {}
set asDisable of spd to {}
set infoNotPresent of spd to {}

set apPath1 to (path to applications folder) as string

–mdfindで指定フォルダ以下にあるすべてのアプリケーションファイルを取得
set (origList of spd) to getFileListWithSpotLight(“kMDItemKind”, “アプリケーション”, apPath1) of me

repeat with i in (origList of spd)
  
  
set aFilePosix to quoted form of POSIX path of i
  
set infoPath to aFilePosix & “Contents/Info.plist”
  
  
set aRec to extractInfoPlistAndFindString(infoPath, “NSAppleScriptEnabled”)
  
  
if aRec = true then
    set the end of (asEnable of spd) to i
  else if aRec = false then
    set the end of (asDisable of spd) to i
  else
    set the end of (infoNotPresent of spd) to i
  end if
end repeat

return {length of (asEnable of spd), length of (asDisable of spd), length of (infoNotPresent of spd)}
–> {294, 580, 36}

–指定のInfoPlist(たぶん)をextractして指定文字列を含んでいるかチェック
on extractInfoPlistAndFindString(aPosix, findStr)
  
  
try
    set a to (do shell script “/usr/bin/plutil -p “ & aPosix)
  on error
    return 0 –エラー(指定のパスにファイルが存在しない)
  end try
  
  
set b to (a contains findStr)
  
return b
  
end extractInfoPlistAndFindString

–指定階層下で、指定メタデータが指定パラメータであるファイルを取得(alias list)
on getFileListWithSpotLight(aMetaDataItem, aParam, startDir)
  
  
set sDirText to quoted form of POSIX path of startDir
  
set shellText to “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

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