Scripting Sparkle Appcasts with Feeder and RubyCocoa

Posted on August 2, 2008 by Nick

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:

  1. Using Scripting Bridge in PyObjC and RubyCocoa Code
  2. 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:

Script Editor Window showing Feeder's Scriptable API

Script Editor Window showing Feeder's Scriptable API

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)

Comments

Leave a Reply

Name

Email

Website