使用JXA AppleScript获取活动Finder窗口的POSIX路径

6
我想要这个AppleScript片段的JXA等效版本:
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

我最接近的方法是使用url属性来初始化一个Foundation NSURL对象,并像下面这样访问它的fileSystemRepresentation属性:

// 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})

但是这似乎过于复杂了。有没有更好的方法来获取POSIX路径,而不使用Foundation API或手动字符串操作呢?

System Events AppleScript Dictionary

如果我尝试这样幼稚的方法:

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.

这个SO答案似乎相关,但我无法将其适应我的需求:
App = Application.currentApplication()
App.includeStandardAdditions = true
SystemEvents = Application('System Events')

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

非常感谢任何帮助!

Cocoa API有什么问题吗?它比发送Apple事件要快得多。 - vadian
2
@vadian:这是一个简单的脚本任务,应该有一个直接的JXA-only解决方案,类似于(相对而言)简单的AppleScript-only解决方案。在这种简单情况下要求脚本编写者使用底层API(这里是Foundation,而不是Cocoa)是一种不必要的负担,代表了所需知识的可避免的飞跃。 - mklement0
@mklement0 我知道这是基础框架,我只是引用了问题的一个短语。但是您在回答中也建议使用 NSURL - vadian
@vadian:我建议使用NSURL,因为在这种情况下它是最简单和最稳健的解决方案,但是要非常清楚:_这不应该是必需的_。我已经在我的答案中更加明确了这一点。 - mklement0
1
NSURL是Foundation的一部分而不是Cocoa,这是一个很好的观点。已编辑问题。使用它没有任何问题,但我希望不必使用它。正如mklement0所提到的那样,我更喜欢纯JXA解决方案(而不是手动字符串操作)。 - Kymer
5个回答

4
这段AppleScript代码如此简单,却没有直接的JXA翻译,在一定程度上证明了JXA和基于OSA脚本的macOS自动化的困境:

正如你自己的例子所示,虽然AppleScript有缺陷,但在这两种即将死亡的自动化脚本语言中,它是更成熟、可靠的选择。


要解决JXA中的问题,看起来你已经想出了最好的方法。 让我将其打包成一个辅助函数,或许可以减轻一些痛苦 - 明确指出:不应该需要这样的辅助函数

// 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])

1
你分享的关于自动化现状的博客文章很有趣。哦,还有感谢你提供的关于无用导入的提示! - Kymer

2
理论上,你应该写类似这样的东西:
finder.finderWindows[0].target({as:"alias"})

但是这并不起作用,文档中也没有任何指示表明它受支持。但这是 JXA 的标准操作程序,就像苹果早期的 Scripting Bridge 一样,存在许多设计缺陷和遗漏,这些问题从未得到解决(也可能永远不会得到解决)[1]。
顺便说一下,在 Node.js 中,使用 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'

请注意,对我来说,NodeAutomation是一个非常低优先级的项目,因为苹果公司的Mac自动化看起来已经接近崩溃。买方自负等等。对于非平凡的脚本编写,我强烈建议坚持使用AppleScript,因为它是唯一官方支持并且实际上能正常工作的解决方案。
例如,JXA 的另一个限制是大多数应用程序的moveduplicate命令严重受到限制,因为 JXA 作者忘记实现插入参考形式。(顺便说一句,在 JXA 发布之前我报告了所有这些问题,而 appscript 在十年前就解决了所有这些问题,所以他们没有任何借口不能把它搞对。)

@JMichaelTX:如果那是你在投反对票,请为提问者的利益服务,而不是你个人的议程。 - foo
1
这个踩的评价绝对不应该。如果是为了让其他答案更加显眼,那就太小气了。不幸的是,对于我的用例,我不能依赖外部依赖项,但你的回答仍然很有帮助。你似乎非常了解自动化。你考虑过在Github上托管你的repo吗?可能会得到更多的关注,我肯定会在那里点赞/关注它们 :) - Kymer
2
关于可见性:编写NodeAutomation更多的是为了证明一个观点,而不是建立一个新的Mac自动化平台。那些关注此事的人认为,在经历了多年的管理不善后,AppleScript和Apple事件IPC终于要退出苹果公司了,因此我不鼓励任何人现在加入xAutomation的行列。已经有人尝试过这样做,并且因此给1000多个appscript用户带来了麻烦。如果其他人想要使用[Swift/Node]Automation并运用它们,他们可以尝试;就我个人而言,我只是提供它们作为一种有益的视角。 - foo

2

@Kymer,你说:

但这似乎过于复杂。有没有更好的方法来获取POSIX路径,而不使用Cocoa API或手动处理字符串?

你的想法很正确。这是我知道的最好的方法。如果有更好的方法,我也很想了解一下。但是,这个方法似乎既快又适用于文件和文件夹。

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
    };

}

1
感谢您抽出时间回答我的问题。我知道我可以手动处理路径字符串,但其实我真正想避免的就是这种操作。我希望能够以某种方式获取posixPath属性。 - Kymer

0
(() => {

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

    return getFinderDirectory();
})();

0
这是一个相当简单的示例函数,它只是获取窗口的target,然后从其url中剥离前导的file://
/*
pathToFrontWindow()
returns path to front Finder window
*/
function pathToFrontWindow() {
    if ( finder.windows.length ) {
        return decodeURI( finder.windows[0].target().url().slice(7) )
    } else {
        return ""
    }
}

虽然这可能回答了问题,但请您也简要解释一下您的代码实际上是如何运作并且为什么它能够解决初始的问题。 - user1438038
切割和分解字符串是我真的想避免的事情。希望能直接获取posixPath属性的控制权。 - Kymer
3
问题在于Finder的window类的target属性返回一个folder类的指定符号,该类没有posixPath属性。要获取posixPath,您需要一个alias对象,据我所知,在JXA中无法将folder转换为alias - Patrick Wynne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接