Fun with Ruby and Cocoa

Preview:

DESCRIPTION

Use RubyCocoa and MacRuby to write scripts that take advantage of the Cocoa frameworks.

Citation preview

Ruby and CocoaFacilitate development on OSX

About me

About me

Rapperswil

huesler informatik

huesler informatik

upstream agile

huesler informatik

upstream agile

co-up.de

Ruby and Cocoa

Ruby

passionatehttp://www.flickr.com/photos/gi/378823/sizes/o/

Apple OSX

passionate

http://www.flickr.com/photos/gi/378823/sizes/o/

Landscape

http://www.flickr.com/photos/kiumo/4203883504/sizes/o/

Cocoa

http://www.flickr.com/photos/luder5/4100921399/sizes/l/

Cocoa

Cocoa

• Core Foundation

Cocoa

• Core Foundation

• Appkit

Cocoa

• Core Foundation

• Appkit

• Core * (Audio/Video/Image/Data etc.)

Cocoa

• Core Foundation

• Appkit

• Core * (Audio/Video/Image/Data etc.)

• Scripting bridge

Cocoa

• Core Foundation

• Appkit

• Core * (Audio/Video/Image/Data etc.)

• Scripting bridge

• others

Interface Builder

XCode

Objective-C

NSMutableArray *anArray = [[NSMutableArray alloc] init

];[anArray addObject:@"Element 1"];[anArray addObject:@"Element 2"];[anArray addObject:@"Element 3"];

//Use a for each loop to iterate through the arrayfor (NSString *s in anArray) { NSLog(s);}//Release the array[anArray release]

Ruby And Cocoa

Ruby Cocoa

HotCocoa

picture shamelessly cropped from http://www.slideshare.net/mattetti/macruby-hotcocoa-presentation-by-rich-kilmer

Scripting Bridge

http://www.flickr.com/photos/bensonkua/2851908095/sizes/l/

Good to know

Good to know

• Available commands for each application are in a .sdef file

Good to know

• Available commands for each application are in a .sdef file

• Terminal.app/Contents/Resources/Terminal.sdef

Good to know

• Available commands for each application are in a .sdef file

• Terminal.app/Contents/Resources/Terminal.sdef

• gem rb-appscript (native extension so not for MacRuby)

1 <command name="do script" code="coredosc" 2 description="Runs a UNIX shell script or command."> 3 <cocoa class="TTDoScriptCommand"/> 4 <direct-parameter type="text" 5 description="The command to execute." optional="yes"/> 6 <parameter name="with command" 7 description="Data to be passed to the Terminal..." 8 code="cmnd" optional="yes" hidden="yes"> 9 <cocoa key="Command" /> 10 <type type="text" /> 11 <type type="any" /> <!-- support null case --> 12 </parameter> 13 <parameter name="in" 14 description="The tab in which to execute the command" 15 code="kfil" optional="yes"> 16 <cocoa key="Target" /> 17 <type type="tab" /> 18 <type type="window" /> 19 <type type="any" /> <!-- support null case --> 20 </parameter> 21 <result type="tab" 22 description="The tab the command was executed in." /> 23 </command> 24

Control Terminal

1 #!/usr/bin/env ruby 2 require 'rubygems' 3 require 'appscript' 4 include Appscript 5 6 terminal = app('Terminal') 7 current_window = terminal.windows.first 8 current_tab = current_window.tabs.last 9 process = app("System Events").application_processes[ 10 "Terminal.app" 11 ] 12 13 process.keystroke('t', :using => :command_down) 14 terminal.do_script('top', :in => current_tab)

1 #!/usr/bin/env ruby 2 require 'rubygems' 3 require 'appscript' 4 include Appscript 5 6 terminal = app('Terminal') 7 current_window = terminal.windows.first 8 current_tab = current_window.tabs.last 9 process = app("System Events").application_processes[ 10 "Terminal.app" 11 ] 12 13 process.keystroke('t', :using => :command_down) 14 terminal.do_script('top', :in => current_tab)

1 #!/usr/bin/env ruby 2 require 'rubygems' 3 require 'appscript' 4 include Appscript 5 6 terminal = app('Terminal') 7 current_window = terminal.windows.first 8 current_tab = current_window.tabs.last 9 process = app("System Events").application_processes[ 10 "Terminal.app" 11 ] 12 13 process.keystroke('t', :using => :command_down) 14 terminal.do_script('top', :in => current_tab)

1 #!/usr/bin/env ruby 2 require 'rubygems' 3 require 'appscript' 4 include Appscript 5 6 terminal = app('Terminal') 7 current_window = terminal.windows.first 8 current_tab = current_window.tabs.last 9 process = app("System Events").application_processes[ 10 "Terminal.app" 11 ] 12 13 process.keystroke('t', :using => :command_down) 14 terminal.do_script('top', :in => current_tab)

Problems with MacRuby

