Script Debugger上でオープン中の2つのAppleScript書類のうち、名称が同じハンドラ(サブルーチン)の内容同士を比較して、お互いに合っているかどうかをチェックするAppleScriptです。
アプリケーション書き出しして、Script Debuggerのスクリプトメニューに入れて実行します(AppleScript書類のままだと、Myriad Tables Lib内のフレームワークが呼べないため)。
▲本Scriptはスクリプトエディタの環境設定で行うAppleScriptの構文色分け設定色が「RGBカラー」でないとエラーになります。CMYKとかグレースケールカラーで指定して、そのままRGBに変換されずに保持される(CMYK色はアクセスしているうちにRGBに変換されるが、グレースケールは保持される)ため、色空間の判定を行ってRGB色に変換してから処理する必要があります(自分用メモ)
スクリプトエディタ上で設定する構文色分け設定をもとに、AppleScriptの構文要素を考慮したハンドラ一覧を作成し、2つのAppleScript書類の間に共通して存在するハンドラ名のハンドラ(サブルーチン)のAppleScriptソースを取得。インデント(tab)を削除したのちに比較を実行。
共通名称のハンドラすべてをチェックして、結果を表インタフェースで表示します。初版(v1)では実行するたびに1つのハンドラをチェックしていたのですが、あまりにかったるいので即座にループですべて調べてまとめて結果を表示するようにしました(v2)。
–> download executable archive (mainly for macOS 10.14 or later)
ただし、1つのAppleScript書類内にScript Object(JavaScriptでいうところのClosureみたいなもの)で論理分割された同名のハンドラが複数回登場するパターンは想定していません。複数検出するとエラーになります。
とくにScript Debuggerを対象にせずスクリプトエディタを対象にしてもよいのですが、些細な変更で実現できるため、興味のある方はやってみてください。あと、本Scriptは必要に迫られまくって作ったものなので、動作でおかしい箇所があったらぜひぜひご指摘を。
個人的には、ハンドラ名称を構文要素を考慮しつつ取得している一方で、ハンドラの内容は単なる文字列検索で取り出しているというあたりにアンバランスさを感じます。ただ、趣味のScriptではないので若干の手抜きを(^ー^;;
macOS 10.14.5上のスクリプトエディタで、構文色分け情報を何回設定しても「黒」になるという現象に遭遇したのですが、他のユーザー環境上での再現性については未知数であるため、まだレポートできない状況です。
→ その後、本ツールは強化されて、構文色分け設定にRGB以外の色が設定してあってもRGBに変換して処理する機能や、構文色分け設定を参照したコメント除去機能や、各種書式情報の取得を3倍高速化するカラーキャッシュなどの機能を実装したプログラムに(必要に迫られて)成長しました。
AppleScript名:Handle diff v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/07/02 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.5" — Sierra (10.12) or later (maybe 10.11 is OK but not confirmed) use framework "Foundation" use scripting additions use framework "OSAKit" use script "Myriad Tables Lib" version "1.0.9" –https://www.macosxautomation.com/applescript/apps/Script_Libs.html property NSArray : a reference to current application’s NSArray property NSString : a reference to current application’s NSString property OSAScript : a reference to current application’s OSAScript property NSPredicate : a reference to current application’s NSPredicate property NSDictionary : a reference to current application’s NSDictionary property NSUnarchiver : a reference to current application’s NSUnarchiver property NSCountedSet : a reference to current application’s NSCountedSet –構文色分け設定に重複色がないかチェック 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" –Script Debugger上でオープン中の2つのAppleScript書類のパスを取得 set dList to retTwoPathList() of me if dList = false then return copy dList to {d1Path, d2Path} –各AppleScript書類のハンドラ名称一覧を取得する set hList1 to retHandlerNamesFromAppleScriptSource(d1Path as alias) of me set hList2 to retHandlerNamesFromAppleScriptSource(d2Path as alias) of me –2つのAppleScript書類のハンドラの共通項目(名称のみ)を抽出する set resList to returnDuplicatesOnly(hList1 & hList2) of me if resList = {} then –共通のハンドラ(サブルーチン)が指定の2つのScriptに存在していなかった display dialog "There is no common handler between the scripts." buttons {"OK"} default button 1 return end if –各AppleScript書類のソースを取得して、指定のハンドラのテキストを取得する set s1Source to getASsourceFor(d1Path as alias) of me set s2Source to getASsourceFor(d2Path as alias) of me if (s1Source = false) or (s2Source = false) then display dialog "Error occured in getteing AS source" buttons {"OK"} default button 1 return end if set outList to {} repeat with i in resList set resHandler to contents of i set s1Source to extractHandlerSourceOnly(s1Source, resHandler) of me set s2Source to extractHandlerSourceOnly(s2Source, resHandler) of me set compareF to (s1Source is equal to s2Source) as boolean set the end of outList to {resHandler, compareF} end repeat –結果表示 tell me to activate display table with data outList with prompt "Handler contents compare results" column headings {"Handler name", "Match?"} with title "Handler Diff Results" –指定のハンドラの内容を抽出する(コメントぐらいは結果から削除してもいいような気もする) –Script Objectの使用は考慮していない。1つのAppleScript書類からScript Objectで論理分割された同名のハンドラが複数検出されるケースは想定していない on extractHandlerSourceOnly(wholeScript as string, handlerName as string) –インデント文字(Tab)をすべて削除 set targScript to repChar(wholeScript, tab, "") of me –"on"ではじまるハンドラと仮定して抽出 set handlerStr to extractStrFromTo(targScript, "on " & handlerName, "end " & handlerName) of me if handlerStr = false then –"to"ではじまるハンドラと仮定して抽出 set handlerStr to extractStrFromTo(targScript, "to " & handlerName, "end " & handlerName) of me if handlerStr = false then return false else return handlerStr end if end extractHandlerSourceOnly on retHandlerNamesFromAppleScriptSource(aFile) set aRec to getAttrRecFromASPath(aFile) of me set cList to getAppleScriptSourceColors() of me 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 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に来る場合に備える –"on"(ハンドラ宣言)の項目をリストアップ 文字と色で抽出 set thePred2 to NSPredicate’s predicateWithFormat_("stringVal == %@ && colorStr == %@ ", "on", ontoCoStr) set fArray to ((tmp1Array’s filteredArrayUsingPredicate:thePred2)’s valueForKeyPath:"itemIndex") as list –"to"(ハンドラ宣言ないしは代入対象指定) の項目をリストアップ文字と色で抽出 set thePred3 to NSPredicate’s predicateWithFormat_("stringVal == %@ && colorStr == %@ ", "to", ontoCoStr) set gArray to ((tmp1Array’s filteredArrayUsingPredicate:thePred3)’s valueForKeyPath:"itemIndex") as list set handlerList to {} –on ではじまるハンドラの抽出 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 as list)) else if (j + 2) = jj then set handlerStr to stringVal of (item (jj + 2) of (aRec as list)) end if if handlerStr is not in {"error", missing value} and handlerStr is not in handlerList then set the end of handlerList to handlerStr end if end repeat end repeat –to ではじまるハンドラの抽出 repeat with i in eArray –改行を含むテキストのアイテム番号リスト set j to (contents of i) as integer repeat with ii in gArray –"to"の項目リスト 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 as list)) else if (j + 2) = jj then set handlerStr to stringVal of (item (jj + 2) of (aRec as list)) end if if handlerStr is not in {"error", missing value} and handlerStr is not in handlerList then set the end of handlerList to handlerStr end if end repeat end repeat return handlerList end retHandlerNamesFromAppleScriptSource –ソースを取得してコンパイル(構文確認)する方式から、URL(fileURL)を指定してコンパイル(構文確認)する方式に変更した on getAttrRecFromASPath(aFile) set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of aFile) set theScript to OSAScript’s alloc()’s initWithContentsOfURL:aURL |error|:(missing value) if theScript is equal to missing value then — handle error error "Compile Error" else –set sourceText to theScript’s source() –No Use set styledSourceText to theScript’s richTextSource() end if set attrRes to getAttributeRunsFromAttrString(styledSourceText) of me return attrRes end getAttrRecFromASPath –指定AppleScriptファイルのソースコードを取得する(実行専用Scriptからは取得できない) — Original Created 2014-02-23 Shane Stanley on getASsourceFor(anAlias as {alias, string}) set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of anAlias) set theScript to OSAScript’s alloc()’s initWithContentsOfURL:aURL |error|:(missing value) if theScript is equal to missing value then — handle error error "Compile Error" else set sourceText to theScript’s source() end if return sourceText as string end getASsourceFor –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:(specifier) 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 –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 log aRes 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 retTwoPathList() tell application "Script Debugger" set dList to name of every document set dResList to choose from list dList with prompt "Select two documents" with multiple selections allowed if dResList = false then return false if length of dResList is not equal to 2 then display dialog "Please Select two documents to compare handler contents" buttons {"OK"} default button 1 with icon 1 with title "Selection Error" return false end if set dPathList to {} repeat with i in dResList set j to contents of i tell document j set tmpPath to (file spec) as string –HFS path string end tell set the end of dPathList to tmpPath end repeat end tell return dPathList end retTwoPathList –Written By Philip Aker –文字置換ルーチン on repChar(origText as string, targStr as string, repStr as string) set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr} set temp to text items of origText set AppleScript’s text item delimiters to repStr set res to temp as text set AppleScript’s text item delimiters to txdl return res end repChar –指定文字と終了文字に囲まれた内容を抽出 on extractStrFromTo(aParamStr, fromStr, toStr) set theScanner to current application’s NSScanner’s scannerWithString:aParamStr set anArray to current application’s NSMutableArray’s array() repeat until (theScanner’s isAtEnd as boolean) set {theResult, theKey} to theScanner’s scanUpToString:fromStr intoString:(specifier) theScanner’s scanString:fromStr intoString:(missing value) set {theResult, theValue} to theScanner’s scanUpToString:toStr intoString:(specifier) if theValue is missing value then set theValue to "" –>追加 theScanner’s scanString:toStr intoString:(missing value) anArray’s addObject:theValue end repeat if anArray’s |count|() is not equal to 1 then return false return first item of (anArray as list) end extractStrFromTo |