6

I would like the JXA equivalent of this AppleScript snippet:

tell application "Finder"

    # Get path
    set currentTarget to target of window 1
    set posixPath to (POSIX path of (currentTarget as alias))

    # Show dialog
    display dialog posixPath buttons {"OK"}

end tell

The closest I got was using the url property to initialize a Foundation NSURL object and access its fileSystemRepresentation property like so:

// Get path
var finder = Application('Finder')
var currentTarget = finder.finderWindows[0].target()
var fileURLString = currentTarget.url()

// I'd like to get rid of this step
var fileURL = $.NSURL.alloc.initWithString(fileURLString)
var posixPath = fileURL.fileSystemRepresentation

// Show dialog
finder.includeStandardAdditions = true
finder.displayAlert('', {buttons: ['Ok'], message: posixPath})

But this seems unnecessarily complex. Is there a nicer way to get to the POSIX path without using Foundation API or manual string wrangling?

System Events AppleScript Dictionary

If I naively try this:

finder.finderWindows[0].target().posixPath()

I get this error:

app.startupDisk.folders.byName("Users").folders.byName("kymer").folders.byName("Desktop").posixPath()
        --> Error -1728: Can't get object.

This SO answer seems relevant, but I can't seem to adapt it to fit my needs:

App = Application.currentApplication()
App.includeStandardAdditions = true
SystemEvents = Application('System Events')

var pathToMe = App.pathTo(this)
var containerPOSIXPath = SystemEvents.files[pathToMe.toString()].container().posixPath()

Any help would be greatly appreciated!

Kymer
  • 1,184
  • 8
  • 20
  • What's wrong with the Cocoa API? It's much faster than sending Apple Events. – vadian Aug 02 '17 at 05:23
  • 2
    @vadian: This is a straightforward _scripting_ task, which _should_ have a straightforward JXA-only solution, analogous to the (reasonably) straightforward AppleScript-only solution. Requiring scripters to resort to the _underlying APIs_ (which is _Foundation_ in this case, not Cocoa, btw) in such a simple case is an unnecessary burden that represents an avoidable quantum leap in terms of requisite knowledge. – mklement0 Aug 06 '17 at 20:58
  • @mklement0 I know it's Foundation, I just quoted a phrase of the question. However you are suggesting to use `NSURL` in your answer, too. – vadian Aug 06 '17 at 21:07
  • @vadian: I _am_ suggesting `NSURL`, because it's the simplest and most robust solution _given the circumstances_, but, just to be absolutely clear: _this should NOT be necessary_. I've made this clearer in my answer. – mklement0 Aug 06 '17 at 21:09
  • 1
    Good point about NSURL being part of Foundation and not Cocoa. Edited the question. There is nothing **wrong** with using it, but I was hoping it wouldn't have been necessary. I'd prefer a pure JXA solution (without resorting to manual string manipulation) for the reasons mklement0 mentioned. – Kymer Aug 07 '17 at 16:10

5 Answers5

4

The fact that such a simple piece of AppleScript code has no straightforward JXA translation is a testament to the sorry state of JXA and macOS automation based on OSA scripting in general:

As your own example suggests, among the two dying automation scripting languages AppleScript - despite all its warts - is the more mature, reliable choice.


To solve your problem in JXA, it looks like you've come up with the best approach yourself. Let me package it as a helper function that perhaps easies the pain somewhat - to be clear: such a helper function should NOT be necessary:

// Helper function: Given a Finder window, returns its folder's POSIX path.
// Note: No need for an ObjC.import() statement, because NSURL is 
//       a Foundation class, and all Foundation classes are implicitly
//       available.
function posixPath(finderWin) {
  return $.NSURL.alloc.initWithString(finderWin.target.url()).fileSystemRepresentation
}

// Get POSIX path of Finder's frontmost window:
posixPath(Application('Finder').finderWindows[0])
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Interesting blog post about the state of automation you linked there. Oh and thanks for the tip about the unnecessary import! – Kymer Aug 07 '17 at 16:07
2

@Kymer, you said:

But this seems unnecessarily complex. Is there a nicer way to get to the POSIX path without using Cocoa API or manual string wrangling?

You're on the right track. Here's the best method I know of. If there are better, I too would like to know about them. But, this seems to work well as fast, and works for both files and folders.

var finderApp = Application("Finder");
var itemList  = finderApp.selection();
var oItem      = itemList[0];
var oItemPaths  = getPathInfo(oItem);

/* --- oItemPaths Object Keys ---
  oItemPaths.itemClass
  oItemPaths.fullPath
  oItemPaths.parentPath
  oItemPaths.itemName
*/

