Get POSIX path of active Finder window with JXA AppleScript
Asked Answered
H

5

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!

Harlen answered 31/7, 2017 at 22:26 Comment(5)
What's wrong with the Cocoa API? It's much faster than sending Apple Events.Staats
@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.Barger
@Barger I know it's Foundation, I just quoted a phrase of the question. However you are suggesting to use NSURL in your answer, too.Staats
@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.Barger
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.Harlen
B
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])
Barger answered 6/8, 2017 at 2:46 Comment(1)
Interesting blog post about the state of automation you linked there. Oh and thanks for the tip about the unnecessary import!Harlen
B
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
    };

}
Bernstein answered 1/8, 2017 at 23:8 Comment(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.Harlen
M
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.)

Mcdevitt answered 2/8, 2017 at 8:47 Comment(3)
@JMichaelTX: If that was you downvoting, please serve the OP’s interest, not your personal agenda.Mcdevitt
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 :)Harlen
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 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.Mcdevitt
R
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 ""
    }
}
Rosie answered 3/8, 2017 at 23:30 Comment(3)
Though this might answer the question, please also add a short explanation what you code actually does and why it solves the initial problem.Pseudonym
Slicing and dicing strings is something I really wanted to avoid. Was hoping to somehow get a hold of the posixPath property directly.Harlen
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.Rosie
E
0
(() => {

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

    return getFinderDirectory();
})();
Entablature answered 3/11, 2017 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.