Menu

Skip to content
AppleScriptの穴
  • Home
  • Products
  • Books
  • Docs
  • Events
  • Forum
  • About This Blog
  • License
  • 仕事依頼

AppleScriptの穴

Useful & Practical AppleScript archive. Click '★Click Here to Open This Script' Link to download each AppleScript

タグ: 10.11savvy

ZipArchive Frameworkを使ってパスワード付きZipアーカイブを作成

Posted on 9月 30, 2018 by Takaaki Naganoya

オープンソースのSSZipArchiveを呼び出して、パスワード付きZipアーカイブを作成するAppleScriptです。

AppleScriptそのものにZipアーカイブの操作機能はないので、この手の操作は他のプログラムを操作することになります。

–> Download ZipArchive.framework (To ~/Library/Frameworks)

(1)Unix shellのzipコマンド
do shell scriptコマンド経由でUnix shellのzipコマンドを呼び出す方法です。データをファイルで扱っている場合にはよいのですが、そうでない場合には困ります(変数に入れたデータを圧縮したいとか)。以前に、なろう小説APIを呼び出したときに、GZIP.frameworkを用いて変数内のデータをZip展開しましたが、こういう用途には使えません。

(2)アーカイブユーティリティを呼び出す
/System/Library/CoreServices/Applications/Archive Utility.appを呼び出す方法です。Mac OS X 10.5までは「BOMArchiveHelper」という名前でした。その他、AppleScriptに対応しているZipアーカイバがあればそれを使ってみてもよいでしょう。これも、(1)同様にファイル単位で操作するものが普通なので、使える用途と使えない用途があります。

(3)各種Cocoa Frameworkを呼び出す
Objective-CのZipアーカイブ操作プログラムを見つけ、Frameworkになっていなくても、無理やりXcode上でCocoa Frameworkを作成し、必要なファイルをそこに突っ込んでFramework化して使っています。

今回使うのはSSZipArchiveです。

いろいろなZip操作のObjective-Cのプログラムをダウンロードしては分析してみると、各プログラムで作者の好みが色濃く反映されており、どれか1つで済むとは思えない状況です。

また、必要なメソッドの呼び出しにBlocks構文による記述が必要な場合にはAppleScriptから呼べません(さらにそのメソッドを呼び出すためのメソッドをObjective-C上で定義するとか、回避方法はいろいろありそうです)。

結局、いろいろGithub上で探してはビルドして試しています。

AppleScript名:ZipArchive Frameworkを使ってパスワード付きZipアーカイブを作成(ファイル指定).scptd
— Created 2018-09-28 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ZipArchive" –https://github.com/ZipArchive/ZipArchive

set aPassWord to "yourpassword"
set targFile to POSIX path of (choose file with prompt "Select a file to Zip")
set archiveTarg to targFile & ".zip"

set zipRes to current application’s SSZipArchive’s createZipFileAtPath:(archiveTarg) withFilesAtPaths:{targFile} withPassword:(aPassWord)
return zipRes as boolean

★Click Here to Open This Script 

Posted in file | Tagged 10.11savvy 10.12savvy 10.13savvy | 7 Comments

ZipZap frameworkを使ってZipアーカイブ内の情報を取得しファイルタイプごとに対応出力

Posted on 9月 29, 2018 by Takaaki Naganoya

オープンソースのZipZap frameworkを用いて、指定Zipアーカイブ内の情報を取得し、ファイルタイプごとに対応した出力を行うAppleScriptです。

–> ZipZap.framework(To ~/Library/Frameworks/)

客観的に動作内容を書くとわかりにくいですが、「目的」を書くと非常にわかりやすい部品です。

「バンドル内にパスワード付きのZipアーカイブ化したAppleScript書類を入れておいて、実行時にアーカイブからAppleScriptのソースを取り出して実行する」

というのが、その用途です。

プレーンテキストと画像を取り出せるような記述になっていますが、それらはあくまでも実験用で、AppleScript書類のソースを取り出すのが本来の目的です。

AppleScriptでGUIアプリケーションを作成したときに、GUIまわりは普通にXcode上で記述して(あるいは、外部エディタ上で記述して)おきますが、外部のアプリケーションをコントロールするコードは、実行専用の.scpt形式でかつ書き込み禁止状態にしてバンドル内に入れておいたりします(SandBox対応のため)。

ただ、さまざまな要求を満たすようにGUIアプリケーション操作用のScriptを呼び出そうとすると、load scriptで読み込んで実行させていたのでは、都合がよくない場合があります(何らかのキーを長押しすると実行を停止できるとか、システム内の別の要素……たとえばCPUの温度が上がりすぎたら停止させるとか)。

load scriptで実行すると、

(1)途中で実行停止しにくい
(2)セキュリティ上の制約がきつい(とくにGUI Scriptingまわり)
(3)スクリプトエディタ上で動かしていた時と挙動が変わる箇所がある(Finder上のselectionを取得していた場合とか)

といった問題がありますが、OSAScriptView上にAppleScriptのソースを展開して実行すると、これらの問題を解決できる一方、ソースが見える形式でアプリケーションバンドル内に入れるのはためらわれます。

その問題を解決するために書いたものです。展開したデータをファイル出力せず、すべて変数上で(オンメモリで)処理できるので都合がいいです。

Mac App Storeで販売するアプリケーションでこの部品を使ったことはありませんが、客先に納品するアプリケーションでは使えるといったところでしょうか。ここまで手の込んだプログラムだとリジェクトできないとは思っています(もっとレベルの低い指摘しか来ないので)。

AppleScript名:ZipZap frameworkを使ってZipアーカイブ内の情報を取得してファイルタイプごとに対応出力 v2
— Created 2018-09-28 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
use framework "OSAKit"
use framework "ZipZap" –https://github.com/pixelglow/zipzap
use bPlus : script "BridgePlus" –https://www.macosxautomation.com/applescript/apps/BridgePlus.html

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 NSImage : a reference to current application’s NSImage
property ZZArchive : a reference to current application’s ZZArchive
property OSAScript : a reference to current application’s OSAScript
property NSPredicate : a reference to current application’s NSPredicate
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSURLTypeIdentifierKey : a reference to current application’s NSURLTypeIdentifierKey

set aPassword to "piyomarusoftware"
set aPath to POSIX path of (choose file of type {"public.zip-archive"})
set aList to extractArchive(aPath, aPassword) of me

on extractArchive(aPath, aPass)
  set oldArchive to ZZArchive’s archiveWithURL:(|NSURL|’s fileURLWithPath:aPath) |error|:(missing value)
  
set aList to oldArchive’s entries() as list
  
  
set outList to {}
  
  
repeat with i in aList
    set encF to i’s encrypted() as boolean
    
    
set aFileName to i’s fileName()
    
    
if (aFileName as string) does not start with "__MACOSX/" then
      set aExt to (aFileName’s pathExtension()) as string
      
set aUTI to my retFileFormatUTI(aExt)
      
