cocoaconf dc - automate with swift - tony ingraldi
TRANSCRIPT
Automate with SwiftCocoaConf DC, April 10, 2015
Tony Ingraldi
GitHub: tingraldi • Twitter: @TonyIngraldi
Why Automate?
• Improve productivity
• Avoid repetitive tasks
• Streamline workflows
• Opportunities for experimentation
• Add missing features to OS X apps!
Open Scripting Architecture• Introduced way back in October 1993
• The state of the Mac looked like this
Beige!
Open Scripting Architecture
• Survived the transition to OS X
• Leveraged by OS X to launch applications, open documents, etc.
• A standard mechanism for interapplication communication
Language Options
• AppleScript (there from the beginning)
• JavaScript for Automation (as of Yosemite)
• Python (PyObjC, py-appscript)
• Ruby (RubyCocoa, RubyOSA)
• And now Swift!
Language Options
• AppleScript (there from the beginning)
• JavaScript for Automation (as of Yosemite)
• Python (PyObjC, py-appscript)
• Ruby (RubyCocoa, RubyOSA)
• And now Swift!
AppleScript
• A “natural language” programming model
• Primarily geared toward being the “glue” between apps
• Limited inherent functionality
AppleScript
• Can be frustrating to people who are fluent in “normal” programming languages
• Verbosity is a hallmark
set fileName to "name.jpg"
set sansExtension to text 1 thru ((offset of "." in fileName) - 1) of fileName
Swift• A “primary” language
• Ready access to Cocoa
• Can run Swift scripts using #! convention
• A familiar coding style
let fileName = "name.jpg"
let sansExtension = fileName.stringByDeletingPathExtension
Scripting Bridge
• Introduced in Mac OS X Leopard (version 10.5)
• Provides high-level Objective-C access to scriptable applications
• Can be leveraged in Swift, with a bit of extra work
Scripting Bridge Recipe
• 1 part sdef
• 1 part sdp
• A pinch of manual intervention
• Automate to taste
Scripting Definition
• Scriptable applications include resources that describe their scripting interface
• Can be in a variety of forms
• The sdef command-line utility extracts the scripting definition
• Writes to standard output in XML format
Scripting Definition
• Using sdef
sdef /path/to/App.app > App.sdef
• For the most part, using sdef is “run and forget”
• Until you find out what’s wrong when you run sdp
• Some sdp warnings can be ignored
Kicking the Tires
• Xcode
• Swift and ObjC command line targets
• Includes the header generated by sdp
• Experiment in main.swift and main.m
Don’t Run with Scissors• Using Objective-C header leads to pervasive AnyObject typing
• Works, but can lead to ambiguity
• For properties, sometimes have to resort to using valueForKey
• Sometimes leads to awkward method invocation
An Alternative
• Create Swift protocols that cover the generated Objective-C API
• 👍 Supports rich set of types
• 👍 API translation can be automated
• 👎 Requires optional declaration of all properties and methods
Objective-C Excerpt
@interface AcornApplication : SBApplication
@property (copy, readonly) NSString *name;
- (SBElementArray *) windows; - (NSString *) taunt;
@end
SBApplication
@objc public protocol SBApplicationProtocol: SBObjectProtocol {
func activate() var delegate:SBApplicationDelegate! { get set }
}
Application Protocol
@objc public protocol AcornApplication: SBApplicationProtocol {
optional var name: String { get } optional func windows() -> SBElementArray
optional func taunt() -> String
} extension SBApplication : AcornApplication {}
Putting it Togetherimport ScriptingBridge
@objc public protocol AcornApplication : … {…} extension SBApplication : AcornApplication {}
let acorn = SBApplication .applicationWithBundleIdentifier( “com.flyingmeat.Acorn4" ) as! AcornApplication
acorn.taunt!()
Plan of Attack
• Create frameworks for target applications
• Install in /Library/Frameworks
• Write scripts instead of compiled code
By the Way• Playgrounds don’t fit well
• The Swift REPL could spontaneously combust at any moment
• What’s a scripter to do?
• Use a text editor
• Edit, run, repeat
Script Invocation
• Hash-bang on the first line
#!/usr/bin/xcrun swift -F /Library/Frameworks
• chmod +x SomeScript.swift
A Little Help
https://github.com/tingraldi/SwiftScripting
• Header conversion utilities
• Sample Application Scripting Frameworks
• Sample Scripts
• Scripting Utilities Framework
A Little More Helpimport ScriptingBridge
@objc public protocol AcornApplication {…} extension SBApplication : AcornApplication {}
let acorn = SBApplication .applicationWithBundleIdentifier( "com.flyingmeat.Acorn4" ) as! AcornApplication
acorn.taunt!()
A Little More Help
import ScriptingBridge import AcornScripting // Acorn Swift protocols
let acorn = SBApplication .applicationWithBundleIdentifier( "com.flyingmeat.Acorn4" ) as! AcornApplication
acorn.taunt!()
A Little More Help
import ScriptingUtilities // Helper framework import AcornScripting // Acorn Swift protocols
let acorn = Application(name: "Acorn") as! AcornApplication
acorn.taunt!()
Where to Put Scripts?
• Short answer: anywhere
• Some applications have special script folders
• Scripting Menu
• Invoke via an Automator Service