Ruby and CocoaFacilitate development on OSX
About me
About me
Rapperswil
http://www.flickr.com/photos/turtlemom_nancy/4026208166/sizes/l/
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?