set aData to (i’s newDataWithError:(missing value)) –Uncompressed raw data
      
      
if aData = missing value then
        set aData to (i’s newDataWithPassword:(aPass) |error|:(missing value))
        
if aData is equal to (missing value) then
          error "Internal archive error in extracting"
        end if
      end if
      
      
if aUTI = "public.plain-text" then
        –Plain Text
        
set aStr to (NSString’s alloc()’s initWithData:aData encoding:(NSUTF8StringEncoding)) as string
        
      else if aUTI = "com.apple.applescript.script" then
        –AppleScript .scpt file
        
set aScript to (OSAScript’s alloc()’s initWithCompiledData:aData |error|:(missing value))
        
set aStr to (aScript’s source()) as string
        
      else if (my filterUTIList({aUTI}, "public.image")) is not equal to {} then
        –Various Images
        
set aStr to (NSImage’s alloc()’s initWithData:aData)
        
      end if
      
      
set the end of outList to aStr
    end if
  end repeat
  
  
return outList
end extractArchive

on retFileFormatUTI(aExt as string)
  tell script "BridgePlus"
    load framework
    
return (current application’s SMSForder’s UTIForExtension:aExt) as string
  end tell
end retFileFormatUTI

–UTIリストが指定UTIに含まれているかどうか演算を行う
on filterUTIList(aUTIList, aUTIstr)
  set anArray to NSArray’s arrayWithArray:aUTIList
  
set aPred to NSPredicate’s predicateWithFormat_("SELF UTI-CONFORMS-TO %@", aUTIstr)
  
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
  
return bRes
end filterUTIList

★Click Here to Open This Script 

Posted in file OSA Text URL UTI | Tagged 10.11savvy 10.12savvy 10.13savvy NSArray NSImage NSPredicate NSString NSURL NSURLTypeIdentifierKey NSUTF8StringEncoding OSAScript ZZArchive | Leave a comment

easyJParse v3

Posted on 9月 27, 2018 by Takaaki Naganoya

簡易的な日本語テキストのParse(辞書なし)を行うAppleScriptです。

英語などの言語では、文章中の各単語の間にスペース(” “)を入れるようになっており、

My name is Takaaki Naganoya.

文章を単語ごとに分割することがきわめて容易です。

words of "My name is Takaaki Naganoya."
--> {"My", "name", "is", "Takaaki", "Naganoya"}

一方、日本語の文章において単語は続けて記述するため、

私の名前は長野谷です。

これを単語ごとに切り分けるのは大変です。そのため、単語の辞書を手掛かりに文章中の単語を切り分けるのが普通です。

辞書を使って単語単位の切り分けを行う日本語形態素解析器

日本語テキストを単語(形態素)ごとに区分けするソフトウェアは日本語形態素解析器と呼ばれます。Chasen、Juman、MeCabなどが有名です。形態素解析のための巨大な辞書を用いて、地名ぐらいの固有名詞なら問題なくParseできることが普通です。各単語がどの品詞なのか、活用形はどうなっているかといった文法的な情報も管理しています。

たとえば、ApitoreのREST API経由でKuromojiを呼び出して形態素解析を行うと、

