Fun with Ruby and Cocoa

4,105 views
3,843 views

Published on

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

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,105
On SlideShare
0
From Embeds
0
Number of Embeds
609
Actions
Shares
0
Downloads
29
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Fun with Ruby and Cocoa

  1. 1. Ruby and Cocoa Facilitate development on OSX
  2. 2. About me
  3. 3. About me
  4. 4. Rapperswil
  5. 5. http://www.flickr.com/photos/turtlemom_nancy/4026208166/sizes/l/
  6. 6. huesler informatik
  7. 7. huesler informatik upstream agile
  8. 8. huesler informatik upstream agile co-up.de
  9. 9. Ruby and Cocoa
  10. 10. Ruby
  11. 11. passionate http://www.flickr.com/photos/gi/378823/sizes/o/
  12. 12. Apple OSX
  13. 13. passionate http://www.flickr.com/photos/gi/378823/sizes/o/
  14. 14. Landscape http://www.flickr.com/photos/kiumo/4203883504/sizes/o/
  15. 15. Cocoa http://www.flickr.com/photos/luder5/4100921399/sizes/l/
  16. 16. Cocoa
  17. 17. Cocoa • Core Foundation
  18. 18. Cocoa • Core Foundation • Appkit
  19. 19. Cocoa • Core Foundation • Appkit • Core * (Audio/Video/Image/Data etc.)
  20. 20. Cocoa • Core Foundation • Appkit • Core * (Audio/Video/Image/Data etc.) • Scripting bridge
  21. 21. Cocoa • Core Foundation • Appkit • Core * (Audio/Video/Image/Data etc.) • Scripting bridge • others
  22. 22. Interface Builder
  23. 23. XCode
  24. 24. Objective-C
  25. 25. 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 array for (NSString *s in anArray) { NSLog(s); } //Release the array [anArray release]
  26. 26. Ruby And Cocoa
  27. 27. Ruby Cocoa
  28. 28. HotCocoa picture shamelessly cropped from http://www.slideshare.net/mattetti/macruby-hotcocoa-presentation-by-rich-kilmer
  29. 29. Scripting Bridge http://www.flickr.com/photos/bensonkua/2851908095/sizes/l/
  30. 30. Good to know
  31. 31. Good to know • Available commands for each application are in a .sdef file
  32. 32. Good to know • Available commands for each application are in a .sdef file • Terminal.app/Contents/Resources/ Terminal.sdef
  33. 33. 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)
  34. 34. 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
  35. 35. Control Terminal
  36. 36. 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)
  37. 37. 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)
  38. 38. 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)
  39. 39. 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)
  40. 40. Problems with MacRuby
  41. 41. Problems with MacRuby
  42. 42. Problems with MacRuby • Some methods don’t seem to be available
  43. 43. iTunes
  44. 44. 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
  45. 45. 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
  46. 46. 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
  47. 47. Core Location
  48. 48. 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 )
  49. 49. 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 )
  50. 50. 1 def locationManager( 2 manager, 3 didUpdateToLocation: new_location, 4 fromLocation: old_location 5 ) 6 7 puts "loc: #{new_location.description}" 8 end
  51. 51. 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 )
  52. 52. 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 )
  53. 53. Grand Central Dispatch
  54. 54. Grand Central Dispatch
  55. 55. Grand Central Dispatch • MacRuby only
  56. 56. Grand Central Dispatch • MacRuby only • Synchronous
  57. 57. Grand Central Dispatch • MacRuby only • Synchronous • Asynchronous
  58. 58. Grand Central Dispatch • MacRuby only • Synchronous • Asynchronous • Parallel
  59. 59. Grand Central Dispatch • MacRuby only • Synchronous • Asynchronous • Parallel • Synchronization
  60. 60. Asynchronous
  61. 61. 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
  62. 62. 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
  63. 63. 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
  64. 64. Synchronous
  65. 65. 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
  66. 66. Synchronized
  67. 67. 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
  68. 68. 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
  69. 69. 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
  70. 70. Concurrent
  71. 71. 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
  72. 72. .plist files
  73. 73. 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 )
  74. 74. 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 )
  75. 75. 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 )
  76. 76. Keychain Access
  77. 77. Details
  78. 78. Details • MacRuby has issues with void pointer (patch pending)
  79. 79. Details • MacRuby has issues with void pointer (patch pending) • Use objective c wrapper instead (dynlib or bundle)
  80. 80. Details • MacRuby has issues with void pointer (patch pending) • Use objective c wrapper instead (dynlib or bundle) • Generate metadata to make it work
  81. 81. Details • MacRuby has issues with void pointer (patch pending) • Use objective c wrapper instead (dynlib or bundle) • Generate metadata to make it work
  82. 82. 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
  83. 83. 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 )
  84. 84. 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 )
  85. 85. 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 )
  86. 86. 1 # Query the keychain 2 status, *password = SecKeychainFindGenericPassword( 3 nil, 4 service.length, 5 service, 6 account.length, 7 account 8 )
  87. 87. 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}"
  88. 88. That’s all! Questions?

×