structurematch 0.1.0

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.
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: []