"警告音「Basso」を最大音量で鳴らす"
-->{{startTime:"1538006762864", tokens:{{partOfSpeechLevel1:"名詞", baseForm:"警告", pronunciation:"ケイコク", position:0, partOfSpeechLevel3:"*", reading:"ケイコク", surface:"警告", known:true, allFeatures:"名詞,サ変接続,*,*,*,*,警告,ケイコク,ケイコク", conjugationType:"*", partOfSpeechLevel2:"サ変接続", conjugationForm:"*", allFeaturesArray:{"名詞", "サ変接続", "*", "*", "*", "*", "警告", "ケイコク", "ケイコク"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"音", pronunciation:"オン", position:2, partOfSpeechLevel3:"一般", reading:"オン", surface:"音", known:true, allFeatures:"名詞,接尾,一般,*,*,*,音,オン,オン", conjugationType:"*", partOfSpeechLevel2:"接尾", conjugationForm:"*", allFeaturesArray:{"名詞", "接尾", "一般", "*", "*", "*", "音", "オン", "オン"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"記号", baseForm:"「", pronunciation:"「", position:3, partOfSpeechLevel3:"*", reading:"「", surface:"「", known:true, allFeatures:"記号,括弧開,*,*,*,*,「,「,「", conjugationType:"*", partOfSpeechLevel2:"括弧開", conjugationForm:"*", allFeaturesArray:{"記号", "括弧開", "*", "*", "*", "*", "「", "「", "「"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"Basso", pronunciation:"バッソ", position:4, partOfSpeechLevel3:"一般", reading:"バッソ", surface:"Basso", known:true, allFeatures:"名詞,固有名詞,一般,*,*,*,Basso,バッソ,バッソ", conjugationType:"*", partOfSpeechLevel2:"固有名詞", conjugationForm:"*", allFeaturesArray:{"名詞", "固有名詞", "一般", "*", "*", "*", "Basso", "バッソ", "バッソ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"記号", baseForm:"」", pronunciation:"」", position:9, partOfSpeechLevel3:"*", reading:"」", surface:"」", known:true, allFeatures:"記号,括弧閉,*,*,*,*,」,」,」", conjugationType:"*", partOfSpeechLevel2:"括弧閉", conjugationForm:"*", allFeaturesArray:{"記号", "括弧閉", "*", "*", "*", "*", "」", "」", "」"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"助詞", baseForm:"を", pronunciation:"ヲ", position:10, partOfSpeechLevel3:"一般", reading:"ヲ", surface:"を", known:true, allFeatures:"助詞,格助詞,一般,*,*,*,を,ヲ,ヲ", conjugationType:"*", partOfSpeechLevel2:"格助詞", conjugationForm:"*", allFeaturesArray:{"助詞", "格助詞", "一般", "*", "*", "*", "を", "ヲ", "ヲ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"最大", pronunciation:"サイダイ", position:11, partOfSpeechLevel3:"*", reading:"サイダイ", surface:"最大", known:true, allFeatures:"名詞,一般,*,*,*,*,最大,サイダイ,サイダイ", conjugationType:"*", partOfSpeechLevel2:"一般", conjugationForm:"*", allFeaturesArray:{"名詞", "一般", "*", "*", "*", "*", "最大", "サイダイ", "サイダイ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"音量", pronunciation:"オンリョー", position:13, partOfSpeechLevel3:"*", reading:"オンリョウ", surface:"音量", known:true, allFeatures:"名詞,一般,*,*,*,*,音量,オンリョウ,オンリョー", conjugationType:"*", partOfSpeechLevel2:"一般", conjugationForm:"*", allFeaturesArray:{"名詞", "一般", "*", "*", "*", "*", "音量", "オンリョウ", "オンリョー"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"助詞", baseForm:"で", pronunciation:"デ", position:15, partOfSpeechLevel3:"一般", reading:"デ", surface:"で", known:true, allFeatures:"助詞,格助詞,一般,*,*,*,で,デ,デ", conjugationType:"*", partOfSpeechLevel2:"格助詞", conjugationForm:"*", allFeaturesArray:{"助詞", "格助詞", "一般", "*", "*", "*", "で", "デ", "デ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"動詞", baseForm:"鳴らす", pronunciation:"ナラス", position:16, partOfSpeechLevel3:"*", reading:"ナラス", surface:"鳴らす", known:true, allFeatures:"動詞,自立,*,*,五段・サ行,基本形,鳴らす,ナラス,ナラス", conjugationType:"五段・サ行", partOfSpeechLevel2:"自立", conjugationForm:"基本形", allFeaturesArray:{"動詞", "自立", "*", "*", "五段・サ行", "基本形", "鳴らす", "ナラス", "ナラス"}, partOfSpeechLevel4:"*"}}, endTime:"1538006762864", |log|:"", processTime:"0"}}

のようになります。これらのデータのうち、surface項目を抽出すると、

--> {"警告", "音", "「", "Basso", "」", "を", "最大", "音量", "で", "鳴らす", "。"}

となります。

辞書を使わずにトリッキーな方法で単語単位の切り分けを行う日本語パーサー

一方で、これらの日本語形態素解析器ほどの大規模なデータや機能が必要ない場合もあります。形態素解析のための辞書を持たず、単にそれっぽく単語ごとに区切ることができればよいという、「割り切った用途」に用いるもので、便宜上「日本語パーサー」と呼びます。単語っぽいものに分割することが目的であり、品詞のデータなどは取得できないのが普通です。

この種類のソフトウェアは、工藤 拓さんのTinySegmenterがあり、これをObjective-Cに移植したSuper compact Japanese tokenizer 「Tiny Segmenter」をCocoa Framework化してAppleScriptから呼び出し、テストしています。正規表現を用いて助詞などをピックアップして、それを手掛かりに単語切り分けを行うもので、そのサイズからは想像できないぐらいまっとうに単語に切り分けてくれます。

このTiny Segmenter(Objective-C版)をコマンド解釈用に使ってみたのですが、

--> {"警告音", "「Basso", "」", "を", "最大", "音量", "で", "鳴ら", "す"}

記号などがきちんと分離されなかったため、いまひとつ。自分でコマンド解釈用のParserを作ってみることにしました。

words ofの不完全さを補う簡易日本語パーサーeasyJParse

AppleScriptの「words of」は、前述のように英文であればスペースを区切り子として、文章の単語への分解を行ってくれます。

一方、日本語テキストに対して「words of」で単語分解処理を行うと、ながらく「文字種別の切り替え箇所で区切る」という気の狂ったような使えない処理が行われていました。その無意味さと使えなさをAppleのエンジニアにことあるごとに説明してきたのですが、一向に理解されず、相手にされてきませんでした。

# 冗談抜きで、Appleのエンジニアとは「戦いの歴史」しかありません。そして、そうして戦って勝ち取っていかないと機能の改善もバグの修正も何もないのであります(本当)

風向きが変わってきたのは、OS X 10.6のころ。このころから日本語テキストのwords ofの実行結果が形態素解析を行なっているような気がする動作を行うようになっており、何かに使えるような気がするものの……

 words of "警告音「Basso」を最大音量で鳴らす。"
 --> {"警告", "音", "Basso", "を", "最大", "音量", "で", "鳴らす"}

なぜか記号類などをすべて無視してしまうので、いまひとつ実用性がありませんでした。

そこで、基本的にはこの「words of」の演算結果を活かしつつ、オリジナルの文章と比較を行なって、欠損した記号類を補うことで簡易日本語parserとして利用できるのでは? と考えました。

set aTargName to "警告音「Basso」を最大音量で鳴らす。"
set aList to parseJ(aTargName) of me
--> {"警告", "音", "「", "Basso", "」", "を", "最大", "音量", "で", "鳴らす", "。"}

実際に作ってテストしてみたところ、自分が必要なコマンド解析ぐらいの目的には十分に使えることがわかりました。むしろ、単語切り分けについてはKuromojiと同じ結果が得られています。

しかも、辞書を持たないためコンパクトであり、実行速度もたいへんに高速で、このeasyJParseを組み込んだプログラムはREST APIの日本語形態素解析器を呼んだバージョンよりも明らかに高速化され、ネットワーク接続のない環境でも実行可能になりました。いいことづくめです。

easyJParseの制約事項

なお、easyJParseはすでに文章単位で分割されたテキストをコマンド解釈用に分解するため「だけ」に作ったものであり、長文を文章ごとに分割する機能は持っていません。別のプログラムやルーチンで文章ごとに分割してからeasyJParseで処理してください。

easyJParseは、日本語ユーザー環境における日本語テキストに対する「words of」の演算結果を利用しており、言語環境が日本語に設定していない環境で同様に演算できることは保証していません。
→ 一応、英語ユーザー環境で実行してみたら期待どおりの動作を行いました

当然のことながら、macOS専用です。一部Cocoaの機能を呼び出しているため、macOS 10.10以降で動きます(10.10では動作確認していませんけれども)。

AppleScript名:easyJParse v3
— Created 2018-09-26 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.5" — El Capitan (10.11) or later
use framework "Foundation"
use scripting additions

property NSArray : a reference to current application’s NSArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor

set aTargName to "警告音「Basso」を最大音量で鳴らす。"
set aList to parseJ(aTargName) of me
–> {"警告", "音", "「", "Basso", "」", "を", "最大", "音量", "で", "鳴らす", "。"}

–set aTargName to "JPEGファイルを50%にリサイズして、デスクトップの「AAA」フォルダに出力"
–set aList to parseJ(aTargName) of me
–> {"JPEG", "ファイル", "を", "50", "%", "に", "リサイズ", "し", "て", "、", "デスクトップ", "の", "「", "AAA", "」", "フォルダ", "に", "出力"}

on parseJ(aTargStr as string)
  copy aTargStr to tStr
  
  
set cList to characters of tStr
  
set wList to words of tStr
  
  
set cLen to length of cList
  
  
set w2List to {}
  
set w3List to {}
  
set aCount to 0
  
  
set lastPos to 0
  
  
repeat with i in wList
    set j to contents of i
    
    
set anOffset to offset of j in tStr
    
    
if anOffset is not equal to 1 then
      set aChar to character (lastPos + 1) of aTargStr
      
      
set the end of w3List to {wordList:aChar, characterList:{aChar}, startPos:(lastPos + 1), endPos:(lastPos + 1)}
    end if
    
    
set aLen to length of j
    
    
set w2List to w2List & (characters of j)
    
set startPointer to (anOffset + aCount)
    
set endPointer to (anOffset + aCount + aLen – 1)
    
    
set the end of w3List to {wordList:j, characterList:(characters of j), startPos:startPointer, endPos:endPointer}
    
    
set trimStart to (anOffset + aLen)
    
    
if trimStart > (length of tStr) then
      set trimStart to 1
    end if
    
    
set tStr to text trimStart thru -1 of tStr
    
    
set aCount to aCount + anOffset + aLen – 1
    
copy endPointer to lastPos
  end repeat
  
  
–句読点など。文末の処理
  
if endPointer is not equal to cLen then
    set the end of w3List to {wordList:tStr, characterList:(characters of tStr), startPos:(lastPos + aCount), endPos:aLen}
  end if
  
  
set bArray to sortRecListByLabel((w3List), "startPos", true) of me
  
set cArray to (bArray’s valueForKeyPath:"wordList") as list
  
  
return cArray
end parseJ

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as string, ascendF as boolean)
  set aArray to NSArray’s arrayWithArray:aRecList
  
set sortDesc to NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
return sortedArray
end sortRecListByLabel

★Click Here to Open This Script 

Posted in Natural Language Processing Record Sort Text | Tagged 10.11savvy 10.12savvy 10.13savvy NSArray NSSortDescriptor | 7 Comments

offset of list in list

Posted on 9月 22, 2018 by Takaaki Naganoya

標準命令「offset of ① in ②」を拡張して、本来はテキスト同士の処理であったものを、リスト同士で処理するようにしたAppleScriptです。

サポートしているのは4つのパターンです。

(1)offset of string1 in string2

標準パターンですが、例によって標準命令よりも2.5倍速に高速化してあります。

(2)offset of list1 in list2

想定ターゲットのパターンです。1D Listと1D Listのoffsetを計算します。

(3)offset of list1 in string2

(2)を指定しようとして間違ったケースです。

(4)offset of string1 in list2

(2)を指定しようとして間違ったケース(2)です。ただし、成立する組み合わせがほとんどないので、エラーにしてもいいかもしれません。

1D List同士のoffset演算は普段であれば必要ないものですが、いざ必要になるとどこにも見つからなかったので、やむを得ず作成しました。

ASOC(use framework “Foundation”宣言時)では、そのままではoffset命令がだましにくくなるので(パラメータが文字列ではないとしてエラーになる)、using terms from scripting additionsで囲う必要がありました。

左から順に1番目:OLD Style AppleScript環境で、offset命令をのっとってstring/listともに処理できるようにした状態。構文確認/実行ともに問題なし。左から2番目:ASOC環境にした。左から3番目:ASOC環境にすると実行時にパラメータの型チェックでエラーになった。左から4番目:ASOC環境でもusing terms from句でoffsetコマンド実行部を囲むと構文確認/実行時にエラーにはならなくなった

AppleScript名:offset of list in list
— Created 2018-09-18 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

–case 1
using terms from scripting additions –ここ、AppleScriptObjC(use framework "Foundation"宣言時)では必要になる
  set bRes to offset of "a" in "bcdefa"
end using terms from
–> 6

–case 2 (The target case)
set aTargList to {"Safari", "の", "メニュー", "で", "「", "ファイル", "」", ">", "「", "PDF", "を", "書き出す", "」", "を", "実行"}
set bList to {"の", "メニュー", "で"}
using terms from scripting additions
  set aRes to offset of bList in aTargList
end using terms from
–> 2

–case 3 (Illegular case)
set aTargList to {"Safari", "の", "メニュー", "で", "「", "ファイル", "」", ">", "「", "PDF", "を", "書き出す", "」", "を", "実行"}
set bList to "で"
using terms from scripting additions
  set aRes to offset of bList in aTargList
end using terms from
–> 4

–case 4 (Illegular case)
set aTargList to "で"
set bList to {"で"}
using terms from scripting additions
  set aRes to offset of bList in aTargList
end using terms from
—> 1

on offset of bArg in anArg
  set aClass to class of anArg
  
set bClass to class of bArg
  
  
if {aClass, bClass} = {text, text} then –case 1
    return getOffset(anArg, bArg) of me
  else if {aClass, bClass} = {list, list} then –case 2 (The target case)
    return execOffsetList(bArg, anArg) of me
  else if {aClass, bClass} = {text, list} then –case 3 (Illegular case)
    return execOffsetList(bArg, {anArg}) of me
  else if {aClass, bClass} = {list, text} then –case 4 (Illegular case)
    return execOffsetList({bArg}, anArg) of me
  end if
end offset

–1D List同士のoffset演算を行うルーチンの本体
on execOffsetList(aList as list, bList as list)
  set resList to {}
  
repeat with i in aList
    set j to contents of i
    
set aCount to 1
    
    
repeat with ii in bList
      set jj to contents of ii
      
if jj = j then
        set the end of resList to aCount
        
exit repeat
      end if
      
set aCount to aCount + 1
    end repeat
  end repeat
  
  
–見つかったItem No.が連続値かどうかチェック
  
set sRes to chkSequential(resList) of me
  
if sRes = true then
    return contents of first item of resList
  else
    return false
  end if
end execOffsetList

–与えられた1D Listが連続値かどうかをチェックする
on chkSequential(aList)
  if length of aList = 1 then return true
  
  
set aFirst to first item of aList
  
set aList to rest of aList
  
  
repeat with i in aList
    set j to contents of i
    
if j is not equal to (aFirst + 1) then
      return false
    end if
    
copy j to aFirst
  end repeat
  
  
return true
end chkSequential

–テキスト同士のoffset ofを(2.5x fasterで)実行する
on getOffset(str, searchStr)
  set d to divideBy(str, searchStr)
  
if (count d) is less than 2 then return 0
  
return (length of item 1 of d) + 1
end getOffset

on divideBy(str, separator)
  set delSave to AppleScript’s text item delimiters
  
set the AppleScript’s text item delimiters to separator
  
set strItems to every text item of str
  
set the AppleScript’s text item delimiters to delSave
  
return strItems
end divideBy

★Click Here to Open This Script 

Posted in list | Tagged 10.11savvy 10.12savvy 10.13savvy | Leave a comment

GUI Scripting的なUI Elementへの参照から所属するアプリケーション名を取得する

Posted on 9月 17, 2018 by Takaaki Naganoya

GUI Scripting的なUI Elementへの参照をもとに、そのUI Elementが所属するアプリケーション名を取得するAppleScriptです。

こんな、Safariの「ファイル」メニューの中にあるメニュー項目、

menu item “PDFとして書き出す…” of menu “ファイル” of menu bar item “ファイル” of menu bar 1 of application process “Safari”

があったとして、このオブジェクトが所属しているアプリケーション名を取得したいというケースがありました。上記の例では「Safari」がそれに該当します。

GUI Scripting的なお約束として、オブジェクト階層が上から下に向かって取得する場合には「entire contents」でまとめて取得できるのですが、下から上に向かってparentで取得できるわけでもなく、アプリケーションプロセス名をさかのぼって取得するといった処理ができていませんでした。

AppleScriptの「仕様」的には不可能です。

でも、不可能であることさえわかれば(正攻法では不可能なので)、いつものトリッキーかつ邪道な手口でなんとかなりそうです。

そうです、エラートラップ中で無理やりエラーを起こして、そのエラーメッセージの情報から取得してしまおうという、いつものやつです。

というわけで、GUI Elementの情報から所属アプリケーション名を取得できました。

最近は、さらなる「卑劣な手段」を用意しているので、そちらで華麗かつ卑劣に処理してもよかったのですが、とりあえず手短にまとめたかったのでいつものやつで処理しました。

AppleScript名:GUI Scripting的なUI Elementへの参照から所属するアプリケーション名を取得する.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2018/09/17
—
–  Copyright © 2018 Piyomaru Software, All Rights Reserved
—

use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

tell application "System Events"
  set aRef to menu item "PDFとして書き出す…" of menu "ファイル" of menu bar item "ファイル" of menu bar 1 of application process "Safari"
end tell

set appName to retRootAppProcessNameFromGUIElementRef(aRef) of me
–> "Safari"

–GUI Scripting的なUI Elementへの参照から、rootのアプリケーションプロセス名を取得する
on retRootAppProcessNameFromGUIElementRef(aRef)
  try
    set aStr to aRef as string –force cause error
  on error erM
    set offsetA to offset of " of application \"System Events\"" in erM
    
set offsetB to offset of "«class pcap» " in erM
    
set appName to text (offsetB + (length of "«class pcap» ") + 1) thru (offsetA – 2) of erM
    
return appName as string
  end try
end retRootAppProcessNameFromGUIElementRef

★Click Here to Open This Script 

Posted in GUI GUI Scripting System | Tagged 10.11savvy 10.12savvy 10.13savvy System Events | Leave a comment

iCloud Driveフォルダへのパスを求める

Posted on 9月 16, 2018 by Takaaki Naganoya

iCloud Driveフォルダへのパスを求めるAppleScriptです。

ただ、これほどわかりにくいものも珍しいことでしょう。自分も避けておきたい仕様です。

パスについての仕様はあり、ファイルの読み書きなどはできそうですが、求めたパスをFinder上で指定すると不思議なことになります。

Finderのサイドバーから「iCloud Drive」アイコンをクリックすると、

のような表示になります。このときのFinder Windowのtargetを取得すると、~/Library/Mobile Documentsが返ってきます(形式はFinder Objectですけれども)。ただ、これと同じことをAppleScript側から行なっても、同じ結果が得られないというジレンマがあります。

~/Library/Mobile Documents

AppleScript名:iCloud Drivegフォルダへのパスを求める
set iCloudPath to ((path to library folder from user domain as string)) & "Mobile Documents:"
tell application "Finder"
  make new Finder window
  
set target of Finder window 1 to (iCloudPath as alias)
end tell

★Click Here to Open This Script 

これは、~/Library/Mobile Documentsをオープンするものですが、これを実行すると、

のようになります。

~/Library/Mobile Documents/com~apple~CloudDocs

これみたいですね。

AppleScript名:iCloud Drivegフォルダへのパスを求める(com~apple~CloudDocs フォルダ)
set iCloudPath to ((path to library folder from user domain as string)) & "Mobile Documents:com~apple~CloudDocs"
tell application "Finder"
  make new Finder window
  
set target of Finder window 1 to (iCloudPath as alias)
end tell

★Click Here to Open This Script 

これは、~/Library/iCloud Drive/com~apple~CloudDocsをオープンするものですが、これを実行すると、

となります。

Terminal上で当該フォルダを表示させてフォルダ名を再度確認してみたところ、きっとこれです。

Posted in File path System | Tagged 10.11savvy 10.12savvy Finder | Leave a comment

CotEditor 3.5でウィンドウの透明度コントロール機能がなくなる

Posted on 9月 15, 2018 by Takaaki Naganoya

CotEditor v3.5で機能に変更があり、「CotEditorでウィンドウの透明度を連続的に変更する」のようなScriptが実行できなくなりました。AppleScript側からウィンドウの透明度をコントロールするための予約語「view opacity」が廃止されました。

予約語が廃止されたときにどういう状態になるのか、という事例として掲載しておきます。

保存ずみの同Scriptをオープンすると、

のような表示になりました。この状態でコンパイル(構文確認)を行うと、

のようにエラーになります。

CotEditorのAppleScript用語辞書をHTMLに書き出してdiffで差分を計算すると、

たしかにview opacityが削除されていることが確認できます。

ただし、アプリケーションの機能としてウィンドウの透明度設定は行えるので、エディタとしての使い勝手は(あまり)変わりません。AppleScriptから透明度をコントロールできないと困ることがあるかと考えると、とくに困ることはありません。

Posted in 未分類 | Tagged 10.11savvy 10.12savvy 10.13savvy CotEditor | Leave a comment

iTunesライブラリの曲名をすべて取得

Posted on 9月 9, 2018 by Takaaki Naganoya

iTunesLibrary.framework経由でiTunesライブラリの曲名をすべて取得するAppleScriptです。

8,500曲の曲名を取得するのにおおよそ1.2秒程度です。

AppleScript名:iTunesライブラリの曲名をすべて取得
— Created 2016-11-02 by Takaaki Naganoya
— 2016 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "iTunesLibrary"

set library to current application’s ITLibrary’s libraryWithAPIVersion:"1.0" |error|:(missing value)
if library is equal to missing value then return

set playLists to library’s allPlaylists()
set gArray to (library’s allMediaItems()’s title) as list

★Click Here to Open This Script 

Posted in list | Tagged 10.11savvy 10.12savvy 10.13savvy ITLibrary iTunes | Leave a comment

Stickiesからのデータ取り出し v2

Posted on 9月 6, 2018 by Takaaki Naganoya

スティッキーズ(Stickies.app)のデータをまとめて引っこ抜いてListに入れるAppleScriptです。色情報、1行目の内容をタイトルとみなしてタイトル情報も付加して出力するようにしてみました。

オープンソースのStickiesViewerをFramework化したstickiesLib.frameworkを経由してデータを読み出します。データベースファイルから情報を直接読み取るので、Stickies.appが起動している必要はありません。

–> Download stickiesLib.framework(To ~/Library/Frameworks/)

–> {creationDate:date “2006年1月15日日曜日 11:02:32″, aTitle:”WX-310Kの画像サイズ”, modificationDate:date “2006年1月15日日曜日 11:03:09″, aContents:”WX-310Kの画像サイズ
ケータイ:120×160
壁紙:240×320″, colorName:”Yellow”}

StickiesDatabaseからまとめて一括で全部抽出するので、個別に何かのIDで指定して抽出するといった動作にはなりません。

93個のスティッキーズ書類(93個のウィンドウが表示されている状態、1つのDBを指定)から、全データを取得して本AppleScriptで0.1秒程度です。

AppleScript名:Stickiesからのデータ取り出し v2.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2018/09/05
—
–  Copyright © 2018 Piyomaru Software, All Rights Reserved
—

use AppleScript version "2.5" — El Capitan (10.11) or later
use framework "Foundation"
use framework "AppKit"
use framework "stickiesLib"
use scripting additions

property NSColor : a reference to current application’s NSColor
property |NSURL| : a reference to current application’s |NSURL|
property SVReader : a reference to current application’s SVReader
property NSFileManager : a reference to current application’s NSFileManager
property NSMutableArray : a reference to current application’s NSMutableArray

set sRes to retStickiesData() of me
–>
(*
{{creationDate:date "2006年1月15日日曜日 11:02:32", aTitle:"WX-310Kの画像サイズ", modificationDate:date "2006年1月15日日曜日 11:03:09", aContents:"WX-310Kの画像サイズ
ケータイ:120×160
壁紙:240×320", colorName:"Yellow"}, ….}
*)

on retStickiesData()
  set dbPath to POSIX path of (path to library folder from user domain) & "StickiesDatabase"
  
set aExt to (NSFileManager’s defaultManager()’s fileExistsAtPath:dbPath) as boolean
  
  
if aExt = false then
    set dbPath to POSIX path of (choose file with prompt "Select StickiesDatabase file")
  end if
  
  
set aURL to |NSURL|’s fileURLWithPath:dbPath
  
set aStickDB to SVReader’s notesWithContentsOfURL:aURL
  
  
set aList to aStickDB as list
  
  
–スティッキーズの色データ
  
set cList to {{"Yellow", {254, 244, 156}}, {"Blue", {173, 244, 255}}, {"Green", {178, 255, 161}}, {"Pink", {255, 199, 199}}, {"Purple", {182, 202, 255}}, {"Gray", {238, 238, 238}}}
  
  
  
set outArray to NSMutableArray’s new()
  
  
repeat with i in aList
    set anAttrStr to i’s attributedString()
    
set aStr to (anAttrStr’s |string|()) as string
    
    
set curTitle to first paragraph of aStr
    
    
set aColor to i’s |color|()
    
set cDate to i’s dateCreated() as date
    
set mDate to i’s dateModified() as date
    
    
set c1List to retColListFromNSColor(aColor, 255) of me
    
    
–Find Color Name
    
set hitF to false
    
repeat with ii in cList
      set {colName, colList} to ii
      
if c1List = colList then
        set hitF to true
        
exit repeat
      end if
    end repeat
    
    
if hitF = false then error "Color name did not hit"
    
    
set curName to colName
    (
outArray’s addObject:{creationDate:cDate, modificationDate:mDate, aContents:aStr, colorName:curName, aTitle:curTitle})
    
  end repeat
  
  
return outArray as list
end retStickiesData

on retColorFromRGBnum(r, g, b)
  return (NSColor’s colorWithCalibratedRed:r / 255.0 green:g / 255.0 blue:b / 255.0 alpha:1.0)
end retColorFromRGBnum

–NSColorからrgbの8bitの値を取り出す
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
  
return {aRed, aGreen, aBlue}
end retColListFromNSColor

★Click Here to Open This Script 

Posted in Color File path RTF System | Tagged 10.11savvy 10.12savvy 10.13savvy NSColor NSFileManager NSMutableArray NSURL Stickies | Leave a comment

Stickiesからのデータ取り出し

Posted on 9月 5, 2018 by Takaaki Naganoya

スティッキーズ(Stickies.app)のデータをまとめて引っこ抜いてListに入れるAppleScriptです。

オープンソースのStickiesViewerをFramework化したstickiesLib.frameworkを経由してデータを読み出します。データベースファイルから情報を直接読み取るので、Stickeys.appが起動している必要はありません。

–> Download stickiesLib.framework(To ~/Library/Frameworks/)

昔はいろいろ苦労したような気がするのですが、あっさりデータを取り出せて驚きます。

色情報も取り出していますが、色名を取り出すにはややScriptがおおげさになるので、とりあえず作成日、修正日、テキストを取り出してみました。

AppleScript名:Stickiesからのデータ取り出し v1.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2018/09/05
—
–  Copyright © 2018 Piyomaru Software, All Rights Reserved
—

use AppleScript version "2.4" — Yosemite (10.10) or later
use scripting additions
use framework "Foundation"
use framework "stickiesLib" –https://github.com/kainjow/StickiesViewer

set aFile to POSIX path of (choose file default location (path to library folder from user domain) with prompt "Select StickiesDatabase file")

set aURL to current application’s |NSURL|’s fileURLWithPath:aFile
set aList to (current application’s SVReader’s notesWithContentsOfURL:aURL) as list

set outList to {}

repeat with i in aList
  set anAttrStr to i’s attributedString()
  
set aStr to (anAttrStr’s |string|()) as string
  
  
set aColor to i’s |color|()
  
set cDate to i’s dateCreated() as date
  
set mDate to i’s dateModified() as date
  
  
set the end of outList to {creationDate:cDate, modificationDate:mDate, aContents:aStr}
end repeat

return outList

★Click Here to Open This Script 

Posted in RTF System | Tagged 10.11savvy 10.12savvy 10.13savvy NSURL Stickies | 3 Comments

SMCkitで各種センサー値を取得する

Posted on 9月 4, 2018 by Takaaki Naganoya

MCWrapperをCocoa Framework化してAppleScriptから呼びやすいように書き換えた「SMCKit.framework」を呼び出して、CPUの温度や放熱ファンの回転数を取得するAppleScriptのパラメータを追跡調査したものです。

最近、夏の暑さ対策のためにMacのファン回転を操作&各種センサー値を取得する「TG Pro」を使い始め、MacBook Proの下にCooling Fanを設置し、人間を冷やすためにサーキュレーター(「戦場の絆」のPOD内によく設置されている一品)を設置しています。


▲このプログラムを書いたころ、ちょうど鹿児島の海沿いの東向きの部屋にいたので、夏場は暑さで人間よりも先にコンピュータ(Mac)が音を上げていました(なつかしい) そして、桜島が噴火を起こすとドアとか窓ガラスが衝撃波でビリビリと震えていました。激しい噴火だと、ドアを蹴られたような音がするほど(海沿いだったもので)

ちょっと前までは、このサーキュレータでMacBook Proに送風していました。これだと人間が冷えなさすぎなので、近くのPC DepotでノートPC用のクーリングファンを買ってきた次第です。


▲TG Pro(左)、HWMonitor(右)

オープンソースのHWMonitor(HWSensorsに含まれる実行バイナリ)の中身を見てみたところ、SMCにはもっといろいろパラメータを指定できることがわかりました。ざっと調べて、その数493個!

ただし、実行するハードウェアの構成によって、取得できるセンサーも変わってきます。デスクトップ型のMac miniやMac Proに明度センサー(アンビエント・センサー)やバッテリー関連のセンサーはついていないですし(Mac Pro 2013には明度センサーが付いていたかもしれない)、ディスクリートGPUを搭載していないMacBook AirやMac miniでディスクリートGPU関連のセンサーから値を取ることはできません。

–> Download SMCKit.framework(To ~/Library/Frameworks/)

実際には、CPU/GPU関連の温度センサーの値だけ取得できれば、Cocoa呼び出しで高速に処理しすぎてCPUの温度が上がりすぎた、というケースにも対応できるはずです(警告を出すとかいった程度で、ファンの回転数を上げるといった処理はまだできていません)。

長時間、ファイルを大量に処理するような場合には、マシンの種別を取得して、ノートであればあらかじめ放熱に気をつけるようにメッセージを表示し、処理中にCPU温度が上がりすぎた場合には何らかの対策を行うといったところでしょうか(サーマルスロットリングが起こらない程度に様子を見る、とか?)。

AppleScript名:SMCkitで各種センサー値を取得する
— 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()

–CPU Proximity
set a0Res to (smc’s readKeyAsString:"TC0P") as string as number
–> 53.875 (℃)

–CPU
set a1Res to (smc’s readKeyAsString:"TCXC") as string as number
–> 69.984(℃)

–PECI SA
set a2Res to (smc’s readKeyAsString:"TCSA") as string as number
–> 68(℃)

–PECI GPU
set a3Res to (smc’s readKeyAsString:"TCGC") as string as number
–> 69(℃)

–PCH Die
set a4Res to (smc’s readKeyAsString:"TPCD") as string as number
–> 71(℃)

–Memory Module, Proximity
set a5Res to (smc’s readKeyAsString:"TM0P") as string as number
–> 56.75(℃)

–GPU Die
set a6Res to (smc’s readKeyAsString:"TG0D") as string as number
–> 65.375(℃)

–GPU Proximity
set a7Res to (smc’s readKeyAsString:"TG0P") as string as number
–> 59.5(℃)

–Airport Proximity
set a8Res to (smc’s readKeyAsString:"TW0P") as string as number
–> 52(℃)

–Battery
set a9Res to (smc’s readKeyAsString:"TB0T") as string as number
–> 38.699

–Battery
set a10Res to (smc’s readKeyAsString:"TB1T") as string as number
–> 38.699

–Battery
set a11Res to (smc’s readKeyAsString:"TB2T") as string as number
–> 36.5

–Heatpipe
set a12Res to (smc’s readKeyAsString:"Th1H") as string as number
–> 46.75(℃)

–Heatpipe Incoming Air
set a12Res to (smc’s readKeyAsString:"Ts0P") as string as number
–> 40(℃)

–CPU VRM
set a13Res to (smc’s readKeyAsString:"VC0C") as string as number
–> 0.86963

–CPU GFX
–set a13Res to (smc’s readKeyAsString:"VC0G") as list of string or string
–> 0.86963

–CPU Core
set a13Res to (smc’s readKeyAsString:"IC0C") as string as number
–> 13.57

–CPU Rail
set a14Res to (smc’s readKeyAsString:"IC0R") as string as number
–> 1.0586

–GPU
set a15Res to (smc’s readKeyAsString:"IG0C") as string as number
–> 7.898

–IM:021C|Memory Module %X
set a16Res to (smc’s readKeyAsString:"IM0C") as string as number
–> 0.3252

–Battery
–set a16Res to (smc’s readKeyAsString:"IBAC") as list of string or string
–> 0.3252

–Mainboard S0 Rail
set a17Res to (smc’s readKeyAsString:"ID0R") as string as number
–> 2.29321

–CPU Core
set a18Res to (smc’s readKeyAsString:"PC0C") as string as number
–> 10.39 (A)

–CPU Package Cores
set a18Res to (smc’s readKeyAsString:"PCPC") as string as number
–> 5.75 (W)

–CPU Package Graphics
set a18Res to (smc’s readKeyAsString:"PCPG") as string as number
–> 0.094

–CPU Package Total
set a19Res to (smc’s readKeyAsString:"PCPT") as string as number
–> 12.07 (W)

–PM:041C|Memory Module %X
set a20Res to (smc’s readKeyAsString:"PM0R") as string as number
–> 0.441

–Battery Rail
set a21Res to (smc’s readKeyAsString:"PBLC") as string as number
–> 0.012

–Mainboard S0 Rail
set a21Res to (smc’s readKeyAsString:"PC0R") as string as number
–> 14.617

–System Total
set a21Res to (smc’s readKeyAsString:"PSTR") as string as number
–> 41.234

★Click Here to Open This Script 

Posted in System | Tagged 10.11savvy 10.12savvy 10.13savvy | Leave a comment

PSPDFkitのじっけん

Posted on 8月 30, 2018 by Takaaki Naganoya

サードパーティの有償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

★Click Here to Open This Script 

この「最前面で実行」(メインスレッドで実行)する機能が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:

★Click Here to Open This Script 

Posted in file PDF | Tagged 10.11savvy 10.12savvy 10.13savvy | Leave a comment

画像ファイルを読み込んでクリップボードに設定する

Posted on 8月 24, 2018 by Takaaki Naganoya

画像ファイルを読み込んで、クリップボードに設定する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

★Click Here to Open This Script 

Posted in Clipboard file Image | Tagged 10.11savvy 10.12savvy 10.13savvy | Leave a comment

CPUの温度、放熱ファンの回転数を取得する

Posted on 8月 21, 2018 by Takaaki Naganoya

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

★Click Here to Open This Script 

Posted in System | Tagged 10.11savvy 10.12savvy 10.13savvy | 1 Comment

Terminal.appでコマンドを実行する

Posted on 8月 20, 2018 by Takaaki Naganoya

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

★Click Here to Open This Script 

Posted in shell script | Tagged 10.11savvy 10.12savvy 10.13savvy Terminal | Leave a comment

Skimで表示中のページ数を現在のNumbersのセルに入れて、選択セルを下にひとつ移動

Posted on 8月 18, 2018 by Takaaki Naganoya

Skimで現在表示中のPDFの現在のページ番号を、Numbers書類で選択中のセルに入れて、Numbersの選択中のセルを1つ下に移動させるAppleScriptです。

–> Watch Demo Movie

作業をちょっとだけ楽にするために作成した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

★Click Here to Open This Script 

Posted in PDF | Tagged 10.11savvy 10.12savvy 10.13savvy NSString Numbers Skim | Leave a comment

Keynoteで一番左上のshapeオブジェクトに他のshapeのサイズを合わせる

Posted on 8月 17, 2018 by Takaaki Naganoya

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

★Click Here to Open This Script 

Posted in list Sort | Tagged 10.11savvy 10.12savvy 10.13savvy Keynote | Leave a comment

Safariで表示中のWebページの最終更新日時を取得

Posted on 8月 17, 2018 by Takaaki Naganoya

指定の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

★Click Here to Open This Script 

だいたいは、これで手を打つでしょう。ただし、最近の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

★Click Here to Open This Script 

これも現在日時を返してくるパターンが多いですね。また、噂レベルではあるものの「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

★Click Here to Open This Script 

結果は3つとも変わりませんでした。Cocoa呼び出しするものも、作り置きしておいたサブルーチンを使いまわしただけなので、作るのに3分もかかっていません。

curlを呼び出すスタイル同様、Mac AppStore上で配布/販売するアプリケーションの中で処理するのもとくに問題はないのですが、httpによる通信を行うことを宣言しておかないとネットワーク接続ができません。

Safariでdo javascriptコマンドを実行するものは、最初にdo javascriptコマンドを実行する設定が必要。curlコマンドはまあそんなもんだろうかと。Cocoaの機能を呼び出す方法は、ここまでやってダメならあきらめがつくというところでしょうか。

Posted in Calendar Internet JavaScript URL | Tagged 10.11savvy 10.12savvy 10.13savvy NSDateFormatter NSLocale NSString NSURLConnection NSURLRequest Safari | Leave a comment

心配性のリネームプログラム

Posted on 8月 15, 2018 by Takaaki Naganoya

安全策の上に安全策を講じた、心配性のファイル名称変更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

★Click Here to Open This Script 

Posted in file File path | Tagged 10.11savvy 10.12savvy NSFileManager NSString NSUUID | Leave a comment

CSVデータを読み込んで表インタフェースで表示確認 v2

Posted on 8月 14, 2018 by Takaaki Naganoya

指定の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:

★Click Here to Open This Script 

Posted in file GUI Text | Tagged 10.11savvy 10.12savvy 10.13savvy NSScreen | Leave a comment

Post navigation

  • Older posts
  • Newer posts

電子書籍(PDF)をオンラインストアで販売中!

Google Search

Popular posts

  • 開発機としてM2 Mac miniが来たのでガチレビュー
  • macOS 15, Sequoia
  • 指定のWordファイルをPDFに書き出す
  • Pages本執筆中に、2つの書類モード切り替えに気がついた
  • Numbersで選択範囲のセルの前後の空白を削除
  • メキシカンハットの描画
  • Pixelmator Pro v3.6.4でAppleScriptからの操作時の挙動に違和感が
  • AdobeがInDesign v19.4からPOSIX pathを採用
  • Safariで「プロファイル」機能を使うとAppleScriptの処理に影響
  • AppleScriptによる並列処理
  • Cocoa Scripting Course 続刊計画
  • macOS 14.xでScript Menuの実行速度が大幅に下がるバグ
  • AppleScript入門③AppleScriptを使った「自動化」とは?
  • Keynote/Pagesで選択中の表カラムの幅を均等割
  • Keynote、Pages、Numbers Ver.14.0が登場
  • デフォルトインストールされたフォント名を取得するAppleScript
  • macOS 15 リモートApple Eventsにバグ?
  • AppleScript入門① AppleScriptってなんだろう?
  • Numbersで最前面の書類のすべてのシート上の表の行数を合計
  • macOS 14で変更になったOSバージョン取得APIの返り値

Tags

10.11savvy (1101) 10.12savvy (1242) 10.13savvy (1391) 10.14savvy (587) 10.15savvy (438) 11.0savvy (283) 12.0savvy (212) 13.0savvy (194) 14.0savvy (147) 15.0savvy (129) CotEditor (66) Finder (51) iTunes (19) Keynote (116) NSAlert (61) NSArray (51) NSBitmapImageRep (20) NSBundle (20) NSButton (34) NSColor (53) NSDictionary (28) NSFileManager (23) NSFont (21) NSImage (41) NSJSONSerialization (21) NSMutableArray (63) NSMutableDictionary (22) NSPredicate (36) NSRunningApplication (56) NSScreen (30) NSScrollView (22) NSString (119) NSURL (98) NSURLRequest (23) NSUTF8StringEncoding (30) NSView (33) NSWorkspace (20) Numbers (76) Pages (55) Safari (44) Script Editor (27) WKUserContentController (21) WKUserScript (20) WKWebView (23) WKWebViewConfiguration (22)

カテゴリー

  • 2D Bin Packing
  • 3D
  • AirDrop
  • AirPlay
  • Animation
  • AppleScript Application on Xcode
  • Beginner
  • Benchmark
  • beta
  • Bluetooth
  • Books
  • boolean
  • bounds
  • Bug
  • Calendar
  • call by reference
  • check sum
  • Clipboard
  • Cocoa-AppleScript Applet
  • Code Sign
  • Color
  • Custom Class
  • date
  • dialog
  • diff
  • drive
  • Droplet
  • exif
  • file
  • File path
  • filter
  • folder
  • Font
  • Font
  • GAME
  • geolocation
  • GUI
  • GUI Scripting
  • Hex
  • History
  • How To
  • iCloud
  • Icon
  • Image
  • Input Method
  • Internet
  • iOS App
  • JavaScript
  • JSON
  • JXA
  • Keychain
  • Keychain
  • Language
  • Library
  • list
  • Locale
  • Localize
  • Machine Learning
  • Map
  • Markdown
  • Menu
  • Metadata
  • MIDI
  • MIME
  • Natural Language Processing
  • Network
  • news
  • Noification
  • Notarization
  • Number
  • Object control
  • OCR
  • OSA
  • parallel processing
  • PDF
  • Peripheral
  • PRODUCTS
  • QR Code
  • Raw AppleEvent Code
  • Record
  • rectangle
  • recursive call
  • regexp
  • Release
  • Remote Control
  • Require Control-Command-R to run
  • REST API
  • Review
  • RTF
  • Sandbox
  • Screen Saver
  • Script Libraries
  • sdef
  • search
  • Security
  • selection
  • shell script
  • Shortcuts Workflow
  • Sort
  • Sound
  • Spellchecker
  • Spotlight
  • SVG
  • System
  • Tag
  • Telephony
  • Text
  • Text to Speech
  • timezone
  • Tools
  • Update
  • URL
  • UTI
  • Web Contents Control
  • WiFi
  • XML
  • XML-RPC
  • イベント(Event)
  • 未分類

アーカイブ

  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2024年2月
  • 2024年1月
  • 2023年12月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年2月
  • 2023年1月
  • 2022年12月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年10月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年12月
  • 2019年11月
  • 2019年10月
  • 2019年9月
  • 2019年8月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年11月
  • 2018年10月
  • 2018年9月
  • 2018年8月
  • 2018年7月
  • 2018年6月
  • 2018年5月
  • 2018年4月
  • 2018年3月
  • 2018年2月

https://piyomarusoft.booth.pm/items/301502

メタ情報

  • ログイン
  • 投稿フィード
  • コメントフィード
  • WordPress.org

Forum Posts

  • 人気のトピック
  • 返信がないトピック

メタ情報

  • ログイン
  • 投稿フィード
  • コメントフィード
  • WordPress.org
Proudly powered by WordPress
Theme: Flint by Star Verte LLC