Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Listing all the files in a folder using JavaScript for Automation on Yosemite

I'm trying to port some old Applescript over to the new JavaScript syntax.

Somethings seem to be pretty straight forward so :

tell application "System Events" to keystroke "t" using command down

becomes :

System = Application('System Events');
System.keystroke("t", {using: "command down"})

However I can't for the life of me work out how to list files at a specific location. In AppleScript, to return me a list of the files in the /usr directory, you would do :

tell application "System Events" to set fileList to name of items in folder "/usr"
-- > {"bin", "include", "lib", "libexec", "local", "sbin", "share", "standalone", "X11"}

However I can't for the life of me work out how to do it in Javascript.

System = Application('System Events')
myPath = Path("/usr")

fileList = System.items(myPath) 
-- > message not understood

fileList = System.folder(myPath)
-- > message not understood

fileList = System.diskItems(myPath)
-- > []

fileList = System.diskItems({at:myPath)
-- > []

I've tried a whole lot of other combinations too, but no luck. Any ideas?

like image 297
dwkns Avatar asked Oct 10 '14 14:10

dwkns


3 Answers

Like Leopard's Scripting Bridge before it, JXA intentionally breaks all sorts of stuff that works perfectly in AppleScript. Here is the translation of your original AppleScript command to JXA syntax:

//tell application "System Events" to name of items in folder "/usr"
Application('System Events').folders.byName('/usr').items.name()

The AS version works perfectly, but the JXA equivalent just throws a completely meaningless Error -1700: Can't convert types.

JXA does seem to work if you write diskItems instead of items:

Application('System Events').folders.byName('/usr').diskItems.name()
// --> ["bin", "lib", "libexec", "local", "sbin", "share", "standalone", "X11", "X11R6"]

which suggests JXA indulges in much the same internal "cleverness" that causes SB to break on so many apps. (Note that I found numerous such design defects in earlier testing, but gave up reporting them once it was clear the AS devs only cared about imposing their own personal ideology and prejudices on everyone else, crippled capabilities and broken compatibility be damned.)

For comparison, here's the JavaScriptOSA (JOSA) prototype I quickly put together for the JXA developers' reference some months back (they promptly ignored it, natch):

app('System Events').folders.named('/usr').items.name()
// -> ["bin", "lib", "libexec", "local", "sbin", "share", "standalone", "X11", "X11R6"]

(While not fully finished or tested, JOSA still works a damn sight better than JXA, is better documented, and even includes an auto-translation tool for converting AS commands to JS syntax. Unfortunately, because Apple have legacied or deprecated the AEM, CM, PM, and OSA Carbon APIs, I cannot recommend it for production use; it's purely there for comparison purposes.)

Similarly:

set myPath to POSIX file "/usr"
tell application "System Events" to name of every disk item of folder named myPath
--> {"bin", "lib", "libexec", "local", "sbin", "share", "standalone", "X11", "X11R6"}

myPath = Path('/usr')
Application('System Events').folders.byName(myPath).diskItems.name()
// Error -1728: Can't get object.

var myPath = Path('/usr')
app('System Events').folders.named(myPath).diskItems.name()
// --> ["bin", "lib", "libexec", "local", "sbin", "share", "standalone", "X11", "X11R6"]

You can work around that particular case by converting the Path object to a string, and using that:

myPath = Path('/usr')
Application('System Events').folders.byName(myPath.toString()).diskItems.name()

Although that workaround won't necessarily help in other situations; e.g. it fails in Finder because Finder doesn't understand POSIX-style path strings, and there's no way to get an HFS-style path string (which Finder does understand) from a JXA Path object:

set myPath to POSIX file "/usr"
tell application "Finder" to name of every item of item myPath
--> {"X11", "X11R6", "bin", "lib", "libexec", "local", "sbin", "share", "standalone"}

myPath = Path('/usr')
Application('Finder').folders.byName(myPath.toString()).items.name()
// Error -1728: Can't get object.

And so it goes. (e.g. Try testing JXA's support for range, filter, relative, and insertion reference forms; it's particularly rotten.)

like image 123
foo Avatar answered Nov 18 '22 11:11

foo


The "System Events" approach certainly does have the merit of simplicity, but it turns out that using $.NSFileManager (now directly available for scripting) gives noticeably faster performance.

On my system, starting for comparison with

var strPath = '/usr';

var appSys = Application('System Events'),
lstHarvest = appSys.folders.byName(strPath).diskItems.name();

A quick test with a few thousand iterations suggests that we can consistently speed it up by a modest 40% with this slightly baroque approach:

var strPath = '/usr';
var fm = $.NSFileManager.defaultManager,
    oURL = $.NSURL.fileURLWithPathIsDirectory(strPath, true),
    lstFiles = ObjC.unwrap(
        fm.contentsOfDirectoryAtURLIncludingPropertiesForKeysOptionsError(
            oURL, [], 1 << 2, null
        )
    ),
    lstHarvest = [];

lstFiles.forEach(function (oItem) {
    lstHarvest.push(
        ObjC.unwrap(oItem.path)
    );
});

and by well over 300% with the rather simpler:

var strPath = '/usr';
var fm = $.NSFileManager.defaultManager,
    lstFiles = ObjC.unwrap(
        fm.contentsOfDirectoryAtPathError(strPath, null)
    ),
    lstHarvest = [];

lstFiles.forEach(function (oItem) {
    lstHarvest.push(
        ObjC.unwrap(oItem)
    );
});
like image 41
houthakker Avatar answered Nov 18 '22 13:11

houthakker


I can get it like this

 foldersList =  foldersList =  System.folders.byName("usr").folders.name()

-> ["bin", "lib", "libexec", "sbin", "share", "standalone", "X11"]

And even this works:

   foldersList =  System.folders.byName("/Users/USERName/Documents/").folders.name()

But I so far cannot get the Path command to work on anything but 'open'

like image 40
markhunte Avatar answered Nov 18 '22 12:11

markhunte