jnunemaker-siren 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.
- data/.gitignore +8 -0
- data/History.txt +4 -0
- data/LICENSE +22 -0
- data/README.txt +9 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/lib/json.rb +1037 -0
- data/lib/json.tt +84 -0
- data/lib/json_query.rb +2045 -0
- data/lib/json_query.tt +162 -0
- data/lib/json_query/nodes.rb +260 -0
- data/lib/siren.rb +63 -0
- data/lib/siren/node.rb +37 -0
- data/lib/siren/observer.rb +14 -0
- data/lib/siren/parser.rb +205 -0
- data/lib/siren/reference.rb +34 -0
- data/lib/siren/walker.rb +36 -0
- data/siren.gemspec +62 -0
- data/test/fixtures/car.rb +14 -0
- data/test/fixtures/people.json +34 -0
- data/test/fixtures/person.rb +15 -0
- data/test/fixtures/refs.json +26 -0
- data/test/fixtures/store.json +32 -0
- data/test/test_siren.rb +112 -0
- metadata +79 -0
data/lib/json_query.tt
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
grammar JsonQuery
|
|
2
|
+
rule expression
|
|
3
|
+
additive / multiplicative / atom
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
rule query
|
|
7
|
+
identifier filter* <Query>
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
rule identifier
|
|
11
|
+
(root / current / symbol) <Identifier>
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
rule root
|
|
15
|
+
"$" <Root>
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
rule current
|
|
19
|
+
"@" <Current>
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
rule symbol
|
|
23
|
+
[A-Za-z$_] [A-Za-z0-9$_]* <Symbol>
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
rule filter
|
|
27
|
+
(field_access / boolean_filter / map_filter / sort_filter) <Filter>
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rule field_access
|
|
31
|
+
("." (symbol / all) / "[" (field_access_expression / all) "]") <FieldAccess>
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
rule field_access_expression
|
|
35
|
+
first:expression others:("," expression)* <FieldAccessExpression>
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
rule all
|
|
39
|
+
"*" <AllFilter>
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
rule boolean_filter
|
|
43
|
+
"[?" boolean_expression "]" <BooleanFilter>
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
rule map_filter
|
|
47
|
+
"[=" expression "]" <MapFilter>
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
rule sort_filter
|
|
51
|
+
"[" sorter expression "]" <SortFilter>
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
rule sorter
|
|
55
|
+
space ("/" / "\\") space <Sorter>
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
rule boolean_expression
|
|
59
|
+
or_expression / and_expression / boolean_atom
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
rule or_expression
|
|
63
|
+
first:and_expression "|" second:or_expression <Or> / and_expression
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
rule and_expression
|
|
67
|
+
first:boolean_atom "&" second:and_expression <And> / boolean_atom
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
rule boolean_atom
|
|
71
|
+
space ("(" boolean_expression ")" / first:expression comparator second:expression) space <BooleanAtom>
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
rule additive
|
|
75
|
+
first:multiplicative operator:(add / subtract) second:additive <Additive> / multiplicative
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
rule multiplicative
|
|
79
|
+
first:atom operator:(times / divide) second:multiplicative <Multiplicative> / atom
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
rule atom
|
|
83
|
+
space ("(" expression ")" / query / number / string / boolean / null) space <Atom>
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
rule times
|
|
87
|
+
"*" <Multiplication>
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
rule divide
|
|
91
|
+
"/" <Division>
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
rule add
|
|
95
|
+
"+" <Addition>
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
rule subtract
|
|
99
|
+
"-" <Subtraction>
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
rule comparator
|
|
103
|
+
(not_equal / lte / gte / equal / lt / gt) <Comparator>
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
rule equal
|
|
107
|
+
"=" <Equal>
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
rule not_equal
|
|
111
|
+
"!=" <NotEqual>
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
rule lt
|
|
115
|
+
"<" <LessThan>
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
rule lte
|
|
119
|
+
"<=" <LessThanOrEqual>
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
rule gt
|
|
123
|
+
">" <GreaterThan>
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
rule gte
|
|
127
|
+
">=" <GreaterThanOrEqual>
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
rule string
|
|
131
|
+
"'" ('\\' ("'" / '\\' / '/' / 'b' / 'f' / 'n' / 'r' / 't' / 'u' hex hex hex hex) / [^\'\\])* "'" <String>
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
rule number
|
|
135
|
+
"-"? ("0" / [1-9] digit*) ("." digit+)? ([eE] [+-]? digit+)? <Number>
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
rule digit
|
|
139
|
+
[0-9]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
rule boolean
|
|
143
|
+
(true / false) <Boolean>
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
rule true
|
|
147
|
+
"true" <True>
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
rule false
|
|
151
|
+
"false" <False>
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
rule null
|
|
155
|
+
"null" <Null>
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
rule space
|
|
159
|
+
" "*
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
module JsonQuery
|
|
2
|
+
|
|
3
|
+
class Query < Treetop::Runtime::SyntaxNode
|
|
4
|
+
def value(root, symbols, current = nil)
|
|
5
|
+
object = identifier.value(root, symbols, current)
|
|
6
|
+
filters.inject(object) { |value, filter| filter.value(value, root, symbols, current) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def filters
|
|
10
|
+
elements[1].elements
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Identifier
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Root < Treetop::Runtime::SyntaxNode
|
|
18
|
+
def value(root, symbols, current = nil)
|
|
19
|
+
root
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Current < Treetop::Runtime::SyntaxNode
|
|
24
|
+
def value(root, symbols, current = nil)
|
|
25
|
+
current
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Symbol < Treetop::Runtime::SyntaxNode
|
|
30
|
+
def value(root, symbols, current = nil)
|
|
31
|
+
symbols[text_value] || FieldAccess.access(current, text_value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Filter
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module FieldAccess
|
|
39
|
+
def index(object, root, symbols, current = nil)
|
|
40
|
+
element = elements[1]
|
|
41
|
+
return [element.text_value] if Symbol === element
|
|
42
|
+
element.value(root, symbols, object)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def value(object, root, symbols, current = nil)
|
|
46
|
+
indexes = index(object, root, symbols, current)
|
|
47
|
+
indexes.size == 1 ?
|
|
48
|
+
FieldAccess.access(object, indexes.first) :
|
|
49
|
+
indexes.map { |i| FieldAccess.access(object, i) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.access(object, index)
|
|
53
|
+
return (Hash === object ? object.values : object) if index == :*
|
|
54
|
+
return object[index] if Array === object and Numeric === index
|
|
55
|
+
|
|
56
|
+
if Hash === object
|
|
57
|
+
key = [index, index.to_s, index.to_sym].find { |i| object.has_key?(i) }
|
|
58
|
+
return object[key] if key
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return object.__send__(index) if object.respond_to?(index)
|
|
62
|
+
object.instance_variable_get("@#{ index }")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class FieldAccessExpression < Treetop::Runtime::SyntaxNode
|
|
67
|
+
def value(root, symbols, current = nil)
|
|
68
|
+
exprs = [first] + others.elements.map { |e| e.expression }
|
|
69
|
+
exprs.map { |e| e.value(root, symbols, current) }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class AllFilter < Treetop::Runtime::SyntaxNode
|
|
74
|
+
def value(root, symbols, current = nil)
|
|
75
|
+
[:*]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class BooleanFilter < Treetop::Runtime::SyntaxNode
|
|
80
|
+
def value(list, root, symbols, current = nil)
|
|
81
|
+
list.select do |object|
|
|
82
|
+
boolean_expression.value(root, symbols, object)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class MapFilter < Treetop::Runtime::SyntaxNode
|
|
88
|
+
def value(list, root, symbols, current = nil)
|
|
89
|
+
list.map do |object|
|
|
90
|
+
expression.value(root, symbols, object)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class SortFilter < Treetop::Runtime::SyntaxNode
|
|
96
|
+
def value(list, root, symbols, current = nil)
|
|
97
|
+
result = list.sort_by { |object| expression.value(root, symbols, object) }
|
|
98
|
+
result.reverse! if sorter.text_value == "\\"
|
|
99
|
+
result
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class Sorter < Treetop::Runtime::SyntaxNode
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class And < Treetop::Runtime::SyntaxNode
|
|
107
|
+
def value(root, symbols, current = nil)
|
|
108
|
+
first.value(root, symbols, current) && second.value(root, symbols, current)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class Or < Treetop::Runtime::SyntaxNode
|
|
113
|
+
def value(root, symbols, current = nil)
|
|
114
|
+
first.value(root, symbols, current) || second.value(root, symbols, current)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class BooleanAtom < Treetop::Runtime::SyntaxNode
|
|
119
|
+
def value(root, symbols, current = nil)
|
|
120
|
+
element = elements[1]
|
|
121
|
+
return element.boolean_expression.value(root, symbols, current) if element.respond_to?(:boolean_expression)
|
|
122
|
+
comparator.value(first.value(root, symbols, current), second.value(root, symbols, current))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def comparator
|
|
126
|
+
elements[1].comparator
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def first
|
|
130
|
+
elements[1].first
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def second
|
|
134
|
+
elements[1].second
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class Multiplicative < Treetop::Runtime::SyntaxNode
|
|
139
|
+
def value(root, symbols, current = nil)
|
|
140
|
+
operator.value(first.value(root, symbols, current), second.value(root, symbols, current))
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class Additive < Treetop::Runtime::SyntaxNode
|
|
145
|
+
def value(root, symbols, current = nil)
|
|
146
|
+
operator.value(first.value(root, symbols, current), second.value(root, symbols, current))
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class Atom < Treetop::Runtime::SyntaxNode
|
|
151
|
+
def value(root, symbols, current = nil)
|
|
152
|
+
element = elements[1]
|
|
153
|
+
return element.expression.value(root, symbols, current) if element.respond_to?(:expression)
|
|
154
|
+
element.value(root, symbols, current)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class Multiplication < Treetop::Runtime::SyntaxNode
|
|
159
|
+
def value(expr1, expr2)
|
|
160
|
+
expr1 * expr2
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class Division < Treetop::Runtime::SyntaxNode
|
|
165
|
+
def value(expr1, expr2)
|
|
166
|
+
expr1 / expr2
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
class Addition < Treetop::Runtime::SyntaxNode
|
|
171
|
+
def value(expr1, expr2)
|
|
172
|
+
String === expr1 || String === expr2 ?
|
|
173
|
+
expr1.to_s + expr2.to_s :
|
|
174
|
+
expr1 + expr2
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class Subtraction < Treetop::Runtime::SyntaxNode
|
|
179
|
+
def value(expr1, expr2)
|
|
180
|
+
expr1 - expr2
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
module Comparator
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class Equal < Treetop::Runtime::SyntaxNode
|
|
188
|
+
def value(expr1, expr2)
|
|
189
|
+
expr1 == expr2
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class NotEqual < Treetop::Runtime::SyntaxNode
|
|
194
|
+
def value(expr1, expr2)
|
|
195
|
+
expr1 != expr2
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
class LessThan < Treetop::Runtime::SyntaxNode
|
|
200
|
+
def value(expr1, expr2)
|
|
201
|
+
expr1 < expr2
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
class LessThanOrEqual < Treetop::Runtime::SyntaxNode
|
|
206
|
+
def value(expr1, expr2)
|
|
207
|
+
expr1 <= expr2
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
class GreaterThan < Treetop::Runtime::SyntaxNode
|
|
212
|
+
def value(expr1, expr2)
|
|
213
|
+
expr1 > expr2
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class GreaterThanOrEqual < Treetop::Runtime::SyntaxNode
|
|
218
|
+
def value(expr1, expr2)
|
|
219
|
+
expr1 >= expr2
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class String < Treetop::Runtime::SyntaxNode
|
|
224
|
+
def value(root, symbols, current = nil)
|
|
225
|
+
@value ||= eval(text_value)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class Number < Treetop::Runtime::SyntaxNode
|
|
230
|
+
def value(root, symbols, current = nil)
|
|
231
|
+
@value ||= eval(text_value)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
module Boolean
|
|
236
|
+
def value(root, symbols, current = nil)
|
|
237
|
+
data.value
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class True < Treetop::Runtime::SyntaxNode
|
|
242
|
+
def value(root, symbols, current = nil)
|
|
243
|
+
true
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
class False < Treetop::Runtime::SyntaxNode
|
|
248
|
+
def value(root, symbols, current = nil)
|
|
249
|
+
false
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
class Null < Treetop::Runtime::SyntaxNode
|
|
254
|
+
def value(root, symbols, current = nil)
|
|
255
|
+
nil
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
end
|
|
260
|
+
|
data/lib/siren.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'treetop'
|
|
3
|
+
|
|
4
|
+
%w(walker parser node reference observer).each do |path|
|
|
5
|
+
require File.dirname(__FILE__) + '/siren/' + path
|
|
6
|
+
end
|
|
7
|
+
%w(json json_query json_query/nodes).each do |path|
|
|
8
|
+
require File.dirname(__FILE__) + '/' + path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
$p = JsonQueryParser.new
|
|
12
|
+
|
|
13
|
+
class JsonParser
|
|
14
|
+
include Siren::Walker
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Siren
|
|
18
|
+
TYPE_FIELD = "type"
|
|
19
|
+
ID_FIELD = "id"
|
|
20
|
+
REF_FIELD = "$ref"
|
|
21
|
+
|
|
22
|
+
def self.parse(string, &block)
|
|
23
|
+
@json_parser ||= JsonParser.new
|
|
24
|
+
Reference.flush!
|
|
25
|
+
@symbols = {}
|
|
26
|
+
|
|
27
|
+
result = @json_parser.parse(string).value rescue nil
|
|
28
|
+
|
|
29
|
+
@json_parser.walk(result) do |holder, key, value|
|
|
30
|
+
if Hash === value && value[REF_FIELD]
|
|
31
|
+
value = Reference.new(value) do |ref, root, symbols|
|
|
32
|
+
holder[key] = ref.find(root, symbols, holder)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@json_parser.walk(result) do |holder, key, value|
|
|
39
|
+
if Hash === value
|
|
40
|
+
id = value[ID_FIELD]
|
|
41
|
+
value = Node.from_json(value)
|
|
42
|
+
@symbols[id] = value
|
|
43
|
+
end
|
|
44
|
+
value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Reference.resolve!(result, @symbols)
|
|
48
|
+
@json_parser.walk(result, &block) if block_given?
|
|
49
|
+
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.query(expression, root)
|
|
54
|
+
compile_query(expression).value(root, @symbols ||{})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.compile_query(expression)
|
|
58
|
+
@query_parser ||= JsonQueryParser.new
|
|
59
|
+
@query_cache ||= {}
|
|
60
|
+
@query_cache[expression] ||= @query_parser.parse(expression)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|