allow mismatched name/class for struct
[lwes-ruby.git] / lib / lwes / struct.rb
blobe7bcfa767876f15b5320156283e01d7fede38205
1 module LWES
2   class Struct
4     # creates a new Struct-based class, takes the following
5     # options hash:
6     #
7     #   :db       - pre-created LWES::TypeDB object
8     #               this is required unless :file is given
9     #   :file     - pathname to the ESF file,
10     #               this is required unless :db is given
11     #   :class    - Ruby base class name, if the ESF file only has one
12     #               event defined (besides MetaEventInfo), then specifying
13     #               it is optional, otherwise it is required when multiple
14     #               events are defined in the same ESF :file given above
15     #   :parent   - parent class or module, the default is 'Object' putting
16     #               the new class in the global namespace.
17     #   :name     - event name if it differs from the Ruby base class name
18     #               given (or inferred) above.  For DRY-ness, you are
19     #               recommended to keep your event names and Ruby class
20     #               names in sync and not need this option.
21     #   :skip     - Array of field names to skip from the Event defininition
22     #               entirely, these could include fields that are only
23     #               implemented by the Listener.  This may also be a
24     #               regular expression.
25     #   :defaults - hash of default key -> value pairs to set at
26     #               creation time
27     #
28     def self.new(options, &block)
29       db = options[:db]
30       db ||= begin
31         file = options[:file] or
32           raise ArgumentError, "TypeDB :db or ESF :file missing"
33         test ?r, file or
34           raise ArgumentError, "file #{file.inspect} not readable"
35         TypeDB.new(file)
36       end
37       dump = db.to_hash
38       klass = options[:class] || begin
39         # make it easier to deal with single event files
40         events = (dump.keys -  [ :MetaEventInfo ])
41         events.size > 1 and
42             raise RuntimeError,
43                   "multiple event defs available: #{events.inspect}\n" \
44                   "pick one with :class"
45         events.first
46       end
48       name = options[:name] || klass
49       parent = options[:parent] || Object
50       event_def = dump[name.to_sym] or
51         raise RuntimeError, "#{name.inspect} not defined in #{file}"
53       # merge MetaEventInfo fields in
54       meta_event_info = dump[:MetaEventInfo]
55       alpha = proc { |a,b| a.first.to_s <=> b.first.to_s }
56       event_def = event_def.sort(&alpha)
57       if meta_event_info
58         seen = event_def.map { |(field, _)| field }
59         meta_event_info.sort(&alpha).each do |field_type|
60           seen.include?(field_type.first) or event_def << field_type
61         end
62       end
64       Array(options[:skip]).each do |x|
65         if Regexp === x
66           event_def.delete_if { |(f,_)| x =~ f.to_s }
67         else
68           if x.to_sym == :MetaEventInfo
69             meta_event_info.nil? and
70               raise RuntimeError, "MetaEventInfo not defined in #{file}"
71             meta_event_info.each do |(field,_)|
72               event_def.delete_if { |(f,_)| field == f }
73             end
74           else
75             event_def.delete_if { |(f,_)| f == x.to_sym }
76           end
77         end
78       end
80       tmp = ::Struct.new(*(event_def.map { |(field,_)| field }))
81       tmp = parent.const_set(klass, tmp)
82       tmp.const_set :TYPE_DB, db
83       tmp.const_set :NAME, name.to_s
84       ed = tmp.const_set :EVENT_DEF, {}
85       event_def.each { |(field,type)| ed[field] = type }
86       type_list = event_def.map { |(field,type)| [ field, field.to_s, type ] }
87       tmp.const_set :TYPE_LIST, type_list
89       defaults = options[:defaults] || {}
90       defaults = tmp.const_set :DEFAULTS, defaults.dup
91       tmp.class_eval(&block) if block_given?
93       # define a parent-level method, eval is faster than define_method
94       eval <<EOS
95 class ::#{tmp.name}
96   class << self
97     alias _new new
98     undef_method :new
99     def new(*args)
100       if Hash === (init = args.first)
101         rv = _new()
102         DEFAULTS.merge(init).each_pair { |k,v| rv[k] = v }
103         rv
104       else
105         rv = _new(*args)
106         DEFAULTS.each_pair { |k,v| rv[k] ||= v }
107         rv
108       end
109     end
110   end
113     tmp
114     end
115   end