# Assumes ruby class is only one in file that calls ib_outlet / ib_action
require 'osx/cocoa'
include OSX
$outlets = []
$actions = []
class OSX::NSObject
class << self
def ns_outlets(*args)
args.each do |arg|
puts "found outlet #{arg}"
$outlets << arg
end
end
alias_method :ns_outlet, :ns_outlets
alias_method :ib_outlet, :ns_outlets
alias_method :ib_outlets, :ns_outlets
def ns_actions(*args)
args.each do |arg|
puts "found action #{arg}"
$actions << arg
end
end
alias_method :ns_action, :ns_actions
alias_method :ib_action, :ns_actions
alias_method :ib_actions, :ns_actions
end
end
class ClassesNibUpdater
def self.update_nib(nib_path, ruby_class, ruby_file)
updater = new
plist = updater.parse_plist(nib_path)
ruby_class_plist = updater.find_ruby_class(plist, ruby_class)
updater.find_outlets_and_actions(ruby_file)
updater.update_superclass(ruby_class, ruby_class_plist)
updater.add_outlets_and_actions_to_plist(ruby_class_plist)
updater.write_plist_data(nib_path, plist)
end
def parse_plist(nib_path)
puts "Parsing: #{nib_path}/classes.nib"
plist_data = NSData.alloc.initWithContentsOfFile("#{nib_path}/classes.nib")
plist, format = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription(plist_data, NSPropertyListMutableContainersAndLeaves)
plist
end
def find_ruby_class(plist, ruby_class)
puts "Looking for #{ruby_class} in plist"
# be nice if NSDictionary had the same methods as hash
ruby_class_plist = nil
plist['IBClasses'].each do |klass|
next unless klass['CLASS'].to_s == ruby_class
ruby_class_plist = klass
end
if ruby_class_plist.nil?
puts "Didn't find #{ruby_class} in plist, creating dictionary"
# didn't find one, create a new one
ruby_class_plist = NSMutableDictionary.alloc.init
ruby_class_plist['CLASS'] = ruby_class
ruby_class_plist['LANGUAGE'] = 'ObjC' # Hopefully one day we can put Ruby here :)
plist['IBClasses'].addObject(ruby_class_plist)
end
ruby_class_plist
end
# we've taken over ns_outlets and ns_actions above, so just requiring the
# class will cause it to be parsed an the methods to be called so we can get
# at them
def find_outlets_and_actions(ruby_file)
puts "Getting outlets and actions"
require ruby_file
end
def update_superclass(ruby_class, ruby_class_plist)
klass = ruby_class.split("::").inject(Object) { |par, const| par.const_get(const) }
superklass = klass.superclass.to_s.sub(/OSX::/, '')
ruby_class_plist.setObject_forKey(superklass, "SUPERCLASS")
end
def add_outlets_and_actions_to_plist(ruby_class_plist)
puts "Adding outlets and actions to plist"
updated_outlets = NSMutableDictionary.dictionary
$outlets.each { |outlet| updated_outlets.setObject_forKey('id', outlet) }
ruby_class_plist['OUTLETS'] = updated_outlets
updated_actions = NSMutableDictionary.dictionary
$actions.each { |action| updated_actions.setObject_forKey('id', action) }
ruby_class_plist['ACTIONS'] = updated_actions
end
def write_plist_data(nib_path, plist)
puts "Writing updated classes.nib plist back to file"
File.open("#{nib_path}/classes.nib", "w+") do |file|
file.write plist
end
end
end
require 'optparse'
class Options
def self.parse(args)
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: ruby -r <extra libs you require> update_classes_nib.rb [options]"
opts.on("-c", "--class NAME", "Name of Ruby class") do |klass|
options[:class] = klass == "" ? nil : klass
end
opts.on("-f", "--file PATH", "Path to file containing Ruby class",
"Defaults to lib/<class>.rb") do |file|
options[:file] = file == "" ? nil : file
end
opts.on("-n", "--nib PATH", "Path to .nib to update") do |nib|
options[:nib] = nib == "" ? nil : nib
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end
opts.parse!(args)
if options[:class].nil? || options[:nib].nil?
puts "Must supply the ruby class and the nib paths"
puts opts
exit
end
options[:file] = "lib/#{options[:class]}.rb" if options[:file].nil?
options
end
end
options = Options.parse(ARGV)
ClassesNibUpdater.update_nib(options[:nib], options[:class], options[:file])