Problems with MacRuby

Problems with MacRuby

• Some methods don’t seem to be available

iTunes

1 #!/usr/bin/env macruby 2 3 framework 'ScriptingBridge' 4 5 itunes = SBApplication.applicationWithBundleIdentifier( 6 "com.apple.iTunes" 7 ) 8 library = itunes.sources.objectWithName("Library") 9 10 library.userPlaylists.each do |playlist| 11 puts playlist.name 12 end

1 #!/usr/bin/env macruby 2 3 framework 'ScriptingBridge' 4 5 itunes = SBApplication.applicationWithBundleIdentifier( 6 "com.apple.iTunes" 7 ) 8 library = itunes.sources.objectWithName("Library") 9 10 library.userPlaylists.each do |playlist| 11 puts playlist.name 12 end

1 #!/usr/bin/env macruby 2 3 framework 'ScriptingBridge' 4 5 itunes = SBApplication.applicationWithBundleIdentifier( 6 "com.apple.iTunes" 7 ) 8 library = itunes.sources.objectWithName("Library") 9 10 library.userPlaylists.each do |playlist| 11 puts playlist.name 12 end

Core Location

1 #!/usr/bin/env macruby 2 framework 'CoreLocation' 3 4 loc = CLLocationManager.alloc.init 5 loc.delegate = self 6 loc.startUpdatingLocation 7 8 # keep the script running 9 NSRunLoop.currentRunLoop.runUntilDate( 10 NSDate.distantFuture 11 )

1 #!/usr/bin/env macruby 2 framework 'CoreLocation' 3 4 loc = CLLocationManager.alloc.init 5 loc.delegate = self 6 loc.startUpdatingLocation 7 8 # keep the script running 9 NSRunLoop.currentRunLoop.runUntilDate( 10 NSDate.distantFuture 11 )

1 def locationManager( 2 manager, 3 didUpdateToLocation: new_location, 4 fromLocation: old_location 5 ) 6 7 puts "loc: #{new_location.description}" 8 end

1 #!/usr/bin/env macruby 2 framework 'CoreLocation' 3 4 loc = CLLocationManager.alloc.init 5 loc.delegate = self 6 loc.startUpdatingLocation 7 8 # keep the script running 9 NSRunLoop.currentRunLoop.runUntilDate( 10 NSDate.distantFuture 11 )

1 #!/usr/bin/env macruby 2 framework 'CoreLocation' 3 4 def locationManager(manager, 5 didUpdateToLocation: new_location, 6 fromLocation: old_location 7 ) 8 puts "location: #{new_location.description}" 9 end 10 11 loc = CLLocationManager.alloc.init 12 loc.delegate = self 13 loc.startUpdatingLocation 14 15 # keep the script running 16 NSRunLoop.currentRunLoop.runUntilDate( 17 NSDate.distantFuture 18 )

Grand Central Dispatch

Grand Central Dispatch

Grand Central Dispatch

• MacRuby only

Grand Central Dispatch

• MacRuby only

• Synchronous

Grand Central Dispatch

• MacRuby only

• Synchronous

• Asynchronous

Grand Central Dispatch

• MacRuby only

• Synchronous

• Asynchronous

• Parallel

Grand Central Dispatch

• MacRuby only

• Synchronous

• Asynchronous

• Parallel

• Synchronization

Asynchronous

1 #!/usr/bin/env macruby 2 3 queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 7 queue.async do 8 puts 'Starting asyn. NONE BLOCKING!' 9 sleep 2.00 10 puts "Finished asyn" 11 end 12 puts "code not being blocked" 13 14 NSRunLoop.currentRunLoop.runUntilDate( 15 NSDate.distantFuture 16 ) 17

1 #!/usr/bin/env macruby 2 3 queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 7 queue.async do 8 puts 'Starting asyn. NONE BLOCKING!' 9 sleep 2.00 10 puts "Finished asyn" 11 end 12 puts "code not being blocked" 13 14 NSRunLoop.currentRunLoop.runUntilDate( 15 NSDate.distantFuture 16 ) 17

1 #!/usr/bin/env macruby 2 3 queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 7 queue.async do 8 puts 'Starting asyn. NONE BLOCKING!' 9 sleep 2.00 10 puts "Finished asyn" 11 end 12 puts "code not being blocked" 13 14 NSRunLoop.currentRunLoop.runUntilDate( 15 NSDate.distantFuture 16 ) 17

Synchronous

1 #!/usr/bin/env macruby 2 3 queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 7 queue.sync do 8 puts 'Starting sync. BLOCKING!' 9 sleep 3.0 10 puts 'Finished sync' 11 end 12 puts "code being blocked" 13 14 NSRunLoop.currentRunLoop.runUntilDate( 15 NSDate.distantFuture 16 ) 17

Synchronized

