structurematch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/structurematch.rb +139 -0
  3. metadata +44 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c071ed38259f2735cad5b428fe364fa186e58ffb
4
+ data.tar.gz: 569278eab25de5512e30d557a409830e4544568b
5
+ SHA512:
6
+ metadata.gz: 210676dc6826c5bc18356469db5931ab733562829d5a64c4e1d61c76eb787d0a625cb6e83eda394c8813e5d73bdce6fd6518779d1f94810789d4d52dd5b590a8
7
+ data.tar.gz: a29709b8af3237fd1a013a97ac32c71d40178d29580ebd748c85b36f5e1cf78dc57b86f7bba93cc878581be490329389c469fb8a80a7c06e4188bd3655ca8749
@@ -0,0 +1,139 @@
1
+ # = structurematch.rb - Library for deep binding values from JSON
2
+ # Copyright 2014, Victor Lowther <victor.lowther@gmail.com>
3
+ # Licensed under the terms of the Apache 2 license.
4
+ #
5
+
6
+ # StructureMatch encapsulates all the logic needed perform deep binding and
7
+ # scoring of a JSON data structure.
8
+ class StructureMatch
9
+
10
+ # Comparator handles the actual comparison operations that StructureMatch uses.
11
+ # Comparators are initialized using a hash with the following structure:
12
+ #
13
+ # {
14
+ # "op" => "operation"
15
+ # "match" => "The value that the operation should work with"
16
+ # }
17
+ #
18
+ # Comparator knows about the following tests:
19
+ # "==","!=",">","<",">=","<=" --> The matching tests from Comparable.
20
+ # "and" --> Returns true if all the submatches return true, false otherwise.
21
+ # "or" --> Returns true if one of the submatches trturn true, false otherwise.
22
+ # "and" and "or" require that "match" be an array of hashes that Comparator.new can process.
23
+ # "not" --> Inverts its submatch. Requires that "match" be a hash that Comparator.new can process.
24
+ # "range" --> Tests to see if a value is within a range of values.
25
+ # Requires that "match" be a two-element array whose elements can be used to construct a Range.
26
+ # "regex" --> Tests to see if a value matches a regular expression. "match" must be a regex.
27
+ # "member" --> Tests to see if a value is within an array. "match" must be the array.
28
+ class Comparator
29
+ def initialize(op)
30
+ raise "#{op.inspect} must be a Hash" unless op.kind_of?(Hash)
31
+ unless ["==","!=",">","<",">=","<=","and","or","not","range","regex","member"].member?(op["op"])
32
+ raise "Operator #{op["op"]} is not one that Comparator knows how to use!"
33
+ end
34
+ raise "#{op.inspect} must have a match key" unless op.has_key?("match")
35
+ @op = op["op"].dup.freeze
36
+ @match = op["match"]
37
+ case @op
38
+ when "and","or"
39
+ raise "#{op.inspect} match key must be an array of submatches" unless @match.kind_of?(Array)
40
+ @match.map!{|m|Comparator.new(@match)}
41
+ when "not"
42
+ @match = Comparator.new(@match)
43
+ when "range"
44
+ raise "#{@match.inspect} is not a two-element array" unless @match.kind_of?(Array) && @match.length == 2
45
+ @match = Range.new(@match[0],@match[1])
46
+ when "regex" then @match = Regexp.compile(@match)
47
+ end
48
+ end
49
+
50
+ # test tests to see if v matches @match.
51
+ # It returns a two-element array:
52
+ # [score,val]
53
+ # score is the score adjustment factor for this test
54
+ # val is the value that test returns. It is usually the value that was passed in,
55
+ # except for regular expressions (which return the MatchData) and array & array
56
+ # comparisons performed by member (which returns the set intersection of the arrays)
57
+ def test(v=true)
58
+ case @op
59
+ when "and" then [_t(@match.all?{|m|m.test(v)[0]}),v]
60
+ when "or" then [_t(@match.any?{|m|m.test(v)[0]}),v]
61
+ when "not" then [_t(!@match.test(v)[0]),v]
62
+ when "range" then [_t(@match === v),v]
63
+ when "regex"
64
+ r = @match.match(v)
65
+ [r.nil? ? -1 : r.length, r]
66
+ when "member"
67
+ if v.kind_of?(Array)
68
+ r = @match & v
69
+ [r.empty? ? -1 : r.length, r]
70
+ else
71
+ [_t(@match.member?(v)),v]
72
+ end
73
+ when "==" then [_t(@match == v),v]
74
+ when "!=" then [_t(@match != v),v]
75
+ when ">" then [_t(@match > v),v]
76
+ when "<" then [_t(@match < v),v]
77
+ when ">=" then [_t(@match >= v),v]
78
+ when "<=" then [_t(@match <= v),v]
79
+ else
80
+ raise "Comparator cannot handle #{@op}"
81
+ end
82
+ end
83
+
84
+ private
85
+ def _t(v)
86
+ v ? 1 : -1
87
+ end
88
+ end
89
+
90
+ def initialize(matcher)
91
+ raise "#{matcher.inspect} must be a Hash" unless matcher.kind_of?(Hash)
92
+ @matchers = Hash.new
93
+ matcher.each do |k,v|
94
+ raise "#{k} must be a String" unless k.kind_of?(String)
95
+ key = k.dup.freeze
96
+ @matchers[key] = case
97
+ when v.kind_of?(TrueClass) ||
98
+ v.kind_of?(FalseClass) ||
99
+ v.kind_of?(NilClass) ||
100
+ v.kind_of?(Numeric)
101
+ Comparator.new("op" => "==", "match" => v)
102
+ when v.kind_of?(String) then Comparator.new("op" => "==", "match" => v.dup.freeze)
103
+ when v.kind_of?(Array) then Comparator.new("op" => "member", "match" => v.dup.freeze)
104
+ when v.kind_of?(Hash) then !!v["__sm_leaf"] ? Comparator.new(v) : StructureMatch.new(v)
105
+ else
106
+ raise "Cannot handle node type #{v.inspect}"
107
+ end
108
+ end
109
+ end
110
+
111
+ def bind(val)
112
+ raise "Must pass a Hash to StructureMatch.bind" unless val.kind_of?(Hash)
113
+ score = 0
114
+ binds = Hash.new
115
+ @matchers.each do |k,v|
116
+ offset = 0
117
+ res = nil
118
+ case
119
+ when !val.has_key?(k) then offset = -1
120
+ when v.kind_of?(Comparator)
121
+ offset,res = v.test(val[k])
122
+ binds[k] = res if offset > 0
123
+ when v.kind_of?(StructureMatch)
124
+ res,offset = v.bind(val[k])
125
+ binds[k] = v
126
+ else
127
+ raise "StructureMatch.bind: No idea how to handle #{v.class.name}: #{v.inspect}"
128
+ end
129
+ score += offset
130
+ end
131
+ [binds,score]
132
+ end
133
+
134
+
135
+ def score(val)
136
+ bind(val)[1]
137
+ end
138
+
139
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: structurematch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Victor Lowther
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A library for ferforming structural and semantic matching on JSON
14
+ email: victor.lowther@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/structurematch.rb
20
+ homepage: https://github.com/VictorLowther/structurematch
21
+ licenses:
22
+ - Apache 2
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.2.2
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Perform structural matching on JSON datastructures
44
+ test_files: []