Remove :optional defs from TypeDB checks, everything can be considered optional
[lwes-ruby.git] / lib / lwes / struct.rb
blobd34abdb7ee37c1ee6ab9d3f38ae2b2c7e72e76f3
1 module LWES
2   class Struct
4     # creates a new Struct-based class, takes the following
5     # options hash:
6     #
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     #   :skip     - Array of field names to skip from the Event defininition
19     #               entirely, these could include fields that are only
20     #               implemented by the Listener.  This may also be a
21     #               regular expression.
22     #   :defaults - hash of default key -> value pairs to set at
23     #               creation time
24     #
25     def self.new(options, &block)
26       file = options[:file] or raise ArgumentError, "esf file missing"
27       test ?r, file or raise ArgumentError, "file #{file.inspect} not readable"
28       db = TypeDB.new(file)
29       dump = db.to_hash
30       klass = options[:class] || begin
31         # make it easier to deal with single event files
32         events = (dump.keys -  [ :MetaEventInfo ])
33         events.size > 1 and
34             raise RuntimeError,
35                   "multiple event defs available: #{events.inspect}\n" \
36                   "pick one with :class"
37         events.first
38       end
40       name = options[:name] || klass
41       parent = options[:parent] || Object
42       event_def = dump[name.to_sym] or
43         raise RuntimeError, "#{name.inspect} not defined in #{file}"
45       # merge MetaEventInfo fields in
46       meta_event_info = dump[:MetaEventInfo]
47       alpha = proc { |a,b| a.first.to_s <=> b.first.to_s }
48       event_def = event_def.sort(&alpha)
49       if meta_event_info
50         seen = event_def.map { |(field, _)| field }
51         meta_event_info.sort(&alpha).each do |field_type|
52           seen.include?(field_type.first) or event_def << field_type
53         end
54       end
56       Array(options[:skip]).each do |x|
57         if Regexp === x
58           event_def.delete_if { |(f,_)| x =~ f.to_s }
59         else
60           if x.to_sym == :MetaEventInfo
61             meta_event_info.nil? and
62               raise RuntimeError, "MetaEventInfo not defined in #{file}"
63             meta_event_info.each do |(field,_)|
64               event_def.delete_if { |(f,_)| field == f }
65             end
66           else
67             event_def.delete_if { |(f,_)| f == x.to_sym }
68           end
69         end
70       end
72       tmp = ::Struct.new(*(event_def.map { |(field,_)| field }))
73       tmp = parent.const_set(klass, tmp)
74       tmp.const_set :TYPE_DB, db
75       ed = tmp.const_set :EVENT_DEF, {}
76       event_def.each { |(field,type)| ed[field] = type }
77       type_list = event_def.map { |(field,type)| [ field, field.to_s, type ] }
78       tmp.const_set :TYPE_LIST, type_list
80       defaults = options[:defaults] || {}
81       defaults = tmp.const_set :DEFAULTS, defaults.dup
82       tmp.class_eval(&block) if block_given?
84       # define a parent-level method, eval is faster than define_method
85       eval <<EOS
86 class ::#{tmp.name}
87   class << self
88     alias _new new
89     undef_method :new
90     def new(*args)
91       if Hash === (init = args.first)
92         rv = _new()
93         DEFAULTS.merge(init).each_pair { |k,v| rv[k] = v }
94         rv
95       else
96         rv = _new(*args)
97         DEFAULTS.each_pair { |k,v| rv[k] ||= v }
98         rv
99       end
100     end
101   end
104     tmp
105     end
106   end