structurematch 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/structurematch.rb +69 -26
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99e88855ccc4edb6ae03e0f7b6679d1cf40e253b
|
4
|
+
data.tar.gz: d1cb943a7475bc63a326f6d1d7d4402bffbb0185
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 767925436ade0b040cdf96559a95db0e9b8eea1dda4077d7ebd8f9c8d12c4fc95cf2eeee9fd67a1b2b9408a1680bdb21ab95e5a30c7f106058d87aebd0fa63c2
|
7
|
+
data.tar.gz: 4d67cb27f30897e3997b8ed8658f7f38b3722301b04aaf601d6bea6fe6e910013c144741a88905ee26a7aa441b10829f3be4e78280c1ca934e824f631f5c3f4d
|
data/lib/structurematch.rb
CHANGED
@@ -8,24 +8,31 @@
|
|
8
8
|
class StructureMatch
|
9
9
|
|
10
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
11
|
class Comparator
|
12
|
+
# Comparators are initialized using a hash with the following structure:
|
13
|
+
#
|
14
|
+
# {
|
15
|
+
# "op" => "operation"
|
16
|
+
# "match" => "The value that the operation should work with"
|
17
|
+
# # Optional, defaults to 1 or the length of the matched thing (if array-ish)
|
18
|
+
# "score" => 1 # The score adjustment a match here gives.
|
19
|
+
# }
|
20
|
+
#
|
21
|
+
# Comparator knows about the following tests:
|
22
|
+
#
|
23
|
+
# "==","!=",">","<",">=","<=":: The matching tests from Comparable.
|
24
|
+
# "and":: Returns true if all the submatches return true, false otherwise.
|
25
|
+
# "or":: Returns true if one of the submatches return true, false otherwise.
|
26
|
+
# "and" and "or" require that "match" be an array of hashes that Comparator.new can process.
|
27
|
+
# "not":: Inverts its submatch. Requires that "match" be a hash that Comparator.new can process.
|
28
|
+
# "range":: Tests to see if a value is within a range of values.
|
29
|
+
# Requires that "match" be a two-element array whose elements can be used to construct a Range.
|
30
|
+
# "regex":: Tests to see if a value matches a regular expression. "match" must be a regex.
|
31
|
+
# The score on a matching regex will be the length of the MatchData by default, and the returned
|
32
|
+
# value will be matching MatchData.
|
33
|
+
# "member":: Tests to see if a value is within an array. "match" must be the array.
|
34
|
+
# If an array is passed to a member test, then scoring and the returned value will be the
|
35
|
+
# set intersection of the match array and the tested array.
|
29
36
|
def initialize(op)
|
30
37
|
raise "#{op.inspect} must be a Hash" unless op.kind_of?(Hash)
|
31
38
|
unless ["==","!=",">","<",">=","<=","and","or","not","range","regex","member"].member?(op["op"])
|
@@ -34,6 +41,8 @@ class StructureMatch
|
|
34
41
|
raise "#{op.inspect} must have a match key" unless op.has_key?("match")
|
35
42
|
@op = op["op"].dup.freeze
|
36
43
|
@match = op["match"]
|
44
|
+
@score = op["score"].to_i
|
45
|
+
@score = nil if @score == 0
|
37
46
|
case @op
|
38
47
|
when "and","or"
|
39
48
|
raise "#{op.inspect} match key must be an array of submatches" unless @match.kind_of?(Array)
|
@@ -47,13 +56,14 @@ class StructureMatch
|
|
47
56
|
end
|
48
57
|
end
|
49
58
|
|
50
|
-
#
|
59
|
+
# Takes a single argument which is the value to be tested.
|
60
|
+
#
|
51
61
|
# It returns a two-element array:
|
52
62
|
# [score,val]
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
63
|
+
# score:: the score adjustment factor for this test
|
64
|
+
# val:: the value that test returns. It is usually the value that was passed in,
|
65
|
+
# except for regular expressions (which return the MatchData) and array & array
|
66
|
+
# comparisons performed by member (which returns the set intersection of the arrays)
|
57
67
|
def test(v=true)
|
58
68
|
case @op
|
59
69
|
when "and" then [_t(@match.all?{|m|m.test(v)[0]}),v]
|
@@ -62,11 +72,11 @@ class StructureMatch
|
|
62
72
|
when "range" then [_t(@match === v),v]
|
63
73
|
when "regex"
|
64
74
|
r = @match.match(v)
|
65
|
-
[r.nil? ? -1 : r.length, r]
|
75
|
+
[r.nil? ? -1 : @score || r.length, r]
|
66
76
|
when "member"
|
67
77
|
if v.kind_of?(Array)
|
68
78
|
r = @match & v
|
69
|
-
[r.empty? ? -1 : r.length, r]
|
79
|
+
[r.empty? ? -1 : @score || r.length, r]
|
70
80
|
else
|
71
81
|
[_t(@match.member?(v)),v]
|
72
82
|
end
|
@@ -83,10 +93,37 @@ class StructureMatch
|
|
83
93
|
|
84
94
|
private
|
85
95
|
def _t(v)
|
86
|
-
v ? 1 : -1
|
96
|
+
v ? @score || 1 : -1
|
87
97
|
end
|
88
98
|
end
|
89
99
|
|
100
|
+
# StructureMatchers are initialized by passing in an example nested hash that maps out what
|
101
|
+
# StructureMatch should dig through, bind matching values, and how to score the matches it found.
|
102
|
+
# As an example:
|
103
|
+
# StructureMatch.new("foo" => "bar",
|
104
|
+
# "numbermatch" => 5,
|
105
|
+
# "regexmatch" => {
|
106
|
+
# "__sm_leaf" => true,
|
107
|
+
# "op"=> "regex",
|
108
|
+
# "match" => "foo(bar)"},
|
109
|
+
# "ormatch" => {
|
110
|
+
# "__sm_leaf" => true,
|
111
|
+
# "op" => "or",
|
112
|
+
# "match" => [
|
113
|
+
# {
|
114
|
+
# "op" => ">",
|
115
|
+
# "match" => 7
|
116
|
+
# },
|
117
|
+
# {
|
118
|
+
# "op" => "<",
|
119
|
+
# "match" => 10}]})
|
120
|
+
# will create a matcher that perfectly matches the following JSON:
|
121
|
+
# { "foo": "bar",
|
122
|
+
# "numbermatch": 5,
|
123
|
+
# "regexmatch": "foobar",
|
124
|
+
# "ormatch": 8
|
125
|
+
# }
|
126
|
+
# This match will be assigned a score of 5.
|
90
127
|
def initialize(matcher)
|
91
128
|
raise "#{matcher.inspect} must be a Hash" unless matcher.kind_of?(Hash)
|
92
129
|
@matchers = Hash.new
|
@@ -108,6 +145,12 @@ class StructureMatch
|
|
108
145
|
end
|
109
146
|
end
|
110
147
|
|
148
|
+
# Takes a (possibly nested) hash that is a result of parsing JSON and matches what it can,
|
149
|
+
# assigning a score in the process.
|
150
|
+
#
|
151
|
+
# Returns a two-entry array in the form of [binds,score]:
|
152
|
+
# binds:: The nested hash corresponding to the matched values from val.
|
153
|
+
# score:: The score assigned to this match.
|
111
154
|
def bind(val)
|
112
155
|
raise "Must pass a Hash to StructureMatch.bind" unless val.kind_of?(Hash)
|
113
156
|
score = 0
|
@@ -131,7 +174,7 @@ class StructureMatch
|
|
131
174
|
[binds,score]
|
132
175
|
end
|
133
176
|
|
134
|
-
|
177
|
+
# Runs bind on val and returns just the score component.
|
135
178
|
def score(val)
|
136
179
|
bind(val)[1]
|
137
180
|
end
|