console.log(JSON.stringify(oItemPaths, undefined, 4))

function getPathInfo(pFinderItem) {

  var itemClass  = pFinderItem.class();  // returns "folder" if item is a folder.
  var itemURL = pFinderItem.url();
  var fullPath  = decodeURI(itemURL).slice(7);

  //--- Remove Trailing "/", if any, to handle folder item ---
  var pathElem  = fullPath.replace(/\/$/,"").split('/')

  var  itemName   = pathElem.pop();
  var parentPath = pathElem.join('/');

  return {
    itemClass:   itemClass,
    fullPath:    fullPath,
    parentPath:  parentPath,
    itemName:    itemName
    };

}
mklement0
  • 382,024
  • 64
  • 607
  • 775
JMichaelTX
  • 1,659
  • 14
  • 19
  • 1
    Thanks for taking the time to answer. I know I could manually *wrangle* the path string, but that is what I really wanted to avoid. Was hoping to be able to grab the `posixPath` property in some way. – Kymer Aug 04 '17 at 08:14
2

In theory you'd write something like:

finder.finderWindows[0].target({as:"alias"})

but this doesn't work and there's nothing in the documentation to indicate it's supported. But this is SOP for JXA which, like Apple's earlier Scripting Bridge, suffers numerous design flaws and omissions, which never have (and likely never will be) fixed.[1]

FWIW, here's how you do it in Node.js, using NodeAutomation:

$ node
> Object.assign(this,require('nodeautomation'));undefined
> const fn = app('Finder')
> var file = fn.FinderWindows[0].target({asType:k.alias}) // returns File object
> file.toString() // converts File object to POSIX path string
'/Users/jsmith/dev/nodeautomation'

(Be aware that NodeAutomation is a very low-priority project for me, given that Mac Automation looks to be pretty much on its last legs at Apple. Caveat emptor, etc. For non-trivial scripting I strongly recommend sticking to AppleScript as it's the only officially supported solution that actually works right.)


[1] For instance, another JXA limitation is that most apps' move and duplicate commands are seriously crippled cos the JXA authors forgot to implement insertion reference forms. (BTW, I reported all these problems before JXA was even released, and appscript had all this stuff solved a decade ago, so they've no excuse for not getting it right either.)

foo
  • 664
  • 1
  • 4
  • 4
  • @JMichaelTX: If that was you downvoting, please serve the OP’s interest, not your personal agenda. – foo Aug 03 '17 at 06:16
  • 1
    The downvote was **definitely** not deserved. And if it was to give other answers more visibility that's very petty indeed. Unfortunately I can't rely on an external dependency for my use case, but your answer is helpful nonetheless. You seem very knowledgeable about automation. Have you considered hosting your repo's on Github? Might get more visibility and I'd definitely star / follow them over there :) – Kymer Aug 04 '17 at 08:22
  • 2
    On visibility: Writing NodeAutomation was more about proving a point than establishing a new platform for Mac automation. Opinion amongst those paying attention is that AppleScript and Apple event IPC are finally on their way out at Apple after years of mismanagement, so I'm not encouraging anyone to jump on the xAutomation bandwagon now. Been there, done that, and burned 1000+ [appscript](http://appscript.sourceforge.net) users for their troubles. If others want to take [Swift/Node]Automation and run with them they're welcome to try; personally, I only present them as a helpful perspective. – foo Aug 04 '17 at 16:44
0

Here's a fairly simple example function that just grabs the window's target and then strips off the leading file:// from its url.

/*
pathToFrontWindow()
returns path to front Finder window
*/
function pathToFrontWindow() {
    if ( finder.windows.length ) {
        return decodeURI( finder.windows[0].target().url().slice(7) )
    } else {
        return ""
    }
}
Patrick Wynne
  • 1,864
  • 15
  • 20
  • Though this might answer the question, please also add a short explanation what you code actually does and why it solves the initial problem. – user1438038 Aug 04 '17 at 07:46
  • Slicing and dicing strings is something I really wanted to avoid. Was hoping to somehow get a hold of the `posixPath` property directly. – Kymer Aug 04 '17 at 08:26
  • 3
    The problem is that the `target` property of the Finder's `window` class returns a specifier of class `folder`, which does not have a `posixPath` property. You need an `alias` object in order to get the `posixPath` and as far as I can tell there's no way to convert a `folder` to an `alias` in JXA. – Patrick Wynne Aug 05 '17 at 19:00
0
(() => {

    // getFinderDirectory :: () -> String
    const getFinderDirectory = () =>
        Application('Finder')
        .insertionLocation()
        .url()
        .slice(7);

    return getFinderDirectory();
})();
houthakker
  • 688
  • 5
  • 13