Chris, thanks for the update/changes. I like them.
One thing I wanted, but didn't find, was the ability to remove duplicate sub-macros for a given parent macro.
So, I made a few minor mods to add that feature, and make some formatting changes to my preferences. Hopefully I didn't break anything. All looks good with my limited testing, but please let me know if see any issues.
One thing I'd like to so, but did not try here, is to sort by Macro Group, Parent Macro, Sub-Macro, and show that as a hierarchy.
Example Output
[SE] NEW Script Document from Template ❯ MG: Script Editor ❮
• [SE] Set Script Editor Doc Properties
[SE] Script Editor Launch Setup ❯ MG: [GLOBAL] ❮
• [SE] Set Script Editor Doc Properties
• [SE] NEW Script Document from Template
[SE] Set Script Editor Doc Properties ❯ MG: Script Editor ❮
• [WIN] Move & Set Window Size for MY Prefs
• [SE] Show Log and Messages
[TEMPLT] Std Macro Template BACKUP ❯ MG: Templates ❮
• [KM] DELETE List of KM Variables [SUB-MACRO]
Key Changes
--- All changes are marked with "## JMTX" ---
--- NEAR THE TOP ---
property removeDupMacros : true ## JMTX Add
--- DOWN BELOW ---
tell theMacro
set end of tempList to its name & " ❯ MG: " & its macro group's name & " ❮" ## JMTX Mod
## set end of tempList to "" ## JMTX Mod
set actionXML to (xml of its actions whose xml contains "ExecuteMacro")
set actionXML to (my joinList:actionXML usingStr:linefeed)
set uuidList to (my regexFindWithCapture:"<string>ExecuteMacro</string>\\n\\h*<key>MacroUID</key>\\n\\h*<string>([0-9A-Z-]+)</string>" fromString:actionXML resultTemplate:"$1")
set macroNameList to {} ## JMTX Add
repeat with i in uuidList
set macroName to getMacroNameFromUUID(i) of me
## JMTX: Revise to Allow Removal of Dup Sub-Macros for a Given Macro ##
if (removeDupMacros and (macroNameList does not contain macroName)) then
set end of tempList to tab & "• " & macroName ## JMTX Mod
set end of macroNameList to macroName
else if not removeDupMacros then
set end of tempList to tab & "• " & macroName ## JMTX Mod
end if
## JMTX: END of Block Changes for Dup Sub-Macros
end repeat
end tell
Full Script with Changes
----------------------------------------------------------------
# Auth: Christopher Stone (minor mods by @JMichaelTX)
# dCre: 2018/12/25 15:03
# dMod: 2018/12/27 17:35
# Appl: Keyboard Maestro
# Task: Find macros that use sub-macros and list by name.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Keyboard_Maestro, @Find, @Macros, @Sub-Macros, @SubMacros, @List, @Name
# Vers: 1.0(b0003) by @JMichaelTX
----------------------------------------------------------------
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
----------------------------------------------------------------
(*
~~~~~~~~~~~ MODS BY @JMichaelTX ~~~~~~~~~~~~~~~~~~
Ver b0003 Date: 2018-12-27 17:37 GMT-6
All changes are marked with "## JMTX"
1. Add feature to remove dup Sub-Macros for a Given Parent Macro
• Set property removeDupMacros
2. Revised BBEdit formatting to my preference of Macro Group, bullets and spacing
• Changed code around line numbers 63-80
*)
property removeDupMacros : true ## JMTX Add
try
tell application "Keyboard Maestro"
set actionList to actions of macros where its xml contains "ExecuteMacro"
try
actionList / 0
on error errorStr
set macroList to errorStr
set macroList to (my regexFindWithCapture:"«class MKma» id \"[0-9A-Z-]+\"" fromString:macroList resultTemplate:("$0" & " of application \"Keyboard Maestro\""))
set tempList to {}
repeat with i in macroList
if contents of i is not in tempList then
set end of tempList to contents of i
end if
end repeat
copy tempList to macroList
set macroList to my joinList:macroList usingStr:", "
set macroList to "{" & macroList & "}"
set macroList to run script macroList
set tempList to {}
repeat with theMacro in macroList
tell theMacro
set end of tempList to its name & " ❯ MG: " & its macro group's name & " ❮" ## JMTX Mod
## set end of tempList to "" ## JMTX Mod
set actionXML to (xml of its actions whose xml contains "ExecuteMacro")
set actionXML to (my joinList:actionXML usingStr:linefeed)
set uuidList to (my regexFindWithCapture:"<string>ExecuteMacro</string>\\n\\h*<key>MacroUID</key>\\n\\h*<string>([0-9A-Z-]+)</string>" fromString:actionXML resultTemplate:"$1")
set macroNameList to {} ## JMTX Add
repeat with i in uuidList
set macroName to getMacroNameFromUUID(i) of me
## JMTX: Revise to Allow Removal of Dup Sub-Macros for a Given Macro ##
if (removeDupMacros and (macroNameList does not contain macroName)) then
set end of tempList to tab & "• " & macroName ## JMTX Mod
set end of macroNameList to macroName
else if not removeDupMacros then
set end of tempList to tab & "• " & macroName ## JMTX Mod
end if
## JMTX: END of Block Changes for Dup Sub-Macros
end repeat
end tell
set end of tempList to ""
end repeat
set reportStr to my joinList:tempList usingStr:linefeed
bbeditNewDoc(reportStr, "activate") of me
end try
end tell
on error e number n
set e to e & return & return & "Num: " & n
if n ≠ -128 then
try
tell application (path to frontmost application as text) to set ddButton to button returned of ¬
(display dialog e with title "ERROR!" buttons {"Copy Error Message", "Cancel", "OK"} ¬
default button "OK" giving up after 30)
if ddButton = "Copy Error Message" then set the clipboard to e
end try
end if
end try
----------------------------------------------------------------
--» HANDLERS
----------------------------------------------------------------
on bbeditNewDoc(_text, _activate)
tell application "BBEdit"
set newDoc to make new document with properties {text:_text, bounds:{0, 44, 1920, 1200}}
tell newDoc
select insertion point before its text
end tell
if _activate = true or _activate = 1 or _activate = "activate" then activate
end tell
end bbeditNewDoc
----------------------------------------------------------------
on cngStr:findString intoString:replaceString inString:dataString
set anNSString to current application's NSString's stringWithString:dataString
set dataString to (anNSString's stringByReplacingOccurrencesOfString:findString withString:replaceString ¬
options:(current application's NSRegularExpressionSearch) range:{0, length of dataString}) as text
end cngStr:intoString:inString:
----------------------------------------------------------------
on getMacroNameFromUUID(macroUUID)
tell application "Keyboard Maestro Engine"
return process tokens "%MacroNameForUUID%" & macroUUID & "%"
end tell
end getMacroNameFromUUID
----------------------------------------------------------------
on joinList:theList usingStr:theStr
set anNSArray to current application's NSArray's arrayWithArray:theList
set theString to anNSArray's componentsJoinedByString:theStr
return theString as text
end joinList:usingStr:
----------------------------------------------------------------
on regexFindWithCapture:thePattern fromString:theString resultTemplate:templateStr
set theString to current application's NSString's stringWithString:theString
set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
set theFinds to theRegEx's matchesInString:theString options:0 range:{0, theString's |length|()}
set theResult to current application's NSMutableArray's array()
repeat with aFind in theFinds
set foundString to (theRegEx's replacementStringForResult:aFind inString:theString |offset|:0 template:templateStr)
(theResult's addObject:foundString)
end repeat
return theResult as list
end regexFindWithCapture:fromString:resultTemplate:
----------------------------------------------------------------
on splitString:someText withString:mySeparator usingRegex:regexFlag ignoringCase:caseFlag ignoringDiacrits:diacritsFlag
-- build options
set theOptions to 0
if caseFlag then set theOptions to (current application's NSCaseInsensitiveSearch)
if regexFlag then
set theOptions to theOptions + (current application's NSRegularExpressionSearch as integer)
else -- ignoring diacriticals only applies if not doing regex
if diacritsFlag then set theOptions to theOptions + (current application's NSDiacriticInsensitiveSearch as integer)
end if
set theString to current application's NSString's stringWithString:someText
if theOptions = 0 then -- simplest case: literal, considering case and ignoring diacrits
set theList to theString's componentsSeparatedByString:mySeparator
else
set theList to {} -- to hold strings
-- get length and set some intital variables
set theFullLength to theString's |length|()
set theLength to theFullLength
set theStart to 0
repeat
-- look for range of next match
set theRange to theString's rangeOfString:mySeparator options:theOptions range:{location:theStart, |length|:theLength}
if theRange's |length|() = 0 then -- no match found, so grab the whole string then exit repeat
set end of theList to (theString's substringWithRange:{location:theStart, |length|:theLength}) as text
exit repeat
end if
-- grab the text before the match
set end of theList to (theString's substringWithRange:{location:theStart, |length|:(location of theRange) - theStart}) as text
-- reset values for next search
set theStart to current application's NSMaxRange(theRange) -- straight after the match
set theLength to theFullLength - theStart
end repeat
end if
return theList as list
end splitString:withString:usingRegex:ignoringCase:ignoringDiacrits:
----------------------------------------------------------------
Thanks again for sharing your solution.