The code below defines a pp() handler that returns a textual description of any AppleScript object. The handler is part of ASUnit’s output routines (https://github.com/lifepillar/ASUnit), but I thought that it might be interesting by itself (the code below is completely self-contained). It has been carefully crafted with the following design goals in mind:
- provide a description as accurate as possible (in particular, distinguish between objects and references to objects);
- never throw an error;
- always terminate (see the demo below for an edge case).
Wrt 1, there are some limitations: first, records are represented as «record {value1, value2, .}» rather than {key1: value1, key2: value2, .}. Second, handlers are represented as «handler» instead of the more descriptive «handler name»; third, all Cocoa references are represented simply as «class ocid» (without the details of the reference itself). But this code is pure AppleScript, and I don’t think that there is a way to overcome such limitations. Maybe, using Cocoa one can do something along the lines of what osascript -s s does (if you know how, please tell me!).
I have tested this code thoroughly, but if you find a case where it does not produce the expected result, please let me know! I need this to be as robust as possible for ASUnit
(*!
@abstract
Determines whether invisible characters should be made visible.
@discussion
When this property is set to true (which is the default), invisible characters
(spaces, tabulations, linefeeds, and returns) are printed as visible characters.
*)
property showInvisibles : true
(*! @abstract The maximum recursion depth for @link pp() @/link. *)
property maxRecursionDepth : 10
(*!
@abstract
Returns a textual representation of an object.
@param
anObject <em>[anything]</em> An expression.
*)
on pp(anObject)
_pp(anObject, 0)
end pp
on _pp(anObject, depth)
local res, klass, referencedObject
if depth > my maxRecursionDepth then return "..."
try -- Is it a reference?
anObject as reference
try
set referencedObject to contents of anObject
on error
return "«undefined reference»"
end try
if anObject is not equal to referencedObject then
return "a reference to" & space & _pp(contents of anObject, depth + 1)
end if
-- Is it an Objective-C reference?
if isCocoaRef(anObject) then return "«class ocid»"
-- Is it a file reference?
try
if class of anObject is alias then
return "alias" & space & asText(anObject)
end if
end try
try
anObject as «class furl»
return "file" & space & asText(anObject)
end try
-- Is it a date?
try
if class of anObject is date then return asText(anObject)
end try
-- Is it a unit type?
try
set klass to class of anObject
if klass is in {centimeters, feet, inches, kilometers, meters, miles, yards, square feet, square kilometers, square meters, square miles, square yards, cubic centimeters, cubic feet, cubic inches, cubic meters, cubic yards, gallons, liters, quarts, grams, kilograms, ounces, pounds, degrees Celsius, degrees Fahrenheit, degrees Kelvin} then
return asText(anObject) & space & asText(klass)
end if
end try
try
return "a reference of class" & space & _pp(klass, depth + 1)
on error
return "Unrecognized reference [please report as ASUnit bug]" -- We should never get here
end try
end try
-- Ok, not a reference. Let's try to get anObject's class
try
set klass to class of anObject
on error
try
return "«" & asText(anObject's name) & "»"
end try
try
return "«" & asText(anObject's id) & "'»"
end try
try
return "«" & asText(anObject's description) & "»"
end try
try
return asText(anObject)
on error -- Give up
return "«object»"
end try
end try
if klass is list or class is RGB color then
local s, n
set n to anObject's length
if n = 0 then return "{}"
set s to "{"
repeat with i from 1 to n - 1
set s to s & _pp(item i of anObject, depth + 1) & "," & space
end repeat
return s & _pp(item n of anObject, depth + 1) & "}"
end if
if klass is record then
return "«record " & _pp(anObject as list, depth + 1) & "»"
end if -- list, RGB color
if klass is script or klass is application or klass is null then
if anObject is AppleScript then return "AppleScript"
try
set res to anObject's id
if res is missing value then error
set res to asText(res)
on error
try
set res to anObject's name
if res is missing value then error
set res to asText(res)
on error
set res to ""
end try
end try
if klass is script then
return "«script" & space & res & "»"
else
return "«application" & space & res & "»"
end if
end if -- script, application, null
if klass is handler then return "«handler»"
try
set res to asText(anObject)
on error
if klass is anObject then return "«object of class self»"
try
return "«object of class" & space & _pp(klass, depth + 1) & "»"
on error errMsg
return "ERROR:" & errMsg -- We should never get here
end try
end try
if klass is text then
if my showInvisibles then -- show invisible characters
local tid, x
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to space
set x to text items of res
set AppleScript's text item delimiters to «data utxtFF65» as Unicode text -- small bullet
set res to x as text
set AppleScript's text item delimiters to tab
set x to text items of res
set AppleScript's text item delimiters to «data utxt21A6» as Unicode text -- rightwards arrow from bar
set res to x as text
set AppleScript's text item delimiters to linefeed
set x to text items of res
set AppleScript's text item delimiters to «data utxt00AC» as Unicode text -- not sign
set res to x as text
set AppleScript's text item delimiters to return
set x to text items of res
set AppleScript's text item delimiters to «data utxt21A9» as Unicode text -- hook arrow
set res to x as text
set AppleScript's text item delimiters to tid
end if
return res
end if
return res
end _pp
(*! @abstract Utility handler to coerce an object to <code>text</code>. *)
on asText(s)
local ss, tid
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ""}
try
set ss to s as text
set AppleScript's text item delimiters to tid
return ss
on error errMsg number errNum
set AppleScript's text item delimiters to tid
error errMsg number errNum
end try
end asText
(*!
@abstract
Utility handler to check whether a given expression is a reference to a Cocoa object.
@discussion
See <a href="http://macscripter.net/viewtopic.php?pid=177998">this MacScripter's thread</a>.
*)
on isCocoaRef(x)
try
(class of x) as reference
(contents of class of x is class of x)
on error
false
end try
end isCocoaRef
script demo
property x : POSIX file "/some/path"
property y : a reference to x
property z : a reference to y
property tweedledee : a reference to tweedledum
property tweedledum : a reference to tweedledee
property doc : missing value
display dialog pp(my parent's class)
display dialog pp({1, "a", {"nested", {"list"}}, {x:7, y:"abc"}, me})
display dialog pp(path to home folder)
tell application "Script Editor" to set doc to get some document
display dialog pp(doc)
display dialog pp(a reference to z)
display dialog pp(linefeed & space & tab & space & return)
display dialog pp(tweedledee)
end script
property class : me -- Weird, but legal
run demo
Edited: make pp() recognize more references, e.g., references to Finder windows.