2018年9月26日に池袋で行うイベント「Think AppleScript」の参加募集を開始いたしました。質問だろうが、自慢だろうがなんでもOKですので、この際にぜひお集まりください。
→ 申し込みページ
2018年9月26日に池袋で行うイベント「Think AppleScript」の参加募集を開始いたしました。質問だろうが、自慢だろうがなんでもOKですので、この際にぜひお集まりください。
→ 申し込みページ
サードパーティの有償Framework「PSPDFkit」をAppleScriptからコントロールする実験のAppleScriptです。
PSPDFkitについては以前から知っていました。サンプルもAppleよりきちんとしているので、いいんじゃないかと思っていました。
PSPDFの重要性を痛感したのは、AppleがmacOS 10.13の開発で大失敗して、α版以下のバグだらけのOSをリリースしたからです。
もはや、「公開デバッグを1年かけて行うので、最新版は使わないでね」という状態です。はい。10.13を積極的に使う意味など何もありません。
とくに、PDFKitまわりは問題だらけで、自分がMac App Storeで売っているソフトウェアも、macOS 10.13では起動してもまともに動きません(Betaで問題なく動くことを確認していたんですがー)。Developper Supportに質問を投げても返事すら来ません(本当)。
そんなわけで、PDF処理でAppleがOS内に作った機能が正しく維持される保証がなく、むしろ悪くなりそうな気配すらある昨今、PSPDFを使うのも悪くないと思えてきました。
問題は、
・ライセンス契約の問題 たんに「たまに動かす程度のScript」で使うのに合ったライセンス形態があるのかないのか
といったあたりでしょう。ライセンス価格表が掲載されているわけでもないので、いまひとつ不明です(ありがちー)。
→ PSPDFkitのsalesと話をしてみたら、ユーザー数と用途、OSプラットフォームなどに応じて、年ごとのビルドライセンス+実行ライセンスで契約するようです。ちなみに、試用ライセンスが切れるとスクリプトエディタ上で実行はできてしまうものの、実行直後にスクリプトエディタが終了させられます
何はともあれ、実際にPSPDFkit for macOSの試用版をダウンロードし、試用ライセンスキーを取得して(今度は真剣に)いろいろ試してみました。
ダウンロードしてきた試用版のPSPDFkit.frameworkを~/Library/Frameworksにインストールして、取得したPSPDFkitの試用版ライセンスを「current application’s PSPDFKit’s setLicenseKey:」のパラメータに書き、Control-Command-Rで「最前面で実行」(実際にはメインスレッド実行だが)を行うと動作します。
いろいろ検討してPSPDFkitは使いませんでした。macOS用だとPDFViewに該当する部品が提供されていないため、PDFView自体の代替にならないためです(そこにScripting Bridgeのバグが集中しているので)。
AppleScript名:PSPDFkitのじっけん(Script Editor) |
— Created 2018-08-29 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "PSPDFKit" –https://pspdfkit.com –This Script have to run in foreground property PSPDFKit : a reference to current application’s PSPDFKit property PSPDFDocument : a reference to current application’s PSPDFDocument current application’s PSPDFKit’s setLicenseKey:"XxXX_XXXXXxXxxXXXXxxXxxxXXXxxXXXXXxXXXxXXXX" set aPDFfile to POSIX path of (choose file) set aURL to current application’s |NSURL|’s fileURLWithPath:aPDFfile set aDoc to current application’s PSPDFDocument’s alloc()’s initWithURL:aURL set pCount to (aDoc’s pageCount()) as integer log pCount set aVf to (aDoc’s isValid()) as boolean log aVf set fList to aDoc’s features() set fName to (aDoc’s fileName()) as string set f0Name to aDoc’s fileNameForPageAtIndex:10 set f1Name to (aDoc’s title()) as string |
この「最前面で実行」(メインスレッドで実行)する機能がScript Debuggerには存在していないので、一部Scriptを書き換えて、Script Debugger上でも動くようにしてみました。今回はたまたま動きましたが、「スクリプトエディタ上で動作するのにScript Debugger上では動作しない」ことも往々にしてありうるので、ご注意を。
AppleScript名:PSPDFkitのじっけん(Script Debugger) |
— Created 2018-08-29 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "PSPDFKit" –https://pspdfkit.com –This Script have to run in foreground property PSPDFKit : a reference to current application’s PSPDFKit property PSPDFDocument : a reference to current application’s PSPDFDocument my performSelectorOnMainThread:"setLicence:" withObject:(missing value) waitUntilDone:true set aPDFfile to POSIX path of (choose file) set aURL to current application’s |NSURL|’s fileURLWithPath:aPDFfile set aDoc to current application’s PSPDFDocument’s alloc()’s initWithURL:aURL set pCount to (aDoc’s pageCount()) as integer log pCount set aVf to (aDoc’s isValid()) as boolean log aVf set fList to aDoc’s features() set fName to (aDoc’s fileName()) as string set f0Name to aDoc’s fileNameForPageAtIndex:10 set f1Name to (aDoc’s title()) as string on setLicence:(anObj) current application’s PSPDFKit’s setLicenseKey:"XxXX_XXXXXxXxxXXXXxxXxxxXXXxxXXXXXxXXXxXXXX" end setLicence: |
画像ファイルを読み込んで、クリップボードに設定するAppleScriptです。
標準のAppleScriptの命令だけで割と簡潔に書けることに驚きます。
AppleScript名:画像ファイルを読み込んでクリップボードに設定する |
set aFile to choose file of type {"public.image"} set anImage to read aFile as TIFF picture set the clipboard to anImage |
SMCWrapperをCocoa Framework化してAppleScriptから呼びやすいように書き換えた「SMCKit.framework」を呼び出して、CPUの温度や放熱ファンの回転数を取得するAppleScriptです。
AppleScriptからCocoaを呼び出して高速処理を行っていたら、CPUの温度が上がりすぎて焦ったことがありました。これは、対策をしておいたほうがよいでしょう。
Macの放熱ファン制御&温度管理ソフトウェアで、最近評判がいいのはTunabelly Softwareの「TG Pro」。
ただし、これはAppleScriptから制御できるタイプのソフトウェアではなかったので、別の手段を探してみました(MacBook Proの放熱強化のためには利用しています)。
SMC(System Management Controller)からCPUの温度を取得するオープンソースのプログラム「SMCWrapper」を見つけたのですが、すでにメンテナンスされておらず、しかもAppleScriptから呼び出しやすい構造になっていなかったため、Cocoa Framework化しただけで手元に放置してありました。
— Created 2017-07-28 by Takaaki Naganoya
— 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "SMCKit"
set numberOfFuns to current application’s NSString’s new()
current application’s SMCWrapper’s readKey:"FNum" asString:(numberOfFuns)
★Click Here to Open This Script
↑こんな風に呼んでは、動かないことを確認していました(↑は本当に動きません)。
CPUの温度は取得できたほうがよさそうだったので、SMCWrapperにAppleScriptから呼び出しやすいようにメソッドを追加して呼び出してみました。
–> Download SMCKit.framework(To ~/Library/Frameworks/)
readKeyAsString: というメソッドだけ新設して使っていますが、けっこう便利です。
AppleScript名:SMCkitのじっけん v2.scptd |
— Created 2018-08-21 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.5" use scripting additions use framework "Foundation" use framework "SMCKit" –https://github.com/FergusInLondon/SMCWrapper –I turned the SMCWrapper into Cocoa Framework and change it to call from AppleScript set smc to current application’s SMCWrapper’s sharedWrapper() –FNum => Number of Fans set a0Res to (smc’s readKeyAsString:"FNum") as list of string or string –as anything –> 2 — TC0P => CPU Temperature set a1Res to smc’s readKeyAsString:"TC0P" –> 57.125 — F0Ac => Fan0 Actual RPM set a2Res to smc’s readKeyAsString:"F0Ac" –> 5891.00 — F0Mn => Min RPM set a3Res to smc’s readKeyAsString:"F0Mn" –> 5888.00 — F0Mx => Max RPM set a4Res to smc’s readKeyAsString:"F0Mx" –> 5940.00 if a0Res ≥ 2 then — F1Ac => Fan1 Actual RPM set a5Res to smc’s readKeyAsString:"F1Ac" –> 5439.00 — F1Mn => Min RPM set a6Res to smc’s readKeyAsString:"F1Mn" –> 5440.00 — F1Mx => Max RPM set a7Res to smc’s readKeyAsString:"F1Mx" –> 5940.00 end if |
Terminal.appでコマンドを実行するAppleScriptです。
AppleScriptからshell commandを実行するならdo shell scriptコマンドを経由することが多いので、Terminal.appをコントロールしてshell commandを実行しているのは「よくわかっていない人」だけです。これをやるとMac App Storeで一発でリジェクトされる処理内容でもあります。
ただ、そうはいってもごくまれにTerminal.app上でshell commandを実行する必要に迫られて(10年に1度ぐらいの割合で)、仕方なくTerminal.appをコントロールすることになりますが、Windowの有無を確認してその場合に応じて書き方を若干変える必要があるのが面倒。なので、このようにサブルーチンにして使い回すのが一般的なやりかたです。
■本ルーチン使用例
・2つのPDFのテキストの指定ページの差分をVimdiffで表示する v2
この例のほかはssh経由でhostにログインしてコマンド実行したときぐらいで、この2例が「本当にTerminal.appを操作しないと処理できない例」で、ほかは99.9%do shell scriptコマンドで済みました。
ごくたまに、Terminalからパラメータを入力することだけを前提に作られている、出来のよろしくないCLIツールも存在します。
普通のCLIツールのように「フルパスを指定しても問題ない」のではなく、「フルパスでパス指定すると不具合を引き起こす」ことには腰を抜かしました。そういうものもあるので、実際にdo shell scriptコマンド経由で自分の望む機能を呼び出せるかは、試してみないと(試してみるまで)わかりません。機械学習系(画像の物体識別)でそういうものに遭遇しました。落とし穴としかいいようがありません。
AppleScript名:Terminal.appでコマンドを実行する |
use AppleScript version "2.4" use scripting additions use framework "Foundation" set aCom to "ps" doComInTerminalWindow(aCom) of me on doComInTerminalWindow(aCMD as string) using terms from application "Terminal" tell application id "com.apple.Terminal" activate set wCount to count (every window whose visible is true) if wCount = 0 then –ウィンドウが1枚も表示されていない場合 do script aCMD else –すでにウィンドウが表示されている場合 do script aCMD in front window end if end tell end using terms from end doComInTerminalWindow |
AppleScriptの穴Blogアーカイブ1〜3が、よりパワーアップ。TOC(しおり)を付加していっそう読みやすくなりました(読みにくくなくなりました、の間違い?)。
Blogアーカイブ本をBOOTHからダウンロード購入された方は、再度ダウンロードを行ってみてください(再購入の必要はありません)。
また、今回のTOCアップデートは目次を持たないアーカイブ本への目次追加の措置であり、TOCはないものの目次がついている他の既刊本はその対象ではありません。
昨日のうちに再ダウンロードしてくださった方、ごめんなさい。その時点ではまだアップデートしていなかったんです、、、
Skimで現在表示中のPDFの現在のページ番号を、Numbers書類で選択中のセルに入れて、Numbersの選択中のセルを1つ下に移動させるAppleScriptです。
作業をちょっとだけ楽にするために作成したAppleScriptです。本来は、すべてのワークフローを自動化するのが筋ですが、限られた時間で限られた範囲の作業のみを自動化するだけでも十分な効果が得られる場合があります。
複数のアプリケーションをまたいでデータのコピー&ペーストを行うようなケースだと、ちょっとした複合作業を自動化することで作業が圧倒的に楽になります。
PDFビューワー「Skim」上でPDFを表示、Numbers上の表にページ数のデータを入れたい。
本AppleScriptを実行すると、
Numbersの選択セルにデータを突っ込んで、選択セルを1つ下に移動させます。
こうして、PDFにTOCをつけてみました(Blogアーカイブ書籍のマイナーバージョンアップ版のため。再ダウンロードでアップデート可能になります)。元データがあるんだから、TOCごと自動で作ってもいいようなものですが、久しぶりだったので(TOC用データを)手で作ってみました。
ここで、意地でもGUI Scriptingを使わないで処理すると、バックグラウンドでも安全に処理できるので大きなメリットがあります。GUI Scriptingだと最前面(フォアグラウンド)でしか操作が保証されていないので、バックグラウンドで操作できたとしてもそれは「ラッキー」でしかありません。
AppleScript名:Skimで表示中のページ数を現在のNumbersのセルに入れて、選択セルを下にひとつ移動 |
— Created 2018-08-18 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" tell application "Skim" tell front document set curPage to index of current page end tell end tell set curPageStr to curPage as string tell application "Numbers" tell front document tell active sheet tell table 1 set mySelectedRanges to properties of selection range set value of cells of selection range to {curPageStr} set selName to name of selection range –選択範囲のrange情報を取得 set {s1, s2} to parseByDelim(selName, ":") of me set s1Row to (address of row of range s1) as integer set colStr to (name of column of range s1) as string set rowStr to (s1Row + 1) as string set adrStr to colStr & rowStr set rangeStr to adrStr & ":" & adrStr try set selection range to range rangeStr on error –Range Over Error from table 1 display dialog "表の範囲をオーバーします" return end try end tell end tell end tell end tell –テキストを指定デリミタでリスト化 on parseByDelim(aData, aDelim) set aText to current application’s NSString’s stringWithString:aData set aList to aText’s componentsSeparatedByString:aDelim return aList as list end parseByDelim |
Keynoteでオープン中の最前面の書類で表示中のスライド(ページ)上の一番左上のshapeオブジェクトの縦横のサイズに他のshapeオブジェクトのサイズを合わせるAppleScriptです。
座標(position)の配列(2D Array)のソートにはBridgePlus Script Libraryを用いています。
▲初期状態
▲本AppleScriptで一番左上のshapeオブジェクトに他のshapeオブジェクトのサイズを合わせた
▲コマンドで適当に上ぞろえや左ぞろえを実行して整列(ここは手作業)
昔、Finder上のアイコンの整列AppleScriptを作ったことがあったので、その処理を使い回せば「いい感じの整列」はできそうです。
AppleScript名:Keynoteで一番左上のshapeオブジェクトに他のshapeのサイズを合わせる |
— Created 2018-08-17 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use bPlus : script "BridgePlus" –https://www.macosxautomation.com/applescript/apps/BridgePlus.html tell application "Keynote" tell front document tell (current slide) — すべてのshapeオブジェクトの座標{x,y}を返す set pList to position of every shape — shapeオブジェクトの全座標を昇順ソートして最もX座標値が小さいものを返す set mostLeftPos to first item of sort2DList(pList) of me — 一番X,Y座標値が小さい(=左上にある)オブジェクトを特定 set mostLeftObj to first item of (every shape whose position is equal to mostLeftPos) set mostLeftProp to properties of mostLeftObj –> {opacity:100, parent:slide 13 of document id "95E47D6C-C444-41BD-9E7E-61229486F370" of application "Keynote", class:shape, reflection showing:false, background fill type:color fill, position:{45, 229}, object text:"", width:144, rotation:0, reflection value:0, height:25, locked:false} set mostLeftHeight to height of mostLeftProp set mostLeftWidth to width of mostLeftProp — 「一番左」以外のshapeオブジェクトへの参照を取得して一気にオブジェクトのwidthをそろえる set otherShape to a reference to (every shape whose position is not equal to mostLeftPos) set width of otherShape to mostLeftWidth set height of otherShape to mostLeftHeight end tell end tell end tell on sort2DList(aList) load framework set sortIndexes to {1, 0} –Key Item id: begin from 0 set sortOrders to {true, true} –ascending = true set sortTypes to {"compare:", "compare:"} set resList to (current application’s SMSForder’s subarraysIn:(aList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list return resList end sort2DList |
指定のWebページ(URL)の最終更新日時(Last Modified Date)を取得するAppleScriptです。
AppleScriptそのものにWebの最終更新日時を取得する関数や機能はありません。はい、おしまい。
……というのでは、AppleScriptの本質がぜんぜん分かっていないね、ということになります。AppleScriptは「それができるアプリケーション」(など)に依頼を出すのが処理スタイルだからです。
まずは、Safariにコマンドを投げて実行するスタイル。
AppleScript名:Safariのdo javascriptコマンドで最終更新日時を取得 |
— Created 2018-08-17 by Takaaki Naganoya — 2018 Piyomaru Software tell application "Safari" tell front document set dRes to (do JavaScript "document.lastModified;") end tell end tell |
だいたいは、これで手を打つでしょう。ただし、最近のmacOSではセキュリティ強化のためにSafariのdo javascriptコマンドがデフォルトでは禁止されているので、Safariで「開発」メニューを表示させたあとに、「開発」メニューの「AppleEventからのJavaScriptを許可」「スマート検索フィールドからのJavaScriptを許可」を実行しておく必要があります(→ 書籍「AppleScript 10大最新技術」P-84)。
Mac AppStore上で配布/販売するアプリケーションの中で処理することを考えると、SafariをコントロールすることをInfo.plist内で宣言しておけばとくに問題はありません。
do javascriptコマンドの実行で一般的にはファイナルアンサーなのですが、なぜでしょう。リアルタイム日付が返ってくるパターンが多いです。
次は、shellのcurlコマンドを呼び出すスタイルです。指定URLのレスポンスヘッダーを出力させられるので、これを検索して出力します。ただ、YouTubeをはじめとするWebサイトでこの最終更新日を返してこないので、これでもダメな時はダメです。
AppleScript名:curlコマンドで最終更新日時を取得 |
— Created 2018-08-17 by Takaaki Naganoya — 2018 Piyomaru Software tell application "Safari" tell front document set aURL to URL end tell end tell try set uRes to (do shell script "curl -D – -s -o /dev/null " & aURL & " | grep Date:") on error return false end try |
これも現在日時を返してくるパターンが多いですね。また、噂レベルではあるものの「do shell scriptコマンドは極力使わないほうがいいよ」というお達しがScripter界隈で流れているので、将来的に何かがあるのかもしれません(昔の、ごくごく初期のMac OS XはBSDレイヤーというかBSDコマンド類が、OSインストール時にオプション扱いだったので、そういう未来はあるかもしれない)。
Mac AppStore上で配布/販売するアプリケーションの中で処理するのも、とくに問題はないのですが、今度はネットワーク接続することをあらかじめ宣言しておくのと、httpによる通信を行うことを宣言しておかないとネットワーク接続ができません。
最後の手段。Cocoaを呼び出して自前でWebのレスポンスヘッダーを取得するスタイル。
AppleScript名:Cocoaの機能で最終更新日時を取得 |
— Created 2018-08-17 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" property NSString : a reference to current application’s NSString property NSLocale : a reference to current application’s NSLocale property NSURLRequest : a reference to current application’s NSURLRequest property NSDateFormatter : a reference to current application’s NSDateFormatter property NSURLConnection : a reference to current application’s NSURLConnection property NSURLRequestUseProtocolCachePolicy : a reference to current application’s NSURLRequestUseProtocolCachePolicy tell application "Safari" tell front document set aURL to URL end tell end tell set aURL to (current application’s |NSURL|’s URLWithString:aURL) set {exRes, headerRes, aData} to checkURLResourceExistence(aURL, 3) of me set aDate to headerRes’s |date| as string set lastUpdateDate to dateFromStringWithDateFormat(aDate, "EEE, dd MMM yyyy HH:mm:ss zzz") of me return lastUpdateDate — 指定URLにファイル(画像など)が存在するかチェック –> {存在確認結果(boolean), レスポンスヘッダー(NSDictionary), データ(NSData)} on checkURLResourceExistence(aURL, timeOutSec as real) set aRequest to (NSURLRequest’s requestWithURL:aURL cachePolicy:(NSURLRequestUseProtocolCachePolicy) timeoutInterval:timeOutSec) set aRes to (NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)) set dRes to (first item of (aRes as list)) set bRes to (second item of (aRes as list)) if bRes is not equal to missing value then set hRes to (bRes’s allHeaderFields()) set aResCode to (bRes’s statusCode()) as integer else set hRes to {} set aResCode to -1 –error end if return {(aResCode = 200), hRes, dRes} end checkURLResourceExistence –指定形式の日付テキストをAppleScriptのdateオブジェクトに変換 on dateFromStringWithDateFormat(dateString, dateFormat) set dStr to NSString’s stringWithString:dateString set dateFormatStr to NSString’s stringWithString:dateFormat set aDateFormatter to NSDateFormatter’s alloc()’s init() aDateFormatter’s setDateFormat:dateFormatStr aDateFormatter’s setLocale:(NSLocale’s alloc()’s initWithLocaleIdentifier:"en_US_POSIX") set aDestDate to (aDateFormatter’s dateFromString:dStr) return aDestDate as date end dateFromStringWithDateFormat |
結果は3つとも変わりませんでした。Cocoa呼び出しするものも、作り置きしておいたサブルーチンを使いまわしただけなので、作るのに3分もかかっていません。
curlを呼び出すスタイル同様、Mac AppStore上で配布/販売するアプリケーションの中で処理するのもとくに問題はないのですが、httpによる通信を行うことを宣言しておかないとネットワーク接続ができません。
Safariでdo javascriptコマンドを実行するものは、最初にdo javascriptコマンドを実行する設定が必要。curlコマンドはまあそんなもんだろうかと。Cocoaの機能を呼び出す方法は、ここまでやってダメならあきらめがつくというところでしょうか。
安全策の上に安全策を講じた、心配性のファイル名称変更AppleScriptです。
基本的にはファイルのリネームプログラムであり、Finder上で選択中のファイルについてリネームを行います。
ただし、ファイルのリネーム時に同じファイル名のファイルがすでに存在していて、普段であれば衝突回避のために子番号を振るなどの対処を行うところが、そういう対応が許されていないようなケースに直面しました。
同じフォルダ内にある3つの画像ファイルを、名称が衝突するような他の名称に変更するようなケースです。
1.jpg --> 2.jpg(すでに2.jpgが存在) 2.jpg --> 3.jpg(すでに3.jpgが存在) 3.jpg --> 1.jpg
少々、頭を抱えてしまいました。
そこで、テンポラリフォルダをファイル1つごとに作成し、テンポラリフォルダにファイルを移動。
1.jpg --> /temp/uuid0001/1.jpg
そのタコツボ的なテンポラリフォルダの中でリネームを実施。
/temp/uuid0001/1.jpg --> /temp/uuid0001/2.jpg
リネームしたファイルを元のフォルダに戻すというリネームルーチンを作成した次第です。もちろん、作成したテンポラリフォルダは削除しておきます。
/temp/uuid0001/2.jpg --> 2.jpg
最近はこのAppleScriptのように、ファイル操作処理はNSFileManagerで実行し、Finderには「選択中のファイルの情報を取得」ぐらいしか行わせていません。そのため、ファイル処理が従来よりもはるかに高速に行えて、ちょっと楽しいぐらいです。
数千ファイルのコピー(込み入ったルールに従いJANコードなどのデータを参照して名前を決定したり、フォルダ名を製品名で作成したりとか)が1・2秒というオーダーで終わってしまうのは、SSD上で処理しているせいもありますが、NSFileManager経由でのファイル処理を行っているおかげです(64bit化したCocoa Finderはとにかく処理が遅いのでAppleScriptからのファイル操作用に使うとダメ)。
AppleScript名:tempフォルダに移動しつつリネームして、終了後に元フォルダに戻す v2.scptd |
— Created 2018-08-14 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" property NSUUID : a reference to current application’s NSUUID property NSString : a reference to current application’s NSString property NSFileManager : a reference to current application’s NSFileManager property tResList : {} property tFol : "" property newNameTemplate : "test_" set tResList to {} set tFol to POSIX path of (path to temporary items) tell application "Finder" set aSel to selection as alias list if aSel = {} then display notification "No Selection" return end if end tell –コピー元ファイルの親フォルダを求める(リネーム後に戻す) set origFol to POSIX path of (contents of first item of aSel) set origFolStr to ((NSString’s stringWithString:origFol)’s stringByDeletingLastPathComponent()) as string –一時フォルダに移動してリネーム set aCount to 1 repeat with i1 in aSel set j1 to POSIX path of (contents of i1) set newName to newNameTemplate & (aCount as string) set j1Res to renameSafely(j1, newName) of me set the end of tResList to (j1Res as string) set aCount to aCount + 1 end repeat –元のフォルダに戻して一時フォルダ削除 repeat with i2 in tResList set j2 to contents of i2 set j2Res to (my moveFileAt:j2 toFolder:origFolStr) set j2Res to deleteParentFol(j2) of me end repeat –安全なリネーム(個別のテンポラリフォルダに移動してリネーム) on renameSafely(aFile, newName) –set tFol to POSIX path of (path to temporary items) set aUUIDstr to ((NSUUID’s UUID()’s UUIDString()) as string) set aTmpFol to tFol & aUUIDstr set dRes to makeDirAbsolutely(aTmpFol) of me set mRes to my moveFileAt:aFile toFolder:aTmpFol set aStr to (NSString’s stringWithString:aFile) set newFullPath to aTmpFol & "/" & aStr’s lastPathComponent() set aExt to aStr’s pathExtension() set fRes to renameFileItem(newFullPath, newName) of me set renamedPath to aTmpFol & "/" & newName & "." & aExt return renamedPath end renameSafely –指定パスににフォルダを作成する on makeDirAbsolutely(dirStr) set fileManager to NSFileManager’s defaultManager() set aRes to fileManager’s createDirectoryAtPath:dirStr withIntermediateDirectories:true attributes:(missing value) |error|:(reference) copy aRes to {aFlag, aError} return aFlag as boolean end makeDirAbsolutely on moveFileAt:POSIXPath toFolder:folderPOSIXPath set POSIXPath to NSString’s stringWithString:POSIXPath set theName to POSIXPath’s lastPathComponent() set folderPOSIXPath to NSString’s stringWithString:folderPOSIXPath set theNewPath to folderPOSIXPath’s stringByAppendingPathComponent:theName set fileManager to NSFileManager’s defaultManager() set theResult to fileManager’s moveItemAtPath:POSIXPath toPath:theNewPath |error|:(missing value) return theNewPath end moveFileAt:toFolder: on renameFileItem(aPOSIXpath as string, newName as string) set theNSFileManager to NSFileManager’s defaultManager() set POSIXPathNSString to NSString’s stringWithString:(aPOSIXpath) –Make New File Path set anExtension to POSIXPathNSString’s pathExtension() set newPath to (POSIXPathNSString’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:newName)’s stringByAppendingPathExtension:anExtension –Rename if theNSFileManager’s fileExistsAtPath:newPath then return false else set theResult to theNSFileManager’s moveItemAtPath:POSIXPathNSString toPath:newPath |error|:(missing value) if (theResult as integer = 1) then return (newPath as string) else return false end if end if end renameFileItem on deleteParentFol(aPOSIX) set aStr to NSString’s stringWithString:aPOSIX set parentFol to aStr’s stringByDeletingLastPathComponent() set aRes to deleteItemAt(parentFol) of me return aRes end deleteParentFol on deleteItemAt(aPOSIX) set theNSFileManager to NSFileManager’s defaultManager() set theResult to theNSFileManager’s removeItemAtPath:(aPOSIX) |error|:(missing value) return (theResult as integer = 1) as boolean end deleteItemAt |
2002〜2003年ごろにAppleScriptでいまのSiriライクな人工知能インタフェース「Newt On」を作っていた頃の、プレゼン資料をSlideshare上に公開しました。
→ Slideshare上のアカウントが凍結されたので(なんで?) 本Blogに掲載しておきました。
書籍「AppleScriptの穴Blogアーカイブvol.3」掲載のNewt On Project記事と合わせてご覧になると、より分かりやすいと思います。
指定のCSVファイルを読み込んで、表インタフェースで表示確認するAppleScriptです。
Numbers.appから文字コードにUTF-8を指定して書き出したCSVファイルを想定しています。CSVデータのparseにはBridgePlusを、表UIの表示にはMyriad Tables Libを利用しています。
ほぼ、テンプレートともいえるような定型処理ですが、巨大なAppleScriptに入れたときにはusing terms from句がないとアプレットとして動作中にクラッシュが発生する可能性があります。
CSVデータの処理はありふれたありきたりの内容で、本来は処理内容にはほとんど関係ない表示・確認を行わせるとユーザーの反応がいいようです。
あとは、CSVデータ中にカラムヘッダーが存在するかどうかをCSVデータからでは検出できないことが問題です。せめて、メタデータ中にでもカラムヘッダーの有無を(Export時に)書いておいてくれるとよいのですが。あとは、CSV1行目のデータと他の行との文字コードの分布状況を比較して統計処理すると、相関性を数値化してヘッダーかどうかを計算できそうですが、、、そこまでやるのは「やりすぎ」でしょう。
ユーザーにヘッダーの有無を確認するとか、そのぐらい?
_kMDItemOwnerUserID = 504 kMDItemAlternateNames = ( "2010_index.csv" ) kMDItemContentCreationDate = 2018-08-14 00:51:24 +0000 kMDItemContentModificationDate = 2018-08-14 00:51:24 +0000 kMDItemContentType = "public.comma-separated-values-text" kMDItemContentTypeTree = ( "public.comma-separated-values-text", "public.data", "public.delimited-values-text", "public.plain-text", "public.item", "public.content", "public.text" ) kMDItemDateAdded = 2018-08-14 00:51:24 +0000 kMDItemDisplayName = "2010_index" kMDItemFSContentChangeDate = 2018-08-14 00:51:24 +0000 kMDItemFSCreationDate = 2018-08-14 00:51:24 +0000 kMDItemFSCreatorCode = "" kMDItemFSFinderFlags = 16 kMDItemFSHasCustomIcon = (null) kMDItemFSInvisible = 0 kMDItemFSIsExtensionHidden = 1 kMDItemFSIsStationery = (null) kMDItemFSLabel = 0 kMDItemFSName = "2010_index.csv" kMDItemFSNodeCount = (null) kMDItemFSOwnerGroupID = 80 kMDItemFSOwnerUserID = 504 kMDItemFSSize = 7746 kMDItemFSTypeCode = "" kMDItemKind = "Comma Separated Spreadsheet (.csv)" kMDItemLogicalSize = 7746 kMDItemPhysicalSize = 8192 kMDItemUserCreatedDate = ( "2018-08-14 00:51:24 +0000" ) kMDItemUserCreatedUserHandle = ( 504 )
▲ここ(メタデータ)にCSVのヘッダーの有無の情報が入っているといいのに、、、、にしても、メタデータに大昔のResource Forkよろしくいろんなデータが突っ込まれるようになってきました
▲CSVファイルの選択
▲CSVファイル内容の表示
▲CSV内のカラム数が合っていた場合の動作
▲CSV内のカラム数が合っていない場合の動作(データ確認表示)
▲CSV内のカラム数が合っていない場合の動作(ダイアログ表示)
AppleScript名:CSVデータを読み込んで表インタフェースで表示確認 v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2018/08/10 — – Copyright © 2018 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "AppKit" use Bplus : script "BridgePlus" –https://www.macosxautomation.com/applescript/apps/BridgePlus.html use tableLib : script "Myriad Tables Lib" –https://www.macosxautomation.com/applescript/apps/Script_Libs.html#MyriadTablesLib –Numbersから書き出したUTF-8のCSVを読み取る set someString to read (choose file of type {"public.comma-separated-values-text"}) as «class utf8» –読み込んだテキストをParseして、ヘッダーとデータ本体に分離 set {theHeader, origList} to readCSVAndParse(someString) of me –Table UIを表示して内容を確認 set colMax to 3 –あらかじめ期待しているカラム数 set {dRes, tRes} to dispTableUI(theHeader, origList, colMax) of me if dRes = false then display dialog "データ不全のため処理を終了します" with title "CSVデータエラー" return else display dialog "CSVデータの処理を続行します" with title "CSVデータOK" end if on readCSVAndParse(someString) load framework using terms from script "BridgePlus" set theData to current application’s SMSForder’s arrayFromCSV:someString commaIs:"," set origList to (current application’s SMSForder’s subarraysIn:theData paddedWith:"" |error|:(missing value)) as list end using terms from set theHeader to contents of first item of origList –Header Row set origList to rest of origList –Data return {theHeader, origList} end readCSVAndParse on dispTableUI(theHeader, origList, colMax) set {curDispW, curDispH} to (current application’s NSScreen’s mainScreen()’s frame()’s |size|()) as list set curLen to length of first item of origList set dRes to (curLen = colMax) as boolean if dRes = true then set aMessage to "問題がなければ「OK」をクリックして実行してください" –Normal message set aTitle to "CSVファイルの内容確認" else set aMessage to "CSVファイルのカラム数が期待どおりではありません。「Cancel」をクリックして実行を停止してください" –Error Message set aTitle to "CSVファイルに異常あり" end if using terms from script "Myriad Tables Lib" tell script "Myriad Tables Lib" set aDispBounds to my makeInitialBounds:(curDispW – 100) withHeight:(curDispH – 100) set theTable to make new table with data origList column headings theHeader with title aTitle with prompt aMessage with row numbering, empty selection allowed and multiple lines allowed modify table theTable initial position aDispBounds –表を表示 set tRes to (display table theTable) return {dRes, tRes} end tell end using terms from end dispTableUI on makeInitialBounds:(aTWidth as integer) withHeight:(aTHeight as integer) set aDispBounds to current application’s NSScreen’s mainScreen()’s frame() set aWidth to (width of |size| of aDispBounds) set aHeight to (height of |size| of aDispBounds) set xPos to (aWidth div 2) – (aTWidth div 2) set yPos to (aHeight div 2) – (aTHeight div 2) return {xPos, yPos, aTWidth, aTHeight} end makeInitialBounds:withHeight: |
込み入った案件で必要になって、Script Debugger 7を購入しました。Adobeの大型アプリケーションのオブジェクト階層を調査したい場合にはScript Debuggerを使わないのは自殺行為です。
その反面、Xcodeの外部エディタとして使うなら、まだASObjC Explorer 4のほうが使い勝手がいいし(Xcode上で外部エディタを指定してscriptを再編集するとxibファイルのトラブルなど諸問題が解決するのに、Script Debuggerだと解決しない)、クラッシュの頻度もややScript Debuggerのほうが高い気がします。
本気で組んだ仕事用のAppleScript相手だと、Script Debugger上で作って、Script Debugger上で動作させるだけなら問題はないのですが、アプレットを書き出して実行させるあたりで疑問符が出てきます。
AppleScript上で何かの予約語が存在していた場合に、「どのアプリケーション、どのScript Libraries」の用語辞書から使用したものなのかをusing terms from句で囲ってこまめに宣言しておかないと、書き出したAppleScriptアプレットを起動しようとしてもクラッシュします。
# しかも、Scriptのサイズが小さい場合にはクラッシュせず、大きくなるとクラッシュするという、、、
また、using terms from句で囲むさいにはuse宣言で代入したプロパティが使えず(using terms from bPlus とか)、using terms from script “BridgePlus”のように書かないとScript Debugger上で構文確認が通りません(こちらはAppleScript処理系そのものの動作の癖?)。
さらに、「anything」(≒”any”)という(Appleに)いい加減に実装されてきた予約語を、さまざまなライブラリで別々に(ちゃんと)実装していたりすると、Script Debugger上での構文確認時に「どのアプリケーションの予約語なのか分からないぞ」というワーニングメッセージが出ます。これ、ダイアログで表示するなら、リストから選択させるとかいった動作の方がいいと思います。
このあたり、Apple純正のスクリプトエディタやASOBjC Explorer 4(現在はScript Debuggerと合流)では確認していない現象ですが、Script Debuggerを使っているかぎりはこまめにusing terms fromで囲う必要があるようです。
ごく一部の愛読者の皆様からのご要望を受け、2018年1月のBlog消滅以前の内容を1年ごとにまとめた「Blogアーカイブ本」のVol.3を販売開始しました。Vol.3は2010年1〜12月の内容をまとめています。全401ページ、2,000円。ファイル形式はPDFで、キーワードによる全文検索や印刷が行えます。
2014年以前の巻についてはCocoaの機能をほとんど使っていないので、Cocoa Scriptingについてはわからないとか、Cocoa Scriptingになじめない方には「Cocoaを使わないやりかた」が説明されている、現時点では数少ないまとまった参考文献になるはずです。
また、2000年代初頭にAppleScriptで開発した人工知能インタフェース「Newt On」プロジェクトについての当時のレポート記事、初期バージョンのNewt Onのソースリストなども掲載しています(さすがに古いバージョンのMac OS X上で構文確認する必要があったので、URLリンク入りのプログラムリストにはできませんでしたが……色分けリストでは掲載しています)。
続刊刊行スケジュールについては、「できた分から」としか言いようがありませんが、各巻のボリュームについては、
vol.4:2011/1〜2012/12(仮組みページ数:372)
vol.5:2013/1〜2014/12(仮組みページ数:276)
vol.6:2015/1〜12(仮組みページ数:320)
といったところです。価格は一律2,000円です。当初は日本語版だけですが、英語版も出せるといいですね。Vol.5/6については、Cocoa Scriptingがわからないと手が出しづらいので、Cooca Scriptingの入門資料を添付することも検討中です。
–Newt On Projectアーカイブ部
#1 始動
#2 インテリジェントな環境を構築するもうひとつの要素〜SOAP
#3 Wordに見るインテリジェント処理の可能性
#4 ついに姿を現した「えせNewton」
#5 えせNewtonの正式名称が決定
#6 えせNewton、大地に立つ!
#7 えせNewton 改良強化新型の系譜
#8 コマンド、目的語、サブキーを認識
#9 Classic MacOS 9への挑戦
#10 ついに疑似対話が可能に
#11 えせNewton、Mac界に激震をもたらす?
#12 複数のコマンドを実装 メール出せます
#13 Newtonを超えた~Now, “Newt On” is beyond the Newton.
#14 Newt Onから見た日本語形態素解析事情
#15 Newt On一般向けバイナリ配布開始
ABUI “Newt On”とは?
Newt On Project 活動報告
用語解説
Newt On ロードマップ
#番外編 プロジェクトMac OS X~技術屋たち「Newtonよもう一度! 〜スタートレックの世界を夢見た男たち」
–Blogアーカイブ部
指定形式でスクリーンキャプチャを撮る
動的に演算子を指定してリストから要素を取り出す
lengthの同義語number of items
タイマー割り込み処理
指定されたファイルのparentにラベルとコメントを付ける
Aperture 3でAppleScript関連機能に大きな前進
AppleScriptで扱う「パス」について(1)
AppleScriptにおける「パス」について(3)
AppleScriptにおける「パス」について(4)
I/Oデータの地デジチューナー「m2TV」添付ソフトがAppleScriptに対応
AppleScript用語辞書の変更履歴が分かるよう「DICT」メニューを整備中
「AppleScriptの穴」、気がつけば3年目
m2TVの録画スケジュール情報を取得する
m2TVの録画ライブラリ情報を取得する
pdfinfoの結果をparseするv3
リストから指定のアイテムを削除する
SHA-1 digestを求める
次期SilverlightでAppleScriptのサポートが現実のものに?
iTunes 9.1で用語辞書に変更アリ
脳波ビジュアライザ「IBVA Ver.4」がAppleScriptに対応
Photoshop CS3で指定アクションセット内のアクション一覧を取得する
現在編集中のXcodeプロジェクトのビルドターゲットが生成したpreferenceファイルを削除する v3
ImageEventsで画像回転(時計回りに90度)
ImageEventsで画像回転(反時計回りに90度)
ImageEventsで画像の縦横サイズを取得する
ImageEventsで75%にリサイズ
ImageEventsでTIFF保存
ImageEventsで横幅400ピクセルに
4月29日のラジオ番組に参加します
Finder上で選択中の画像を横幅400ドットにリサイズ
指定のtarアーカイブを展開する
手書き文字認識アプリケーション「QuickScript」がバージョン3.0でAppleScriptに対応
マウント中のdiskimage中のファイルをすべて削除
Xcodeで表示中のScriptの各ハンドラ行数をカウント
ファイルのコピー(通常実行、非同期高速実行)
指定のリストから削除指定リストに入っているアイテムを削除する
deleteListに入っている数値に該当するものをリストから削除する
リストから、指定データが何アイテム目に登場するかを算出
指定のアプリケーションの起動を待つ
指定文字列のAppleScriptをAppleScriptエディタ上で実際に実行して結果を文字列として取得する
指定文字列のAppleScriptをAppleScriptエディタ上で実際に実行して結果を文字列として取得する 10.5_10.6
指定のzipアーカイブを展開してファイルを取り出す
指定フォルダ内をすべて削除し、そのフォルダも削除
iChatで文字チャットの相手に文字列を送信。長い場合にはテキストに書き出してzip圧縮して送信
リストをレコードに v2
iEPGのファイルから各種情報を取り出してみるテスト v2
iEPGのファイルから各種情報を取り出してみるテスト v3
iEPGのファイルから各種情報を取り出してみるテスト v4
Mail.appで選択しておいたメッセージに返信してsubjectと本文を加える
iPhotoに指定画像をインポートする
DateオブジェクトからYYYY-MM-DD-hh-mm-ssの形式の文字列を返す
数値の序数の文字列を返す
アドレスブックでmy cardがあるかどうか取得する
Photoshopで複数画像の「差の絶対値」を計算するテスト v3
Snow Leoaprdで追加になったsayコマンドのパラメータテスト3
ファイル作成日付を変更する
文字列から数字の部分を取り出してリスト化
指定矩形座標内に含まれる座標をピックアップ
現在のドキュメント上のアイテムをすべてグループ解除
GUI部品にAppleScriptを貼り付ける「UI Actions 2.0.0」
近似色を求めるサンプル
エイリアス書類からオリジナルファイルの情報を取得する
Script Objectのparseのじっけん v4
時刻文字列の差を数値で返す
改行のみで囲まれた行をリストで取り出す
現在のユーザーの権限を調べる v2
数値リストを連続部分に分解する
Elgatoのハードウェアエンコーダturbo.264 HDがAppleScriptに対応
指定年_月の初日(1日)がその年の何日目かを取得する
指定日がその年の何日目かを取得する
指定年月の最初のx曜日を返す
指定年月の最後のx曜日を返す
与えられた日付の「月」が異なるかどうかチェック
2つのdateの差を月単位で取得する
実体参照している文字列をデコードする
簡単な素因数分解
簡単な素因数分解v2
数字で指定した月の英語名称を返す
2つのdateの差を月単位で取得する。パラメータはdateオブジェクトではなくリストで
dateに対してincMonthNumか月足した結果の年、月を取得する
openハンドラでファイルパス以外を渡す
指定の数字の組み合わせのすべてのパターンをリストアップ
アプリのアイコンをJPEG画像化してデスクトップに書き出すv23
SkimでPDFをページごとに分割する
指定名称のムービーを指定開始-終了時間の間を取り出して指定名称で保存 v1
Font Bookでfont collectionを作成する
Font Bookで指定文字列ではじまるfont collectionをすべて削除する
指定のフォントファイルをFont Bookに追加して情報を取得する
指定のフォントをFont Bookから削除する v3
フォルダ差分コピー
指定フォルダ以下のファイルやフォルダからラベルを外す
Font Bookで選択中のフォントを削除(Font Containerなしフォント、および複数選択対応)
フォント情報をSystem Profiler経由で取得する
ひらがなカタカナ変換
iLife 11のAppleScript用語辞書の変更点
AppleScriptObjCでハマったこと
数字桁漢字まじり日本語的数値表現テキストをparseする
Mobiolaで静止画像をキャプチャする
Mobiolaで静止画像をキャプチャ、ファイル名指定つき
Mobiolaでビデオ録画を開始、停止
Finder上で選択中のフォルダをdiskimageに(パスワード指定つき)
入れ子のリストから、指定アイテムの値が連続して同じ値で続くブロックを検出する
Terminal.appで指定コマンドを実行する
指定フォルダ容量をMB単位で返す_v2
Mac添付の赤外線リモコンのon,off_v2
Adobe Illustrator CC 2018(バージョン22.1)で、複数のアートボードを持つAdobe Illustrator書類をアートボードごとに分割保存するAppleScriptです。
オプションの指定方法にものすごく納得しづらいものを感じるものの、これで動くので仕方ないだろうかというところ。
AppleScript名:複数のアートボードを持つAI書類を、デスクトップにアートボードごとに分割して保存.scptd |
use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions set dtPath to ((path to desktop) as string) & "template.ai" tell application "Adobe Illustrator" tell front document set aCount to (count every artboard) as string set sOpt to {class:Illustrator save options, save multiple artboards:true, artboard range:"1-" & aCount} save as Illustrator in file dtPath with options sOpt end tell end tell |
Adobe Illustrator CC 2018(バージョン22.1)で、オープン中の(複数のアートボードを持つ)書類を、アートボード単位で別書類に分けるAppleScriptの改修版です。
書類は(とりあえず)デスクトップに書き込まれます。前バージョンのように、アートボード外のオブジェクトが残るといったことはありません。
「exportSelectedArtwork」という長いコマンドを見つけて、ためしてみたら期待どおりの挙動であったために使ってみました。
……すると、なおのことAppleScriptからartworkのselectができないのか?(実行すると100% Illustratorがクラッシュする) artworkからnameの取得ができないのか?(実行するとエラーになる)というあたりが謎です。
artboardも選択状態になっている(はず)だと思って、そのまま実行してみたところ、artboardのサイズでは書き出されていないようです。少し別の方法を試す必要があり、自分は前バージョンのScriptにもどって作業を行っています。
AppleScript名:オープン中のIllustrator書類をartboardごとに分割 v3.scptd |
— – Created by: Takaaki Naganoya – Created on: 2018/08/07 – Modified on: 2018/08/09 – Copyright © 2018 Piyomaru Software, All Rights Reserved — use AppleScript version "2.5" — El Capitan (10.11) or later use framework "Foundation" use scripting additions set dPath to (path to desktop) as string set namesList to {} tell application "Adobe Illustrator" tell front document set aCount to count every artboard set aPath to (file path) as string end tell repeat with i from 1 to aCount set aName to getArtboardName(i – 1) of me –index base correction (1 –> 0) set outPath to dPath & aName & ".ai" selectArtboard(i – 1) of me –index base correction (1 –> 0) tell front document selectobjectsonactiveartboard exportSelectedArtwork to file outPath end tell end repeat end tell –https://lists.apple.com/archives/applescript-users/2015/Jul/msg00104.html on selectArtboard(theArtboardIndex as string) tell application "Adobe Illustrator" tell front document –Index is 0 based do javascript ("app.activeDocument.artboards.setActiveArtboardIndex(" & theArtboardIndex & ")") end tell end tell end selectArtboard on getArtboardName(theArtboardIndex as string) tell application "Adobe Illustrator" tell front document –Index is 0 based do javascript ("app.activeDocument.artboards[" & theArtboardIndex & "].name") end tell end tell end getArtboardName |
Adobe Illustrator CC 2018(バージョン22.1)で、オープン中の(複数のアートボードを持つ)書類を、アートボード単位で別書類に分けるAppleScriptです。
書類は(とりあえず)デスクトップに書き込まれます。
こういうコマンドが最初からあってもおかしくないところですが、存在していないようなので書いてみました。まずは、オープン中の書類のパスを取得し、各アートボードの名称を取得して書き出し時のファイル名として使用します。
元ファイルをクローズしては、デスクトップに新規名称でコピーし、コピーした書類をオープン、書き出し対象外のアートボードを順次選択してはアートボード上のオブジェクトを削除してアートボード自体も削除、すべて削除したら保存してクローズという動作を行います。
本来であれば高速化のために複数のアートボードを一括で削除することを考えるところですが、アートボードの削除だけであればできるものの、アートボード上のオブジェクトの削除が行えなくなってしまうので、順次削除に落ち着きました。また、本Scriptが本気でスピードを求められるようなハードな用途ではなく、文字や画像の流し込みテンプレートのメンテナンス用という「平和な用途」のために作成したものであるため、高速化処理については考慮していません。
最初からアートボード外に置かれたオブジェクトについては削除していません。あくまで自分用のツールなので作り込む必要性を感じません。気が向いたら削除するのではないでしょうか。
本Scriptの作成中にも、単にAppleScriptで命令を出しただけで派手にクラッシュしまくるAdobe Illustrator。バージョンが上がって見た目が変わっても、あの出来の悪いIllustratorであることに変わりはないのだと思い知らされました。
AppleScript名:オープン中のIllustrator書類をartboardごとに分割 v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2018/08/07 — – Copyright © 2018 Piyomaru Software, All Rights Reserved — use AppleScript version "2.5" — El Capitan (10.11) or later use framework "Foundation" use scripting additions set dPath to (path to desktop) as string set namesList to {} tell application "Adobe Illustrator" tell front document set aCount to count every artboard set aPath to (file path) as string end tell repeat with i from 1 to aCount set aName to getArtboardName(i – 1) of me –index base correction (1 –> 0) set the end of namesList to aName end repeat close every document saving no end tell repeat with i from 1 to aCount set aRes to retSerialListWithExclusion(1, aCount, i) of me set aaRes to reverse of aRes –artboardをIndex値で指定するため、先頭から削除するとIndex値が変わる。そのため逆順に処理 set tmpName to contents of item i of namesList set fromFile to POSIX path of aPath set fromFilePOSIX to quoted form of POSIX path of fromFile set toFile to (dPath & tmpName & ".ai") set toFilePOSIX to quoted form of POSIX path of toFile set shText to "cp " & fromFilePOSIX & " " & toFilePOSIX do shell script shText tell application "Adobe Illustrator" open file toFile tell front document repeat with ii in aaRes set jj to ii as integer selectArtboard(jj – 1) of me –index base correction (1 –> 0) selectobjectsonactiveartboard delete selection delete artboard jj end repeat close with saving end tell end tell end repeat –https://lists.apple.com/archives/applescript-users/2015/Jul/msg00104.html on selectArtboard(theArtboardIndex as string) tell application "Adobe Illustrator" tell front document –Index is 0 based do javascript ("app.activeDocument.artboards.setActiveArtboardIndex(" & theArtboardIndex & ")") end tell end tell end selectArtboard on getArtboardName(theArtboardIndex as string) tell application "Adobe Illustrator" tell front document –Index is 0 based do javascript ("app.activeDocument.artboards[" & theArtboardIndex & "].name") end tell end tell end getArtboardName on retSerialListWithExclusion(aFromNum as integer, aToNum as integer, anEXnum as integer) set outList to {} repeat with i from aFromNum to aToNum if i = anEXnum then — else set the end of outList to i end if end repeat return outList end retSerialListWithExclusion |
本BlogのようなWordPressで運用されており、AppleScriptのURLリンクを記事に埋め込んでいるWordPressに対して、XML-RPC経由で指定IDの記事本文を取得し、埋め込まれているURLリンクからAppleScriptのソースコードを取得して、メモリー上でコンパイルして書式つきテキストに変換し、AppleScript構文書式をもとにpropertyラベルを抽出、そのうちCocoa Classのみをリストで取得するAppleScriptです。
本Blogに投稿した記事から宣言しているCocoa Classを抽出し、自動でタグ付けするために準備したものです。1記事に対して複数のAppleScriptが掲載されている場合にも対応しています。
HTMLReader.frameworkを用いてBlog本文からのリンク抽出、リンクURL抽出を行っています。
–> HTMLReader.framework(To ~/Library/Frameworks/)
本Sample Scriptで指定したIDの記事のproperty部分はこのようになっており、
本Scriptの実行によって抽出されたCocoa Class(単なる変数的なproperty項目や、Enumは対象外のため排除)は、
–> {“NSString”, “NSArray”, “OSAScript”, “NSPredicate”, “OSALanguage”, “NSDictionary”, “OSALanguageInstance”, “NSBundle”, “NSUnarchiver”}
のようになります。自分の環境でMacBook Proを有線ネットワーク接続した状態で、3.3〜3.4秒程度かかっています。大量の記事を処理する場合には本AppleScriptの並列処理を行うと処理時間の大幅な短縮が期待できます(MacのCPUがサーマルスロットリングで速度低下しなければ)。
また、負荷が集中している特定コアの動作周波数を上げ、他のコアの動作周波数を落とすTurbo Boostが有効な状態で並列処理を実行すると、並列処理を行う意義そのものが低下してしまうため、Turbo-Boost-Switcherのようなツールの併用が必要と思われます。
AppleScript名:WordPressの指定IDの記事にリンクされているapplescriptからCocoa Classのproperty宣言を抽出 v3 |
— Created 2018-07-30 by Takaaki Naganoya — Modified 2018-08-05 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "AppKit" use framework "OSAKit" use framework "HTMLReader" –https://github.com/nolanw/HTMLReader property |NSURL| : a reference to current application’s |NSURL| property NSArray : a reference to current application’s NSArray property NSString : a reference to current application’s NSString property NSBundle : a reference to current application’s NSBundle property NSThread : a reference to current application’s NSThread property OSAScript : a reference to current application’s OSAScript property NSPredicate : a reference to current application’s NSPredicate property NSTextView : a reference to current application’s NSTextView property NSDictionary : a reference to current application’s NSDictionary property NSUnarchiver : a reference to current application’s NSUnarchiver property OSALanguage : a reference to current application’s OSALanguage property OSAScriptView : a reference to current application’s OSAScriptView property NSMutableArray : a reference to current application’s NSMutableArray property OSAScriptController : a reference to current application’s OSAScriptController property NSMutableDictionary : a reference to current application’s NSMutableDictionary property OSALanguageInstance : a reference to current application’s OSALanguageInstance property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding set postID to 3864 set {myUser, myPass} to getAcountData() of me set aURL to "http://piyocast.com/as/xmlrpc.php" set cocoaClassList to getCocoaPropListFromPost(aURL, postID, myUser, myPass) of me –> {"NSString", "NSBundle", "NSPredicate", "NSDictionary", "NSMutableArray", "NSMutableDictionary"} –指定Blogの指定IDの記事にURLリンクされているAppleScriptから、Cocoa Classのpropertyのみ取得する on getCocoaPropListFromPost(aURL, postID, myUser, myPass) –AppleScriptの構文色分け設定ファイルを読み込んで、重複色のチェックを実施 set cList to getAppleScriptSourceColors() of me set cRes to chkASLexicalFormatColorConfliction(cList) of me –構文色分けの重複色チェック if cRes = false then error "There is some duplicate(s) color among AppleScript’s lexical color settings" –WordPressの指定Post IDの記事を取得してリンクされているURLからURL Schemeでフィルタして、リンクされているAppleScriptのソースを取得 set aScheme to "applescript://" set sourceList to getASSouceLinkedInWordPressPost(postID, aURL, aScheme, myUser, myPass) of me –AppleScriptのソースをRAM上でコンパイル(構文確認)して、構文色分けしたRTFを取得。RTFの書式情報をparseしてattribute runsと同様のrecordを生成 –構文色分けをもとにproperty項目を抽出し、Cocoa Classに該当するもののみを抽出 set outList to {} repeat with i in sourceList set j to contents of i set anAttrStr to compileASandReturnAttributedString(j) of me set attrRes to getAttributeRunsFromAttrString(anAttrStr) of me set propNames to getPropertyNamesCocoaOnly(cList, attrRes) of me if propNames is not equal to {} then set outList to outList & propNames end if end repeat –1D Listのユニーク化(重複要素の排除) set aArray to NSArray’s arrayWithArray:outList set bArray to aArray’s valueForKeyPath:"@distinctUnionOfObjects.self" set bList to bArray as list of string or string –as anything return bList end getCocoaPropListFromPost –Property名称を取得する on getPropertyNamesCocoaOnly(cList, aRec) script spdHnd property aRec : {} end script set (aRec of spdHnd) to aRec set targAttr to contents of item 7 of cList –ハンドラあるいは変数 set tmpCoStr to ((redValue of targAttr) as string) & " " & ((greenValue of targAttr) as string) & " " & ((blueValue of targAttr) as string) set ontoColItem to contents of item 3 of cList –スクリプティング予約語(on/to) set ontoCoStr to ((redValue of ontoColItem) as string) & " " & ((greenValue of ontoColItem) as string) & " " & ((blueValue of ontoColItem) as string) –変数あるいはハンドラ名称をリストアップ(variables & handler) set tmp1Array to NSArray’s arrayWithArray:(aRec of spdHnd) set thePred0 to NSPredicate’s predicateWithFormat_("colorStr == %@", tmpCoStr) set dArray to (tmp1Array’s filteredArrayUsingPredicate:thePred0) as list –改行を含むデータをリストアップ(text data contains return) set thePred1 to NSPredicate’s predicateWithFormat_("stringVal CONTAINS %@", return) set eArray to ((tmp1Array’s filteredArrayUsingPredicate:thePred1)’s valueForKeyPath:"itemIndex") as list set the beginning of eArray to 0 –ハンドラ宣言部がTopに来る場合に備える –"property"(プロパティ宣言)の項目をリストアップ 文字と色で抽出 set thePred2 to NSPredicate’s predicateWithFormat_("stringVal == %@ && colorStr == %@ ", "property", ontoCoStr) set fArray to ((tmp1Array’s filteredArrayUsingPredicate:thePred2)’s valueForKeyPath:"itemIndex") as list set handlerList to {} –property ではじまるハンドラの抽出 repeat with i in eArray –改行を含むテキストのアイテム番号リスト set j to (contents of i) as integer repeat with ii in fArray –"on"の項目リスト set jj to (contents of ii) as integer set handlerStr to missing value if (j + 1) = jj then set handlerStr to stringVal of (item (jj + 2) of ((aRec of spdHnd) as list)) else if (j + 2) = jj then set handlerStr to stringVal of (item (jj + 2) of ((aRec of spdHnd) as list)) end if set tmpStr to repChar(handlerStr, "|", "") of me if tmpStr is not in {"error", missing value} and tmpStr is not in handlerList then –抽出したProperty宣言がCocoa Classのものかどうか判定 if searchClassInFrameworks(tmpStr) of me is not equal to false then set the end of handlerList to tmpStr end if end if end repeat end repeat return handlerList end getPropertyNamesCocoaOnly –RAM上にスクリプトエディタと同じ部品を組み立て(非表示)、AppleScriptのソーステキストからObjectを生成し、Attributed Stringデータを返す on compileASandReturnAttributedString(theSource as string) set targX to 1024 –View Width set targY to 2048 –View Height set osaCon to current application’s OSAScriptController’s alloc()’s init() set osaView to current application’s OSAScriptView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, targX, targY)) set resView to NSTextView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, targX, targY)) resView’s setRichText:true resView’s useAllLigatures:true osaCon’s setScriptView:osaView osaCon’s setLanguage:(OSALanguage’s languageForName:"AppleScript") osaCon’s setResultView:resView osaView’s setString:theSource osaCon’s compileScript:(missing value) –Compile(構文確認) set aRes to (osaView’s attributedString()) return aRes end compileASandReturnAttributedString –Attributed StringをDictionary化 on getAttributeRunsFromAttrString(theStyledText) script aSpd property styleList : {} end script set (styleList of aSpd) to {} —for output set thePureString to theStyledText’s |string|() –pure string from theStyledText set theLength to theStyledText’s |length|() set startIndex to 0 set itemCount to 1 repeat until (startIndex = theLength) set {theAtts, theRange} to theStyledText’s attributesAtIndex:startIndex longestEffectiveRange:(reference) inRange:{startIndex, theLength – startIndex} –String set aText to (thePureString’s substringWithRange:theRange) as string –Color set aColor to (theAtts’s valueForKeyPath:"NSColor") if aColor is not equal to missing value then set aSpace to aColor’s colorSpace() set aRed to (aColor’s redComponent()) * 255 set aGreen to (aColor’s greenComponent()) * 255 set aBlue to (aColor’s blueComponent()) * 255 set colList to {aRed as integer, aGreen as integer, aBlue as integer} –for comparison set colStrForFind to (aRed as integer as string) & " " & (aGreen as integer as string) & " " & (aBlue as integer as string) –for filtering else set colList to {0, 0, 0} set colStrForFind to "0 0 0" end if –Font set aFont to (theAtts’s valueForKeyPath:"NSFont") if aFont is not equal to missing value then set aDFontName to aFont’s displayName() set aDFontSize to aFont’s pointSize() end if set the end of (styleList of aSpd) to {stringVal:aText, colorStr:colStrForFind, colorVal:colList, fontName:aDFontName as string, fontSize:aDFontSize, itemIndex:itemCount} set startIndex to current application’s NSMaxRange(theRange) set itemCount to itemCount + 1 end repeat return (styleList of aSpd) end getAttributeRunsFromAttrString –指定クラスがいずれかのCocoa Frameworkに所属しているかを検索 on searchClassInFrameworks(aTarget) set aClass to current application’s NSClassFromString(aTarget) if aClass = missing value then return false set theComponenents to (NSBundle’s bundleForClass:aClass)’s bundleURL’s pathComponents() set thePred to NSPredicate’s predicateWithFormat:"pathExtension == ’framework’" set aRes to (theComponenents’s filteredArrayUsingPredicate:thePred)’s firstObject() as list of string or string return aRes end searchClassInFrameworks –指定Post IDのWordPress記事から、指定SchemeのURLを抽出し、AS Sourceをdecodeしてproperty行のみ抽出 on getASSouceLinkedInWordPressPost(postID, aURL, aScheme, myUser, myPass) –call xmlrpc命令に対するURLの間接指定を有効にするために、AppleScriptの構文解釈機能をダミーURLでだます using terms from application "http://piyocast.com/as/xmlrpc.php" –URLと判定されればなんでもいい tell application aURL set wRes to (call xmlrpc {method name:"wp.getPost", parameters:{"1", myUser, myPass, postID as string}}) end tell end using terms from set aBody to post_content of wRes –Blog本文 –記事中でリンクしているURLを取得し、指定のURL Schemeでフィルタする set urlList to filterURLLinksByScheme(aBody, aScheme) of me set propList to {} repeat with i in urlList set j to contents of i set urlRec to parseQueryDictFromURLString(j) of me set tmpScript to (urlRec’s |script|) as string –Get AppleScript Source set propList to propList & tmpScript end repeat return propList end getASSouceLinkedInWordPressPost on parseQueryDictFromURLString(aURLStr as string) if aURLStr = "" then error "No URL String" set aURL to |NSURL|’s URLWithString:aURLStr set aQuery to aURL’s query() –Get Query string part from URL if aQuery’s |length|() = 0 then return false set aDict to NSMutableDictionary’s alloc()’s init() set aParamList to (aQuery’s componentsSeparatedByString:"&") as list repeat with i in aParamList set j to contents of i if length of j > 0 then set tmpStr to (NSString’s stringWithString:j) set eList to (tmpStr’s componentsSeparatedByString:"=") set anElement to (eList’s firstObject()’s stringByReplacingPercentEscapesUsingEncoding:(NSUTF8StringEncoding)) set aValStr to (eList’s lastObject()’s stringByReplacingPercentEscapesUsingEncoding:(NSUTF8StringEncoding)) (aDict’s setObject:aValStr forKey:anElement) end if end repeat return aDict end parseQueryDictFromURLString –指定のHTML文字列から、Link URLを抽出し、schemeで再抽出する on filterURLLinksByScheme(aBody, aScheme) set conType to "text/html" –HTML文字列をいったんNSDataにしているのは、HTMLReader.frameworkの仕様のため set aData to (current application’s NSString’s stringWithString:aBody)’s dataUsingEncoding:(current application’s NSUTF8StringEncoding) set aHTML to current application’s HTMLDocument’s documentWithData:aData contentTypeHeader:conType set aTextArray to ((aHTML’s nodesMatchingSelector:"a")’s textContent) as list –リンク文字 set aLinkList to ((aHTML’s nodesMatchingSelector:"a")’s attributes’s valueForKeyPath:"href") as list –URL文字列 set outList to {} repeat with i in aLinkList set j to contents of i if j begins with aScheme then set the end of outList to j end if end repeat return outList end filterURLLinksByScheme –文字置換 on repChar(origText as string, targChar as string, repChar as string) set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to targChar set tmpList to text items of origText set AppleScript’s text item delimiters to repChar set retText to tmpList as string set AppleScript’s text item delimiters to curDelim return retText end repChar –AppleScriptの構文色分けのカラー値をRGBで取得する on getAppleScriptSourceColors() — get the plist info as a dictionary set thePath to NSString’s stringWithString:"~/Library/Preferences/com.apple.applescript.plist" set thePath to thePath’s stringByExpandingTildeInPath() set theInfo to NSDictionary’s dictionaryWithContentsOfFile:thePath — extract relevant part and loop through set theArray to (theInfo’s valueForKey:"AppleScriptSourceAttributes") as list set colList to {} repeat with i from 1 to count of theArray set anEntry to item i of theArray set colorData to NSColor of anEntry set theColor to (NSUnarchiver’s unarchiveObjectWithData:colorData) set {rVal, gVal, bVal} to retColListFromNSColor(theColor, 255) of me set fontData to NSFont of anEntry set theFont to (NSUnarchiver’s unarchiveObjectWithData:fontData) set aFontName to theFont’s displayName() as text set aFontSize to theFont’s pointSize() set aColRec to {redValue:rVal, greenValue:gVal, blueValue:bVal, fontName:aFontName, fontSize:aFontSize} set the end of colList to aColRec end repeat return colList end getAppleScriptSourceColors –NSColorからRGBの値を取り出す on retColListFromNSColor(aCol, aMAX as integer) set aRed to round ((aCol’s redComponent()) * aMAX) rounding as taught in school set aGreen to round ((aCol’s greenComponent()) * aMAX) rounding as taught in school set aBlue to round ((aCol’s blueComponent()) * aMAX) rounding as taught in school if aRed > aMAX then set aRed to aMAX if aGreen > aMAX then set aGreen to aMAX if aBlue > aMAX then set aBlue to aMAX return {aRed, aGreen, aBlue} end retColListFromNSColor –AS書式で配色に重複がないかどうかチェック on chkASLexicalFormatColorConfliction(aList) set anArray to current application’s NSArray’s arrayWithArray:aList set bList to (anArray’s valueForKeyPath:"redValue.stringValue") as list set cList to (anArray’s valueForKeyPath:"greenValue.stringValue") as list set dList to (anArray’s valueForKeyPath:"blueValue.stringValue") as list set colStrList to {} repeat with i from 1 to (length of bList) set bItem to contents of item i of bList set cItem to contents of item i of cList set dItem to contents of item i of dList set the end of colStrList to bItem & " " & cItem & " " & dItem end repeat set aRes to returnDuplicatesOnly(colStrList) of me if aRes is equal to {} then return true –重複が存在しなかった場合 else return false –重複があった場合 end if end chkASLexicalFormatColorConfliction on returnDuplicatesOnly(aList as list) set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList set bList to (aSet’s allObjects()) as list set dupList to {} repeat with i in bList set aRes to (aSet’s countForObject:i) if aRes > 1 then set the end of dupList to (contents of i) end if end repeat return dupList end returnDuplicatesOnly on getAcountData() return {"xxxxxxxx_xx", "XXXXXXXXXXXXXXXXXXXXXXXX"} –user name, password end getAcountData |
Google Drive File Stream上でSpotlight Indexを生成するAppleScriptです。
Google Driveには、Gmailのオマケでついてくる個人のデバイス間で情報のシンクロを行う目的のPersonal版(DropboxのGoogle版的なもの)と、企業内やグループ内でファイル共有を行うG SuiteのGoodle Drive File Streamがあり、後者についてはGoogleによる「Google Drive File Stream」ツールが提供され、GoogleDriveをデスクトップにマウントできます。
実際にGoogle Drive File Streamを使ってみたところ、デフォルト状態だとSpotlight検索が効きませんでした。
そこで、Index生成してみたものの…既存のファイルについてはSpotlight検索できませんでした。Index生成後に、新規追加したファイルについてはSpotlight検索が有効でした。
後日談:ただ、Google Drive File Streamに対してSpotlight Indexを常時生成するようにすると、CPUに対する負荷が大きくなるため、実際の運用ではSpotlightを無効にして運用するようにしました。
AppleScript名:Google Drive File Streamでインデックス生成 |
do shell script "mdutil -E /Volumes/GoogleDrive/" with administrator privileges |
自分が試したのは、Google Drive File Stream内の「マイドライブ」領域のみで、
「グループドライブ」領域についてはまだテストできていません。
既存のファイルについては、mdimportコマンドでインデックス作成すれば大丈夫だと思っていますが、実際に試してみると予想外の挙動を行うのかもしれません。
AppleScript名:GDFS上でSpotlight検索 |
— Created 2016-06-17 by Takaaki Naganoya — 2016 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" property NSArray : a reference to current application’s NSArray property NSPredicate : a reference to current application’s NSPredicate property NSSortDescriptor : a reference to current application’s NSSortDescriptor property NSMetadataQuery : a reference to current application’s NSMetadataQuery property NSNotificationCenter : a reference to current application’s NSNotificationCenter property NSMetadataQueryDidUpdateNotification : a reference to current application’s NSMetadataQueryDidUpdateNotification property NSMetadataQueryDidFinishGatheringNotification : a reference to current application’s NSMetadataQueryDidFinishGatheringNotification property searchRes : {} set origPath to POSIX path of (choose folder) set aList to spotlightSearch(origPath, "kMDItemContentType == ’public.png’") of me on spotlightSearch(origPath, aMDfindParam) set my searchRes to {} –initialize initiateSearchForFullPath(aMDfindParam, origPath) –Predicate & Scope Directory –Waiting for the result repeat while my searchRes = {} delay 0.01 end repeat set anObj to my searchRes’s firstObject() –Pick up the first one for test if anObj = missing value then return {} –No Result set resArray to {} repeat with anItem in my searchRes set j to contents of anItem set aPath to (j’s valueForAttribute:"kMDItemPath") as string set the end of resArray to aPath end repeat return resArray end spotlightSearch on initiateSearchForFullPath(aQueryStrings, origPath) set aSearch to NSMetadataQuery’s alloc()’s init() NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:"queryDidUpdate:" |name|:(NSMetadataQueryDidUpdateNotification) object:aSearch NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:"initalGatherComplete:" |name|:(NSMetadataQueryDidFinishGatheringNotification) object:aSearch set aPredicate to NSPredicate’s predicateWithFormat:aQueryStrings aSearch’s setPredicate:aPredicate set aScope to NSArray’s arrayWithObjects:{origPath} aSearch’s setSearchScopes:aScope set sortKeys to NSSortDescriptor’s sortDescriptorWithKey:"kMDItemFSName" ascending:true aSearch’s setSortDescriptors:(NSArray’s arrayWithObject:sortKeys) aSearch’s startQuery() end initiateSearchForFullPath on queryDidUpdate:sender log sender –> (NSConcreteNotification) NSConcreteNotification 0x7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = <NSMetadataQuery: 0x7fa6172c6ca0>} end queryDidUpdate: on initalGatherComplete:sender set anObject to sender’s object anObject’s stopQuery() NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(NSMetadataQueryDidUpdateNotification) object:anObject NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(NSMetadataQueryDidFinishGatheringNotification) object:anObject set my searchRes to anObject’s results() end initalGatherComplete: |