[macruby-changes] [561] MacRuby/trunk/tool/rb_nibtool.old
source_changes at macosforge.org
source_changes at macosforge.org
Fri Sep 5 18:05:31 PDT 2008
Revision: 561
http://trac.macosforge.org/projects/ruby/changeset/561
Author: lsansonetti at apple.com
Date: 2008-09-05 18:05:31 -0700 (Fri, 05 Sep 2008)
Log Message:
-----------
and also the older one
Added Paths:
-----------
MacRuby/trunk/tool/rb_nibtool.old
Added: MacRuby/trunk/tool/rb_nibtool.old
===================================================================
--- MacRuby/trunk/tool/rb_nibtool.old (rev 0)
+++ MacRuby/trunk/tool/rb_nibtool.old 2008-09-06 01:05:31 UTC (rev 561)
@@ -0,0 +1,443 @@
+#!/usr/bin/env ruby
+# Copyright (c) 2006-2007, The RubyCocoa Project.
+# Copyright (c) 2007 Chris Mcgrath.
+# All Rights Reserved.
+#
+# RubyCocoa is free software, covered under either the Ruby's license or the
+# LGPL. See the COPYRIGHT file for more information.
+
+require 'osx/cocoa'
+include OSX
+require 'erb'
+
+def log(*s)
+ $stderr.puts s if $DEBUG
+end
+
+def die(*s)
+ $stderr.puts s
+ exit 1
+end
+
+# Requires rubygems if present.
+begin require 'rubygems'; rescue LoadError; end
+
+class OSX::NSObject
+ class << self
+ @@subklasses = {}
+
+ def subklasses
+ @@subklasses
+ end
+ end
+end
+
+begin
+ require 'rubynode'
+ require 'enumerator'
+ # RubyNode is found, we can get the IB metadata by parsing the code.
+ class OSX::NSObject
+ class << self
+ def collect_ib_metadata(ruby_file)
+ @current_class = nil
+ __parse_nodes(File.read(ruby_file).parse_to_nodes.transform)
+ end
+
+ def __parse_nodes(ary)
+ ary.each_slice(2) { |key, val| __parse_nodes_pair(key, val) }
+ end
+
+ def __parse_nodes_pair(key, val)
+ case val
+ when Array
+ if val.all? { |e| e.is_a?(Array) }
+ val.each { |p| __parse_nodes(p) }
+ else
+ __parse_nodes(val)
+ end
+ when Hash
+ case key
+ when :class
+ a = val[:super]
+ if a and a.is_a?(Array) and a[1].is_a?(Hash)
+ # This class inherits from another class, let's memorize the
+ # class name. We could actually check that the super class is
+ # an Objective-C class, but this would require to load all the
+ # required frameworks.
+ sclass = (a[1][:vid] or a[1][:mid])
+ a = val[:cpath]
+ if sclass and a and a.is_a?(Array) and a[1].is_a?(Hash)
+ @current_class = a[1][:mid]
+ if @current_class
+ subklasses[@current_class] ||= {}
+ subklasses[@current_class][:super] = sclass
+ end
+ end
+ end
+ when :fcall
+ case val[:mid]
+ # Memorize IB outlets.
+ when :ns_outlet, :ib_outlet, :ns_outlets, :ib_outlets
+ if @current_class.nil?
+ $stderr.puts "ib_outlet detected without current_class, skipping..."
+ elsif val[:args].is_a?(Array) and !val[:args].empty?
+ c = (subklasses[@current_class][:outlets] ||= [])
+ val[:args][1].each do |key2, val2|
+ if key2 == :lit and val2.is_a?(Hash) and val2[:lit]
+ c << val2[:lit]
+ end
+ end
+ end
+ # Memorize IB actions.
+ when :ib_action
+ if @current_class.nil?
+ $stderr.puts "ib_action detected without current_class, skipping..."
+ elsif val[:args].is_a?(Array) and !val[:args].empty?
+ c = (subklasses[@current_class][:actions] ||= [])
+ a = val[:args][1][0]
+ if val[:args][1].size != 1
+ $stderr.puts "ib_action called without or with more than one argument, skipping..."
+ elsif a[0] == :lit and
+ a[1].is_a?(Hash) and
+ a[1][:lit]
+ c << a[1][:lit]
+ end
+ end
+ end
+ end
+ val.each do |key2, val2|
+ if val2.is_a?(Array)
+ __parse_nodes_pair(key2, val2)
+ end
+ end
+ end
+ end
+ end
+ end
+ RUBYNODE_LOADED = true
+rescue LoadError
+ # We don't have a Ruby parser handy, let's evaluate/interpret the code as
+ # a second alternative.
+ class OSX::NSObject
+ class << self
+ @@collect_child_classes = false
+
+ def ib_outlets(*args)
+ args.each do |arg|
+ log "found outlet #{arg} in #{$current_class}"
+ (subklasses[$current_class][:outlets] ||= []) << arg
+ end
+ end
+
+ alias_method :ns_outlet, :ib_outlets
+ alias_method :ib_outlet, :ib_outlets
+ alias_method :ns_outlets, :ib_outlets
+
+ def ib_action(name, &blk)
+ log "found action #{name} in #{$current_class}"
+ (subklasses[$current_class][:actions] ||= []) << name
+ end
+
+ alias_method :_before_classes_nib_inherited, :inherited
+ def inherited(subklass)
+ if @@collect_child_classes
+ unless subklass.to_s == ""
+ log "current class: #{subklass.to_s}"
+ $current_class = subklass.to_s
+ subklasses[$current_class] ||= {}
+ subklasses[$current_class][:super] = subklass.superclass.to_s
+ end
+ end
+ _before_classes_nib_inherited(subklass)
+ end
+
+ def collect_ib_metadata(ruby_file)
+ @@collect_child_classes = true
+ require ruby_file
+ @@collect_child_classes = false
+ end
+ end
+ end
+ RUBYNODE_LOADED = false
+end
+
+class ClassesNibPlist
+ attr_reader :plist
+
+ def initialize(plist_path=nil)
+ @plist_path = plist_path
+ if plist_path and File.exist?(plist_path)
+ plist_data = NSData.alloc.initWithContentsOfFile(plist_path)
+ @plist, format, error = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription(plist_data, NSPropertyListMutableContainersAndLeaves)
+ die "Can't deserialize property list at path '#{plist_path}' : #{error}" if @plist.nil?
+ else
+ @plist = NSMutableDictionary.alloc.init
+ end
+ self
+ end
+
+ def find_ruby_class(ruby_class)
+ log "Looking for #{ruby_class} in plist"
+ # be nice if NSDictionary had the same methods as hash
+ ruby_class_plist = nil
+ classes = @plist['IBClasses']
+ if classes
+ classes.each do |klass|
+ next unless klass['CLASS'].to_s == ruby_class.to_s
+ ruby_class_plist = klass
+ end
+ else
+ @plist['IBClasses'] = []
+ end
+ if ruby_class_plist.nil?
+ log "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
+
+ def write_plist_data
+ data, error = NSPropertyListSerialization.objc_send \
+ :dataFromPropertyList, @plist,
+ :format, NSPropertyListXMLFormat_v1_0,
+ :errorDescription
+ if data.nil?
+ $stderr.puts error
+ exit 1
+ end
+ data = OSX::NSString.alloc.initWithData_encoding(data, NSUTF8StringEncoding)
+
+ if @plist_path
+ log "Writing updated classes.nib plist back to file"
+ File.open(@plist_path, "w+") { |io| io.puts data }
+ else
+ log "Writing updated classes.nib plist back to standard output"
+ puts data
+ end
+ end
+
+ def each_class(&block)
+ plist['IBClasses'].each do |klass|
+ next if klass['CLASS'].to_s == 'FirstResponder'
+ yield(klass)
+ end
+ end
+end
+
+class ClassesNibUpdater
+ def self.update_nib(plist_path, ruby_file, sorted_plist)
+ plist = ClassesNibPlist.new(plist_path)
+ updater = new
+ updater.find_classes_outlets_and_actions(ruby_file)
+ log "Found #{NSObject.subklasses.size} classes in #{ruby_file}"
+ NSObject.subklasses.each do |klass, data|
+ ruby_class_plist = plist.find_ruby_class(klass)
+ updater.update_superclass(klass, ruby_class_plist)
+ updater.add_outlets_and_actions_to_plist(klass, ruby_class_plist,
+ sorted_plist)
+ end
+ plist.write_plist_data
+ 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_classes_outlets_and_actions(ruby_file)
+ log "Getting classes, outlets and actions"
+ NSObject.collect_ib_metadata(ruby_file)
+ end
+
+ def update_superclass(ruby_class, ruby_class_plist)
+ superklass = NSObject.subklasses[ruby_class][:super].to_s.sub(/^OSX::/, '')
+ unless RUBYNODE_LOADED
+ # If the class has a superclass which isn't defined in the classes in the nib/ib
+ # then the class will still not show up. Because we can assume that it will be a
+ # descendant of NSObject use that as a default if the superclass can't be found.
+ begin
+ Object.const_get(superklass)
+ rescue NameError
+ superklass = :NSObject
+ end
+ end
+ ruby_class_plist.setObject_forKey(superklass, "SUPERCLASS")
+ end
+
+ def add_outlets_and_actions_to_plist(klass, ruby_class_plist, sorted_plist)
+ log "Adding outlets and actions to plist for #{klass}"
+
+ [:outlets, :actions].each do |sym|
+ cont = NSObject.subklasses[klass][sym]
+ next if cont.nil?
+ unless sorted_plist
+ hash = {}
+ cont.each do |val|
+ log "adding #{sym.to_s} #{val}"
+ hash[val] = 'id'
+ end
+ cont = hash
+ end
+ ruby_class_plist[sym.to_s.upcase] = cont unless cont.empty?
+ end
+ end
+end
+
+class ClassesNibCreator
+ def self.create_from_nib(plist_path, output_dir)
+ creator = new
+ plist = ClassesNibPlist.new(plist_path)
+ plist.each_class { |klass| creator.create_class(output_dir, klass) }
+ end
+
+ def initialize
+ @class_template = ERB.new(DATA.read, 0, "-")
+ end
+
+ def create_class(output_dir, klass)
+ @class_name = klass['CLASS']
+ output_file = File.join(output_dir, "#{@class_name}.rb")
+ if File.exists?(output_file)
+ log "#{output_file} exists, skipping"
+ return
+ end
+ @superclass = klass['SUPERCLASS']
+ if klass['OUTLETS'].nil?
+ @outlets = []
+ else
+ @outlets = klass['OUTLETS'].allKeys.to_a
+ end
+ if klass['ACTIONS'].nil?
+ @actions = []
+ else
+ @actions = klass['ACTIONS'].allKeys.to_a
+ end
+ log "Writing #{@class_name} to #{output_file}"
+ File.open(output_file, "w+") do |file|
+ file.write(@class_template.result(binding))
+ end
+ end
+end
+
+require 'optparse'
+class Options
+ def self.parse(args)
+ options = {}
+ options[:update] = false
+ options[:create] = false
+ options[:plist] = false
+ options[:sorted_plist] = false
+ opts = OptionParser.new do |opts|
+ opts.banner = "Usage: #{__FILE__} [options]"
+ opts.on("-u", "--update", "Update the classes.nib file from a Ruby",
+ "class (requires -f and -n options)") do |_|
+ options[:update] = true
+ end
+ opts.on("-c", "--create", "Create new Ruby classes from a nib",
+ "(requires -d and -n options)") do |_|
+ options[:create] = true
+ end
+ opts.on("-p", "--plist", "Dump on standard output a property list", "of the Ruby class IB metadata",
+ "(requires -f option)") do |_|
+ options[:plist] = true
+ end
+ opts.on("-s", "--sorted-plist",
+ "Dump a property list where the actions and",
+ "outlets are in sorted collections.",
+ "NOT compatible with the nib format.",
+ "(requires -p and -f options)") do |_|
+ options[:sorted_plist] = true
+ end
+ opts.on("-d", "--directory PATH", "Path to directory to create Ruby classes", "(requires -c option)") do |dir|
+ options[:dir] = dir == "" ? nil : dir
+ end
+ opts.on("-f", "--file PATH", "Path to file containing Ruby class(es)", "(requires -u or -p options)") do |file|
+ options[:file] = case file
+ when '' then nil
+ when '-' then '/dev/stdin'
+ else file
+ end
+ 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)
+ unless options[:update] || options[:create] || options[:plist]
+ puts "Must supply --update or --create or --plist"
+ exit_with_opts(opts)
+ end
+ if [:update, :create, :plist].map { |x| (options[x] or nil) }.compact.size > 1
+ puts "Can only specify one of --update or --create or --plist"
+ exit_with_opts(opts)
+ end
+ if options[:update]
+ if options[:file].nil? || options[:nib].nil?
+ puts "Must supply the ruby file and the nib paths"
+ exit_with_opts(opts)
+ end
+ end
+ if options[:create]
+ if options[:dir].nil? || options[:nib].nil?
+ puts "Must supply the output directory and the nib paths"
+ exit_with_opts(opts)
+ end
+ end
+ if options[:plist]
+ if options[:file].nil?
+ puts "Must supply the ruby file"
+ exit_with_opts(opts)
+ end
+ end
+ if options[:sorted_plist]
+ unless options[:plist]
+ puts "Must specify plist format"
+ exit_with_opts(opts)
+ end
+ end
+ options
+ end
+
+ def self.exit_with_opts(opts)
+ puts opts
+ exit
+ end
+end
+
+options = Options.parse(ARGV)
+nib_plist =
+ if options[:nib]
+ "#{options[:nib]}/classes.nib"
+ else
+ nil
+ end
+if options[:update] || options[:plist]
+ ClassesNibUpdater.update_nib(nib_plist, options[:file],
+ options[:sorted_plist])
+elsif options[:create]
+ ClassesNibCreator.create_from_nib(nib_plist, options[:dir])
+else
+ puts "Unknown options"
+end
+
+# wierd indentation here seems to be needed to produce nice output
+__END__
+require 'osx/cocoa'
+include OSX
+
+class <%= @class_name %> < <%= @superclass %>
+ <% unless @outlets.size == 0 -%>
+ ib_outlets <%= @outlets.map { |o| ":#{o}" }.join(", ") -%>
+ <% end -%>
+ <% @actions.each do |action| %>
+ ib_action :<%= action %> do |sender|
+ NSLog("Need to implement <%= @class_name%>.<%= action %>")
+ end
+ <% end %>
+end
Property changes on: MacRuby/trunk/tool/rb_nibtool.old
___________________________________________________________________
Added: svn:executable
+ *
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macruby-changes/attachments/20080905/22df2b8a/attachment-0001.html
More information about the macruby-changes
mailing list