Access to ObjC library functions is facilitated, in JavaScript for Automation by the ObjC.wrap() and ObjC.unwrap functions, which automatically convert to and from the matching ObjC datatypes.
ObjC.wrap() in JS can also be used in a syntactic sugar abbreviation to the $() operator, so that we can simply write:
$(strPath).stringByStandardizingPath
Where $() converts a JS string to an NSString object.
Similarly we can abbreviate the use of ObjC.unwrap by simply appending the three-character affix .js
to an ObjC object, to convert it straight back to the appropriate JavaScript data type.
For some reason, however, AppleScript has not been provided with out-of-the-box wrap()
or unwrap()
functions, or with any equivalent syntactic sugaring.
Here is one approach to facilitating AS access to ObjC libraries by writing wrap() and unwrap() for ourselves.
We can start a script with:
use framework "Foundation"
and then include these two functions
-- wrap :: AS value -> NSObject
on wrap(v)
set ca to current application
ca's (NSArray's arrayWithObject:v)'s objectAtIndex:0
end wrap
-- unwrap :: NSObject -> AS value
on unwrap(objCValue)
if objCValue is missing value then
return missing value
else
set ca to current application
return item 1 of ((ca's NSArray's arrayWithObject:objCValue) as list)
end if
end unwrap
(These functions benefit from an insight by Shane Stanley, expounded on the AppleScript users list in response to a question by JMichaelTX, that we can delegate the selection of matching datatypes to methods of NSArray)
With these two helper functions we can, for example, replace the more verbose expansion of a file path tilde, usually something like:
use framework "Foundation"
set strPath to "~/Desktop"
set oPath to (current application's NSString's stringWithString:strPath)'s ¬
stringByStandardizingPath
which leads to a value like «class ocid» id «data optr0000000020BCE14DDA7F0000»
and still needs to be converted back to an AppleScript string
Instead, with our own wrap() and unwrap(), we can more simply write:
set strFullPath to unwrap(wrap("~/Desktop")'s stringByStandardizingPath)
Which immediately gives an AppleScript result like:
"/Users/houthakker/Desktop"
Here is a fuller example, which uses wrap() and unwrap() to simplify use of an ObjC contentsOfDirectoryAtPath() function.
use framework "Foundation"
-- Example of simplified access from AppleScript to ObjC functions
-- using generic wrap(ASObject) and unwrap(NSObject) functions
-- (In the manner of JavaScript for Automation)
on run
set strFullPath to unwrap(wrap("~/Desktop")'s stringByStandardizingPath)
return directoryContents(strFullPath)
end run
-- wrap :: AS value -> NSObject
on wrap(v)
set ca to current application
ca's (NSArray's arrayWithObject:v)'s objectAtIndex:0
end wrap
-- unwrap :: NSObject -> AS value
on unwrap(objCValue)
if objCValue is missing value then
return missing value
else
set ca to current application
return item 1 of ((ca's NSArray's arrayWithObject:objCValue) as list)
end if
end unwrap
-- directoryContents :: Unix Path String -> [FileName]
on directoryContents(strPath)
set ca to current application
set strFullPath to wrap(strPath)'s stringByStandardizingPath
filter(my notDotted, unwrap(ca's NSFileManager's defaultManager's ¬
contentsOfDirectoryAtPath:strFullPath |error|:(missing value)))
end directoryContents
on notDotted(strPath)
text item 1 of strPath is not "."
end notDotted
-- filter :: (a -> Bool) -> [a] -> [a]
on filter(f, xs)
set mf to mReturn(f)
set lst to {}
set lng to length of xs
repeat with i from 1 to lng
set v to item i of xs
if mf's lambda(v, i, xs) then
set end of lst to v
end if
end repeat
return lst
end filter
-- Lift 2nd class function into 1st class wrapper
-- handler function --> first class script object
on mReturn(f)
if class of f is script then return f
script
property lambda : f
end script
end mReturn