4 # creates a new Struct-based class, takes the following
7 # :file - pathname to the ESF file, this is always required
8 # :class - Ruby base class name, if the ESF file only has one
9 # event defined (besides MetaEventInfo), then specifying
10 # it is optional, otherwise it is required when multiple
11 # events are defined in the same ESF :file given above
12 # :parent - parent class or module, the default is 'Object' putting
13 # the new class in the global namespace.
14 # :event - event name if it differs from the Ruby base class name
15 # given (or inferred) above. For DRY-ness, you are
16 # recommended to keep your event names and Ruby class
17 # names in sync and not need this option.
18 # :optional - Array of field names that are optional, the special
19 # "MetaEventInfo" name makes all elements defined in
20 # the MetaEventInfo section of the ESF is optional.
21 # This may also be a regular expression.
22 # :skip - Array of field names to skip from the Event defininition
23 # entirely, these could include fields that are only
24 # implemented by the Listener. This may also be a
26 # :defaults - hash of default key -> value pairs to set at
29 def self.new(options, &block)
30 file = options[:file] or raise ArgumentError, "esf file missing"
31 test ?r, file or raise ArgumentError, "file #{file.inspect} not readable"
32 dump = TypeDB.new(file).to_hash
33 klass = options[:class] || begin
34 # make it easier to deal with single event files
35 events = (dump.keys - [ :MetaEventInfo ])
38 "multiple event defs available: #{events.inspect}\n" \
39 "pick one with :class"
43 name = options[:name] || klass
44 parent = options[:parent] || Object
45 event_def = dump[name.to_sym] or
46 raise RuntimeError, "#{name.inspect} not defined in #{file}"
48 # merge MetaEventInfo fields in
49 meta_event_info = dump[:MetaEventInfo]
50 alpha = proc { |a,b| a.first.to_s <=> b.first.to_s }
51 event_def = event_def.sort(&alpha)
53 seen = event_def.map { |(field, _)| field }
54 meta_event_info.sort(&alpha).each do |field_type|
55 seen.include?(field_type.first) or event_def << field_type
59 Array(options[:skip]).each do |x|
61 event_def.delete_if { |(f,_)| x =~ f.to_s }
63 if x.to_sym == :MetaEventInfo
64 meta_event_info.nil? and
65 raise RuntimeError, "MetaEventInfo not defined in #{file}"
66 meta_event_info.each do |(field,_)|
67 event_def.delete_if { |(f,_)| field == f }
70 event_def.delete_if { |(f,_)| f == x.to_sym }
75 tmp = ::Struct.new(*(event_def.map { |(field,_)| field }))
76 tmp = parent.const_set(klass, tmp)
77 type_db = tmp.const_set :TYPE_DB, {}
78 event_def.each { |(field,type)| type_db[field] = type }
79 tmp.const_set :TYPE_LIST, event_def
81 optional = tmp.const_set :OPTIONAL, {}
82 Array(options[:optional]).each do |x|
84 event_def.each { |(f,_)| optional[f] = true if x =~ f.to_s }
86 if x.to_sym == :MetaEventInfo
87 meta_event_info.nil? and
88 raise RuntimeError, "MetaEventInfo not defined in #{file}"
89 meta_event_info.each{ |(field,_)| optional[field] = true }
91 optional[x.to_sym] = true
95 defaults = options[:defaults] || {}
96 defaults = tmp.const_set :DEFAULTS, defaults.dup
98 # define a parent-level method, so instead of Event.new
102 Kernel : parent.instance_eval { class << self; self; end }
103 ).__send__(:define_method, klass) do |*args|
104 if Hash === (init = args.first)
106 defaults.merge(init).each_pair { |k,v| rv[k] = v }
110 defaults.each_pair { |k,v| rv[k] ||= v }
115 tmp.instance_eval { class << self; self; end }.instance_eval do
116 alias_method :_new, :new
117 define_method(:new) do |*args|
118 if Hash === (init = args.first)
120 defaults.merge(init).each_pair { |k,v| rv[k] = v }
124 defaults.each_pair { |k,v| rv[k] ||= v }
131 tmp.class_eval(&block) if block_given?