1 #!/usr/bin/env macruby 2 3 worker_queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 group = Dispatch::Group.new 7 8 0.upto(10) do |i| 9 puts "Dispatch #{i} to GCD" 10 worker_queue.async(group) do 11 puts "working on #{i}" 12 end 13 end 14 puts "waiting for gcd" 15 group.wait 16 puts "done" 17

1 #!/usr/bin/env macruby 2 3 worker_queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 group = Dispatch::Group.new 7 8 0.upto(10) do |i| 9 puts "Dispatch #{i} to GCD" 10 worker_queue.async(group) do 11 puts "working on #{i}" 12 end 13 end 14 puts "waiting for gcd" 15 group.wait 16 puts "done" 17

1 #!/usr/bin/env macruby 2 3 worker_queue = Dispatch::Queue.new( 4 'ch.huesler-informatik.scotrubyconf.gcd' 5 ) 6 group = Dispatch::Group.new 7 8 0.upto(10) do |i| 9 puts "Dispatch #{i} to GCD" 10 worker_queue.async(group) do 11 puts "working on #{i}" 12 end 13 end 14 puts "waiting for gcd" 15 group.wait 16 puts "done" 17

Concurrent

1 #!/usr/bin/env macruby 2 3 group = Dispatch::Group.new 4 result = [] 5 1.upto(10).each do |i| 6 Dispatch::Queue.concurrent.async(group) do 7 sleep 2 8 result << i 9 end 10 end 11 group.wait 12 puts result.inspect

.plist files

1 #!/usr/bin/env ruby 2 require "osx/cocoa" 3 include OSX 4 5 file_name = 'Info.plist' 6 plist = NSDictionary.dictionaryWithContentsOfFile( 7 file_name 8 ) 9 10 plist['CFBundleVersion'] = '2.0.1' 11 12 plist.writeToFile_atomically( 13 file_name, 14 true 15 )

1 #!/usr/bin/env ruby 2 require "osx/cocoa" 3 include OSX 4 5 file_name = 'Info.plist' 6 plist = NSDictionary.dictionaryWithContentsOfFile( 7 file_name 8 ) 9 10 plist['CFBundleVersion'] = '2.0.1' 11 12 plist.writeToFile_atomically( 13 file_name, 14 true 15 )

1 #!/usr/bin/env ruby 2 require "osx/cocoa" 3 include OSX 4 5 file_name = 'Info.plist' 6 plist = NSDictionary.dictionaryWithContentsOfFile( 7 file_name 8 ) 9 10 plist['CFBundleVersion'] = '2.0.1' 11 12 plist.writeToFile_atomically( 13 file_name, 14 true 15 )

Keychain Access

Details

Details

• MacRuby has issues with void pointer (patch pending)

Details

• MacRuby has issues with void pointer (patch pending)

• Use objective c wrapper instead (dynlib or bundle)

Details

• MacRuby has issues with void pointer (patch pending)

• Use objective c wrapper instead (dynlib or bundle)

• Generate metadata to make it work

Details

• MacRuby has issues with void pointer (patch pending)

• Use objective c wrapper instead (dynlib or bundle)

• Generate metadata to make it work

1 export FILE_PATH = ~/Library/BridgeSupport 2 export FRAMEWORK_PATH = ~/Library/BridgeSupport/Security.bridgesupport 3 mkdir $FILE_PATH 4 gen_bridge_metadata -f Security -o $FRAMEWORK_PATH

1 require 'osx/cocoa' 2 include OSX 3 require_framework 'Security' 4 5 # Set up some relevant variables 6 7 service = "ch.huesler-informatik.scotrubyconf.keychain" 8 account = "Highlander" 9 original_password = "Rrrueby" 10 11 # Add password 12 SecKeychainAddGenericPassword( 13 nil, 14 service.length, 15 service, 16 account.length, 17 account, 18 original_password.length, 19 original_password, 20 nil 21 )

1 require 'osx/cocoa' 2 include OSX 3 require_framework 'Security' 4 5 # Set up some relevant variables 6 7 service = "ch.huesler-informatik.scotrubyconf.keychain" 8 account = "Highlander" 9 original_password = "Rrrueby" 10 11 # Add password 12 SecKeychainAddGenericPassword( 13 nil, 14 service.length, 15 service, 16 account.length, 17 account, 18 original_password.length, 19 original_password, 20 nil 21 )

1 # Add password 2 SecKeychainAddGenericPassword( 3 nil, 4 service.length, 5 service, 6 account.length, 7 account, 8 original_password.length, 9 original_password, 10 nil 11 )

1 # Query the keychain 2 status, *password = SecKeychainFindGenericPassword( 3 nil, 4 service.length, 5 service, 6 account.length, 7 account 8 )

1 # Password-related data. Shifting pointers 2 length = password.shift 3 data = password.shift 4 plain_password = data.bytestr(length) 5 6 puts "Password: #{plain_password}"

That’s all!Questions?

Recommended