=begin rdoc = SuperStruct This is an easy way to create Struct-like classes; it converts easily between hashes and arrays, and it allows OpenStruct-like dynamic naming of members. Unlike Struct, it creates a "real" class, and it has real instance variables with predictable names. A basic limitation is that the hash keys must be legal method names (unless used with send()). Basically, ss["alpha"], ss[:alpha], ss[0], and ss.alpha all mean the same. == Usage == Author(s) * Hal Fulton == Legal Ruby License (see incuded file) =end class SuperStruct def SuperStruct.new(*args) @table = [] @setsyms = [] # Setter symbols klass = Class.new if (args.size == 1) && (args[0].is_a? Array) args = args[0] end strs = args.map {|x| x.to_s } args.each_with_index do |k,i| case when (! [String,Symbol].include? k.class) raise ArgumentError, "Need a String or Symbol" when (strs[i] !~ /[_a-zA-Z][_a-zA-Z0-9]*/) raise ArgumentError, "Illegal character" end k = k.intern if k.is_a? String @table << k @setsyms << (k.to_s + "=").intern klass.instance_eval { attr_accessor k } end setsyms = @setsyms table = @table vals = @vals klass.class_eval do attr_reader :singleton define_method(:initialize) do |*vals| n = vals.size m = table.size case when n < m # raise ArgumentError, "Too few arguments (#{n} for #{m})" # Never mind... extra variables will just be nil when n > m raise ArgumentError, "Too many arguments (#{n} for #{m})" end setsyms.each_with_index do |var,i| self.send(var,vals[i]) end end define_method(:pretty_print) do |q| # pp.rb support q.object_group(self) do q.seplist(self.members, proc { q.text "," }) do |member| # self.members.each do |member| # q.text "," # unless q.first? q.breakable q.text member.to_s q.text '=' q.group(1) do q.breakable '' q.pp self[member] end end end end define_method(:inspect) do str = "#<#{self.class}:" table.each {|item| str << " #{item}=#{self.send(item)}" } str + ">" end define_method(:[]) do |*index| case index.map {|x| x.class } when [Fixnum] self.send(table[*index]) when [Fixnum,Fixnum], [Range] table[*index].map {|x| self.send(x)} when [String] self.send(index[0].intern) when [Symbol] self.send(index[0]) else raise ArgumentError,"Illegal index" end end define_method(:[]=) do |*index| value = index[-1] index = index[0..-2] case index.map {|x| x.class } when [Fixnum] self.send(table[*index]) when [Fixnum,Fixnum], [Range] setsyms[*index].map {|x| self.send(x,value) } when [String] self.send(index[0].intern,value) when [Symbol] self.send(index[0],value) else raise ArgumentError,"Illegal index" end end define_method(:to_a) { table.map {|x| eval("@"+x.to_s) } } define_method(:to_ary) { to_a } define_method(:members) { table.map {|x| x.to_s } } define_method(:to_struct) do mems = table Struct.new("TEMP",*mems) # Struct::TEMP.new(*vals) # Why doesn't this work?? data = mems.map {|x| self.send(x) } Struct::TEMP.new(*data) end define_method(:to_hash) do hash = {} table.each do |mem| mem = mem.to_s hash.update(mem => self.send(mem)) end hash end define_method(:set) {|h| h.each_pair {|k,v| send(k.to_s+"=",v) } } # Class methods... @singleton = class << self self end @singleton.instance_eval do define_method(:members) do table.map {|x| x.to_s } end me = self define_method(:attr_tester) do |*syms| syms.each {|sym| alias_method(sym.to_s+"?",sym) } end end end klass end def SuperStruct.open(*args) klass = SuperStruct.new(*args) table = @table setsyms = @setsyms table = @table klass.class_eval do define_method(:method_missing) do |meth, *args| mname = meth.id2name if mname =~ /=$/ getter = mname.chop setter = mname elsif mname =~ /\?$/ raise NoMethodError # ?-methods are not created automatically else getter = mname setter = mname + "=" end gsym = getter.intern ssym = setter.intern ivar = "@" + getter setsyms << setter table << getter len = args.length if mname == getter klass.class_eval do # getter define_method(getter) do instance_variable_get(ivar) end end else klass.class_eval do # setter define_method(setter) do |*args| if len != 1 raise ArgumentError, "Wrong # of arguments (#{len} for 1)", caller(1) end instance_variable_set(ivar,args[0]) instance_variable_get(ivar) end end end if mname == setter self.send(setter,*args) else if len == 0 self.send(getter) else raise NoMethodError, "Undefined method '#{mname}' for #{self}", caller(1) end end end end klass end end require "test-sstruct" if $0 == __FILE__