【问题标题】:Avoiding AppleScript through Ruby: rb-appscript or rubyosa?通过 Ruby 避免 AppleScript:rb-appscript 还是 ruby​​osa?
【发布时间】:2010-11-21 13:13:14
【问题描述】:

大家好,Mac ruby​​ 爱好者和 AppleScript 仇恨者,

对于那些同时使用 ruby​​osa 和 rb-appscript 的人,我想听听它们各自的优缺点,你决定坚持使用哪个,以及你会推荐哪个完全不-AppleScript 精通红宝石的老前辈。另外,还有其他我错过的选择吗?

顺便说一句,我们也欢迎任何有关 AppleScript 方面的技巧(例如浏览字典等)。

查看一些示例代码也很有帮助。

【问题讨论】:

    标签: ruby macos applescript rb-appscript rubyosa


    【解决方案1】:

    Apple 通过一个名为“Scripting Bridge”的框架包括对 Cocoa 兼容语言的脚本支持。我通过 RubyCocoa/MacRuby 使用它来满足我的脚本需求。它包含在包装盒中,因此非常方便。

    require 'osx/cocoa'
    require_framework 'ScriptingBridge'
    iTunes = SBApplication.applicationWithBundleIdentifier 'com.apple.iTunes'
    puts iTunes.selection.name
    

    我发现 Scripting Bridge 的唯一主要烦恼是您必须使用类似的捆绑 ID 而不是名称,但这对我来说并不是什么大问题。它也只包含在 10.5 中,因此如果您需要 Panther 或 Tiger 支持,您将需要其他之一。

    在另外两个中,rb-appscript 仍在积极开发中,而 RubyOSA 在几年前被有效冻结,所以我可能会选择前者。由于 Ruby 2、MacRuby 和其他新的实现带来了语言的变化,rb-appscript 更有可能在未来工作。否则它们非常相似。我本质上将 rb-appscript 视为 RubyOSA 的更新版本,尽管它在技术上并不正确。

    【讨论】:

    • 很好,但现在我很好奇脚本桥与applescript 的比较。我想我会读一些书。
    • 我不确定您所说的“与 Applescript 相比如何”是什么意思。它以不同的语言提供相同的功能。
    【解决方案2】:

    我没有尝试过 RubyOSA,但我在 rb-appscript 上取得了巨大的成功。它对我来说非常有效,而且比直接使用 AppleScript 要好得多。

    你见过this thread comparing the two吗?它有一个很好的详细答案,指出了差异。

    【讨论】:

    • 使用 rb-appscript。 rb-appscript 的开发者是 AppleScript 和底层 OSA 架构方面的全球专家之一。 rb-appscript 建立在他多年的经验之上,包括 appscript 的原始 Python 版本。他提供的支持非常出色。
    【解决方案3】:

    问号:

    很好,但现在我很好奇 脚本桥的比较 小程序。我想我会有一些 读书做事。

    SB 省略了 AppleScript 中的一些功能。例如,以下脚本将所有文件从桌面移动到 Documents 文件夹:

    tell application "Finder"
       move every file of desktop to folder "Documents" of home
    end tell
    

    在 SB 中,SBElementArray 类严重限制了您将单个命令应用于多个对象的能力,因此您要么必须求助于低级 API,要么获取单个文件引用的列表并一次移动一个:

    require 'osx/cocoa'; include OSX
    require_framework 'ScriptingBridge'
    
    finder = SBApplication.applicationWithBundleIdentifier('com.apple.finder')
    destination = finder.home.folders.objectWithName('Documents')
    finder.desktop.files.get.each do |f|
       f.moveTo_replacing_positionedAt_routingSuppressed(destination, nil, nil, nil)
    end
    

    在 rb-appscript 中,您将使用与 AppleScript 相同的方法:

    require 'appscript'; include Appscript
    
    app("Finder").desktop.files.move(:to => app.home.folders["Documents"])
    

    ...

    SB 比 AppleScript 更严重地混淆了 Apple 事件机制。 AppleScript 可能会让您难以理解,奇怪的语法、关键字冲突的倾向等等,但除此之外,它主要按原样呈现 Apple 事件。 AS 中唯一真正重要的魔法是它的“隐式获取”行为,当它评估一个不作为命令参数出现的文字引用时。 AppleScript 最大的罪过是它的文档没有更好地解释它的实际工作原理,但是有一个 very good paper by William Cook 可以很好地说明实际发生的情况。

    另一方面,SB 最难假装它是一个真正的 Cocoa API,具有 Cocoa 风格的行为,因此层层叠加了大量的魔法。结果是表面上吸引了 Cocoa 开发人员,但是一旦这些抽象开始泄漏 - 正如抽象总是如此 - 你就完全无法理解正在发生的事情。例如,SBElementArray 声称是一个数组——它甚至是 NSMutableArray 的子类——但是当你真正尝试使用它的数组方法时,其中一半有效,一半无效。事实上,它根本不是一个真正的数组。它是一个未评估的 Apple 事件对象说明符的包装器,伪装成 NSMutableArray。因此,当它执行类似非数组的操作时,您在很大程度上无法理解为什么。而且,正如 #1 中提到的,这些厚抽象中的一些使得难以访问底层的标准 Apple 事件功能。

    SB 首先尝试成为一个好的 Cocoa API,而不是一个好的 Apple 事件 API,结果两者都不太擅长。

    顺便说一句,Appscript 遵循 AppleScript 的领导并采取了相反的方法:正确处理 Apple 事件,然后担心适应宿主语言。这就是为什么有些人更喜欢 RubyOSA 而不是 rb-appscript;虽然 appscript 是功能更强大的解决方案,但如果您有大量面向对象的背景,那会感觉很奇怪。这是因为 Apple 事件使用基于 RPC-plus-query 的范例,并且任何与 OOP 相似的 appscript 可能都是纯语法。最接近的类比是通过 XML-RPC 发送 XQueries,这需要一些时间来适应。

    ...

    SB 往往比 AppleScript 遭受更多的应用程序兼容性问题。

    其中一些问题是由于 SB 将其自己的想法强加于 Apple 事件 IPC应该在其实际如何工作的基础上工作。例如,SB 创建了一组 [pseudo] 代理类,表示字典中定义的类;然后,它主要基于经典的面向对象的行为规则,对您如何与这些对象进行交互施加了各种限制。

    例如,以下脚本获取 Documents 文件夹的所有子文件夹的名称:

    tell application "Finder"
       get name of every folder of entire contents of folder "Documents" of home
    end tell
    

    如果您在 SB 中尝试相同的方法:

    finder.home.folders.objectWithName('Documents').entireContents.folders.arrayByApplyingSelector(:name)
    

    它到达#folders 方法,然后抛出错误,因为Finder 字典中的“整个内容”属性的类型被声明为“引用”。由于字典中没有定义“文件夹”元素的“参考”类,SB 不允许您构建特定查询(除非您想下拉到低级 API 并使用原始 AE 代码)。根据 Apple 事件规则,这是完全合法的,但不符合 SB 强加的以 OO 为中心的狭窄规则集。

    其他错误是由于 SB 假设可编写脚本的应用程序将如何实现某些命令和其他功能。例如:

    tell application "iTunes"
       make new playlist with properties {name:"test 1"}
    end tell
    

    SB 不允许您利用 iTunes 提供的任何快捷方式(您可以省略对要在其中创建播放列表的源对象的引用,在这种情况下使用主“库”源),所以让我们完整地写出来以便更好地比较:

    tell application "iTunes"
       make new playlist at source "Library" with properties {name:"test"}
    end tell
    

    在 SB 你可以这样写:

    itunes = SBApplication.applicationWithBundleIdentifier('com.apple.itunes')
    
    playlists = itunes.sources.objectAtIndex(0).playlists()
    newplaylist = itunes.classForScriptingClass(:playlist).alloc().initWithProperties({:name => 'test'})
    playlists.addObject(newplaylist)
    

    当你运行它时,它会在#addObject 上产生错误。在尝试将单个“make”命令转换为多行练习时,SB 假设“at”参数将始终是表单“ 的结尾”的引用,这就是 Cocoa Scripting基于应用程序可以做到这一点。但是,Carbon 应用程序没有用于实现 Apple 事件支持的单一标准框架,因此它们的要求往往会有所不同。例如,iTunes 需要对容器对象的引用,在本例中为“源“库””,并且当 SB 传递“源“库”的播放列表的结尾”时,它不喜欢它。很多 AppleScriptable 应用程序就是这样,但 SB 在决定“面向对象”时忽略了这一现实。

    当应用程序字典不是 100% 准确或详尽详尽时,会导致更多问题。 aete 和 sdef 格式都不允许您 100% 详细地描述应用程序的脚本接口是如何工作的;有些事情只需要用户猜测,或在补充文档中描述 - Finder 的“整个内容”属性的性质就是一个例子。其他信息,例如哪些对象类可以是哪些其他对象类的元素,以及每个属性的类型是什么,AppleScript 本身从未实际使用过——它仅作为用户文档存在。由于 AppleScript 不依赖此信息,因此在针对 AppleScript 测试应用程序的脚本支持时会遗漏任何错误,因为尽管如此,脚本仍然可以正常工作。 SB 确实使用了该信息,因此任何拼写错误都会导致功能丢失或损坏,必须通过再次下拉到低级 API 来规避这些功能。

    顺便说一句,Appscript 也不是 100% 'AppleScript-compliant',但它确实更接近了。 appscript 的早期版本还试图对 Apple 事件施加各种 OO 规则,例如强制执行字典定义的对象模型,但在遇到应用程序不兼容一年后,我收集了所有“聪明”的代码,并在接下来的几年里试图黑盒对 AppleScript 的内部机制进行逆向工程,并使 appscript 尽可能地模拟它们。换句话说,“如果你不能打败他们(你不能),那就加入他们”。在 appscript 确实遇到兼容性问题的地方,通常有一些方法可以解决它,包括翻转内部兼容性设置、将应用程序术语导出到模块、手动修补它并改用它,或者下降到它的低级原始 AE 代码API。

    ...

    FWIW,我还应该插入一些相关的 appscript 好东西。

    首先,appscript 网站上的 ASDictionary 和 ASTranslate 工具是您的朋友。 ASDictionary 将以 appscript 样式的 HTML 格式导出应用程序字典,并启用 rb-appscript 中内置的#help 方法;非常适合在 irb 中进行交互式开发。 ASTranslate 将采用 AppleScript 命令并(有错误愿意)以 appscript 语法返回等效命令。

    其次,rb-appscript 的源代码分发包含文档和示例脚本。如果您安装 appscript gem,请记住同时获取这些资源的 zip 分发包。

    第三,Matt Neuburg has written a book about rb-appscript。如果您正在考虑使用 rb-appscript,请阅读它。不管你最终决定什么,去阅读库克博士的论文吧。

    ...

    无论如何,希望对您有所帮助。 (哦,抱歉,篇幅较长,但我这周刚写了大约 25000 字,所以这只是一些轻松的放松。)

    附言内德,你闪亮的美元在邮局里。 ;)

    【讨论】:

    • 我不清楚“每个文件”和“files.each”之间的区别——它们不是都将所有文件从一个位置迭代移动到另一个位置吗?
    • 不同之处在于谁进行迭代。使用 files.each,您将发送一个单独的 Apple 事件来操作每个元素。使用“每个文件”,您将向应用程序发送一个事件,说“在您结束时处理所有文件”。 Apple 事件 IPC 最初是为 System 7 设计的,其中上下文切换非常昂贵。作为补偿,AppleScript 设计者允许您对每个事件执行多个操作。虽然 AE IPC 在 OS X 中要便宜得多,但它仍然进行了优化,以便使用更少的事件更有效地执行更多操作。如果您要处理数百个对象,这会有所不同。
    • 这个答案应该刻在石头上,或者至少变成一篇博文。
    【解决方案4】:

    简答:rb-appscript。

    因为 Scripting Bridge 似乎一团糟,而 RubyOSA 已经停产。

    【讨论】:

    • rb-appscript 也是如此。 (不过已经过了 3 年。)
    • 是的,很遗憾。我喜欢 rb-appscript。
    猜你喜欢
    • 1970-01-01
    • 2015-01-23
    • 2020-02-10
    • 2011-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-21
    相关资源
    最近更新 更多