As I’ve said several times in the past, I use OmniOutliner on the Mac and iPad for a vast array of tasks. OmniOutliner’s scriptability on the mac is outstanding and demonstrates what’s possible when a developer commits to making automation a key feature of their product.
A while ago I had the need to create an OmniOutliner outline of a number of scripts I had written. These outlines include the names of each property and each handler defined in the script. Improvements to Script Debugger 7’s scripting interface finally made this possible.
I think this is a great example of how application scriptability, combined with AppleScript, makes very personal and idiosyncratic automations possible and valuable. This could have been done by hand, but now its something I can repeat at will without errors creeping in. There is no way this specific feature would appear in either OmniOutliner or Script Debugger, but I can create it for myself.
This example script demonstrates the following technologies:
- OmniOutliner’s scripting interface for creating outlines
- Script Debugger 7’s ability to introspect a script
- Script Debugger 7’s enhanced applets for creating attractive droplets
- Script Debugger 7’s ability to control the types of files a droplet accepts
NOTE: MarksLib is needed to compile this script.
As an example of how this works, if I drop my MarksLib script onto my Export To OmniOutliner droplet this is the result:
Here’s the AppleScript code for an enhanced droplet which accepts a script and generates a new OmniOutliner document. Note that the sourceForHandlerName
handler contains the logic needed to reconstruct a handler’s source declaration from the meta-data offered by Script Debugger 7’s scripting interface.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use MarksLib : script "MarksLib" version "1.0"
-- SD7 Enhanced applet properties
property appletDropName : "Drop your scripts here"
property appletSearchName : "Find scripts"
on open theFiles
set progress description to "Generate OmniOutliner Outline"
set progress total steps to count theFiles
set progress completed steps to 0
repeat with aFile in theFiles
tell application "Finder"
set aFileName to name of item aFile
set outputFolder to container of item aFile as alias
end tell
set progress additional description to "Processing " & aFileName & "..."
processFile(contents of aFile)
set my progress completed steps to (my progress completed steps) + 1
end repeat
end open
on processFile(theFile)
local ooDocument
tell application "OmniOutliner"
set ooDocument to make new document
end tell
tell application "Script Debugger"
local numOpenDocuments, documentAlreadyOpen, ooProperties, propertyName, propertyValue, ooHandlers, handlerName, handlerCode
set numOpenDocuments to count of documents
tell (open theFile)
set documentAlreadyOpen to (count of current application's documents) = numOpenDocuments
-- Inventory the script's properties...
if (count of script properties) > 0 then
tell application "OmniOutliner"
set ooProperties to make new row in ooDocument with properties {topic:"Properties"}
end tell
repeat with aProperty in script properties
set propertyName to aProperty's name
set propertyValue to aProperty's source text
tell application "OmniOutliner"
make new row in ooProperties with properties {topic:propertyName, note:propertyValue}
end tell
end repeat
end if
-- Inventory the script's handlers
if (count of script handlers) > 0 then
tell application "OmniOutliner"
set ooHandlers to make new row in ooDocument with properties {topic:"Handers"}
end tell
repeat with aHandler in script handlers
set handlerName to my sourceForHandlerName(aHandler)
set handlerCode to aHandler's source text
tell application "OmniOutliner"
make new row in ooHandlers with properties {topic:handlerName, note:handlerCode}
end tell
end repeat
end if
if not documentAlreadyOpen then
close saving no
end if
end tell
end tell
end processFile
on sourceForHandlerName(theHandler)
tell application "Script Debugger"
local handlerName
set handlerName to theHandler's name
if handlerName contains "_" then -- interlieved syntax/ASObjC
-- handler: varName parameter: varName ...
local handlerNameParts, numParts, formattedHandlerName, formattedHandlerNameParts
set handlerNameParts to MarksLib's split(handlerName, "_")
if last item of handlerNameParts is "" then ¬
set handlerNameParts to items 1 thru -2 of handlerNameParts
set numParts to count handlerNameParts
set formattedHandlerNameParts to {}
repeat with i from 1 to numParts
set end of formattedHandlerNameParts to (handlerNameParts's item i) & ":" & (theHandler's handler parameter i's name)
end repeat
return MarksLib's |join|(formattedHandlerNameParts, " ")
else
--
local positionalParameters, keywordParameterNames, keywordParameterVariables, givenParameterNames, givenParameterVariables, eventParameters
set positionalParameters to name of (every handler parameter whose parameter type is positional parameter) of theHandler
set keywordParameterNames to name of (every handler parameter whose parameter type is keyword parameter) of theHandler
set keywordParameterVariables to variable name of (every handler parameter whose parameter type is keyword parameter) of theHandler
set givenParameterNames to name of (handler parameters whose parameter type is named parameter) of theHandler
set givenParameterVariables to variable name of (handler parameters whose parameter type is named parameter) of theHandler
set eventParameters to name of (handler parameters whose parameter type is direct parameter) of theHandler
if (count of eventParameters) = 1 then
if (count of keywordParameterNames) > 0 then
-- handler of p1 ...
set formattedHandlerName to handlerName & " of " & (first item of eventParameters)
else
-- handler p1
set formattedHandlerName to handlerName & " " & (first item of eventParameters)
end if
else if (count of eventParameters) > 1 then
-- handler {p1, ...}
set formattedHandlerName to handlerName & " {" & (MarksLib's |join|(eventParameters, ", ")) & "}"
else if (count of positionalParameters) > 0 then
-- handler(p1, ...)
set formattedHandlerName to handlerName & "(" & (MarksLib's |join|(positionalParameters, ", ")) & ")"
else
-- Use `script handler`'s `handler type` property to determin this once 7.0.1 ships
if handlerName = "run" or handlerName = "idle" or handlerName = "quit" then
-- handler
set formattedHandlerName to handlerName
else
-- handler()
set formattedHandlerName to handlerName & "()"
end if
end if
if (count of keywordParameterNames) > 0 then
-- handler ... given {name: varName, ...}
set params to {}
repeat with i from 1 to count of keywordParameterNames
set end of params to (item i of keywordParameterNames) & " " & (item i of keywordParameterVariables)
end repeat
set formattedHandlerName to formattedHandlerName & " " & (MarksLib's |join|(params, " "))
end if
if (count of givenParameterNames) > 0 then
-- handler of p1 name varName ...
set params to {}
repeat with i from 1 to count of givenParameterNames
set end of params to (item i of givenParameterNames) & ": " & (item i of givenParameterVariables)
end repeat
set formattedHandlerName to formattedHandlerName & " given " & (MarksLib's |join|(params, ", "))
end if
return formattedHandlerName
end if
end tell
end sourceForHandlerName
And here is the code packaged as an enhanced applet:
Export To OmniOutliner.zip (2.3 MB)