Class: StructureMatch

Inherits:
Object
  • Object
show all
Defined in:
lib/structurematch.rb

Overview

StructureMatch encapsulates all the logic needed perform deep binding and scoring of a JSON data structure.

Defined Under Namespace

Classes: Comparator

Instance Method Summary collapse

Constructor Details

#initialize(matcher) ⇒ StructureMatch

StructureMatchers are initialized by passing in an example nested hash that maps out what StructureMatch should dig through, bind matching values, and how to score the matches it found. As an example:

StructureMatch.new("foo" => "bar",
                   "numbermatch" => 5,
                   "regexmatch" => {
                     "__sm_leaf" => true,
                     "op"=> "regex",
                     "match" => "foo(bar)"},
                   "ormatch" => {
                     "__sm_leaf" => true,
                     "op" => "or",
                     "match" => [
                       {
                         "op" => ">",
                         "match" => 7
                       },
                       {
                         "op" => "<",
                         "match" => 10}]})

will create a matcher that perfectly matches the following JSON:

{ "foo": "bar",
  "numbermatch": 5,
  "regexmatch": "foobar",
  "ormatch": 8
}

This match will be assigned a score of 5.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/structurematch.rb', line 127

def initialize(matcher)
  raise "#{matcher.inspect} must be a Hash" unless matcher.kind_of?(Hash)
  @matchers = Hash.new
  matcher.each do |k,v|
    raise "#{k} must be a String" unless k.kind_of?(String)
    key = k.dup.freeze
    @matchers[key] = case
                     when v.kind_of?(TrueClass) ||
                         v.kind_of?(FalseClass) ||
                         v.kind_of?(NilClass)   ||
                         v.kind_of?(Numeric)
                       Comparator.new("op" => "==", "match" => v)
                     when v.kind_of?(String) then Comparator.new("op" => "==", "match" => v.dup.freeze)
                     when v.kind_of?(Array) then Comparator.new("op" => "member", "match" => v.dup.freeze)
                     when v.kind_of?(Hash) then !!v["__sm_leaf"] ? Comparator.new(v) : StructureMatch.new(v)
                     else
                       raise "Cannot handle node type #{v.inspect}"
                     end
  end
end

Instance Method Details

#bind(val) ⇒ Object

Takes a (possibly nested) hash that is a result of parsing JSON and matches what it can, assigning a score in the process.

Returns a two-entry array in the form of [binds,score]:

binds

The nested hash corresponding to the matched values from val.

score

The score assigned to this match.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/structurematch.rb', line 154

def bind(val)
  raise "Must pass a Hash to StructureMatch.bind" unless val.kind_of?(Hash)
  score = 0
  binds = Hash.new
  @matchers.each do |k,v|
    offset = 0
    res = nil
    case
    when !val.has_key?(k) then offset = -1
    when v.kind_of?(Comparator)
      offset,res = v.test(val[k])
      binds[k] = res if offset > 0
    when v.kind_of?(StructureMatch)
      res,offset = v.bind(val[k])
      binds[k] = res unless res.empty?
    else
      raise "StructureMatch.bind: No idea how to handle #{v.class.name}: #{v.inspect}"
    end
    score += offset
  end
  [binds,score]
end

#flatbind(val, sep = '.') ⇒ Object

As bind, but transforms the nested hash into a flat hash with nested keys joined by sep.



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/structurematch.rb', line 179

def flatbind(val, sep='.')
  binds,score = bind(val)
  target = Hash.new
  prefix = ''
  _flatbind = lambda do |vals,p|
    vals.each do |k,v|
      key = p.empty? ? k : "#{p}#{sep}#{k.to_s}"
      if v.kind_of?(Hash)
        _flatbind.call(v,key)
      else
        target[key] = v
      end
    end
  end
  _flatbind.call(binds,"")
  [target,score]
end

#score(val) ⇒ Object

Runs bind on val and returns just the score component.



198
199
200
# File 'lib/structurematch.rb', line 198

def score(val)
  bind(val)[1]
end