Scripting Sparkle Appcasts with Feeder and RubyCocoa
This article will demonstrate how easy it is to use RubyCocoa and the ScriptingBridge to talk to scriptable applications. The example I’ve used is using the Feeder (RSS Feed Creator) application to create (and optionally publish) updates to a Sparkle Appcast Feed.
Background
I spent some time last night looking into the ScriptingBridge (new in Leopard), which lets you talk to scriptable applications from Objective-C, Ruby and Python (rather than piping AppleScript through NSAppleScript).
The goal was to translate the following AppleScript (which creates and publishes a new feed item through Feeder’s scriptable interface):
tell application "Feeder"
set theFeed to selected feed
set episodeTitle to "This Episode's Title"
set episodeLink to "http://example.com/epsiodelink.html"
set enclosureFile to "Users:yourname:Desktop:Episode1.mp3"
make new feed item at theFeed with properties {feed:theFeed, item title:episodeTitle, item link:episodeLink, enclosure upload file:enclosureFile}
publish theFeed
end tell
NOTE: Sourced from AppleScript Examples?
Required Reading
I had to trawl through a number of documents to get my head around the topic, as I’d never used AppleScript in the past. These are the key references:
- Using Scripting Bridge in PyObjC and RubyCocoa Code
- Scripting Bridge Release Notes for Mac OS X v10.5
Where’s the API?
The first document above is a good starting point, but it only got me so far:
include OSX
OSX.require_framework 'ScriptingBridge'
feeder = SBApplication.applicationWithBundleIdentifier_("com.reinvented.Feeder")
I didn’t know what to do next. Where was the API documentation..?
It turns out there’s two ways you can find out what APIs are supported.
#1: Script Editor
The Script Editor, located in /Applications/AppleScript/ can load up the API information, which provides the following:
NOTE: To access this in Script Editor, File->Open Dictionary and select Feeder.
#2: Scripting Definition Extractor (sdef)
If you prefer to read the API in a C Header format, you can easily extract it using a tool (sdef) mentioned in the Scripting Bridge release notes. Here’s how to extract Feeder’s scripting interface.
sdef /Applications/Feeder.app | sdp -fh --basename Feeder
This will dump the interface into Feeder.h in your current directory.
Here’s an excerpt of what Feeder.h looks like:
/*
* Feeder.h
*/
#import <AppKit/AppKit.h>
#import <ScriptingBridge/ScriptingBridge.h>
@class FeederApplication, FeederItem, FeederColor, FeederDocument, FeederWindow, FeederRichText, FeederCharacter, FeederParagraph, FeederWord, FeederAttributeRun, FeederAttachment, FeederApplication, FeederFolderItem, FeederFolder, FeederFeed, FeederFeedItem, FeederITunesOwner, FeederITunesDuration, FeederCategory;
typedef enum {
FeederSaveOptionsYes = 'yes ' /* Save the file. */,
FeederSaveOptionsNo = 'no ' /* Do not save the file. */,
FeederSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */
} FeederSaveOptions;
/*
* Standard Suite
*/
// The application's top-level scripting object.
@interface FeederApplication : SBApplication
- (SBElementArray *) windows;
@property (copy, readonly) NSString *name; // The name of the application.
@property (readonly) BOOL frontmost; // Is this the frontmost (active) application?
@property (copy, readonly) NSString *version; // The version of the application.
- (void) quitSaving:(FeederSaveOptions)saving; // Quit the application.
@end
// A scriptable object.
@interface FeederItem : SBObject
@property (copy) NSDictionary *properties; // All of the object's properties.
- (void) closeSaving:(FeederSaveOptions)saving savingIn:(NSURL *)savingIn; // Close a document.
- (void) saveIn:(NSURL *)in_ as:(NSString *)as; // Save a document.
- (void) delete; // Delete an object.
- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy object(s) and put the copies at a new location.
- (void) moveTo:(SBObject *)to; // Move object(s) to a new location.
- (BOOL) exists; // Verify if an object exists.
@end
Example Code
This code creates a new item to a Sparkle appcast, and automatically adds the file specified
#!/usr/bin/env ruby
#
# NOTE: Make sure the selected Feed in Feeder is the one you want to add to
#
# --file needs to be full path to file
# --shortVersion is the human readable / marketing version - aka 1.0, 2.0, etc
# --longVersion is what sparkle checks... aka 1.0_123
#
# www.nickbrawn.com // Nick Brawn
require 'optparse'
require 'osx/cocoa'
options = {}
def feeder(filename, shortVersion, longVersion)
include OSX
OSX.require_framework 'ScriptingBridge'
feeder = SBApplication.applicationWithBundleIdentifier_("com.reinvented.Feeder")
selectedFeed = feeder.selectedFeed
p = {}
p["itemTitle"] = "New Build Available (#{longVersion})"
p['enclosureUploadFile'] = filename
p['feed'] = selectedFeed
p['sparkleVersion'] = longVersion
p['sparkleShortVersion'] = shortVersion
newItem = feeder.classForScriptingClass_("feed item").alloc.initWithProperties_(p)
feeder.selectedFeed.feedItems.addObject_(newItem)
feeder.activate # use this if you want to edit prior to publishing
#feeder.selectedFeed.publish # use this for automatic publishing
end
opts = OptionParser.new
opts.on("--file=ARG", String) do |v|
options[:file] = v
end
opts.on("--shortVersion=ARG", String) do |v|
options[:short] = v
end
opts.on("--longVersion=ARG", String) do |v|
options[:long] = v
end
rest = opts.parse(ARGV)
feeder(options[:file],options[:short],options[:long])
NOTE: This code has been added to the xcode-build-scripts repository (feeder-appcast-update.rb)

[...] Link: Scripting Sparkle Appcasts with Feeder and RubyCocoa [...]