json_select 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/Gemfile +1 -0
- data/Rakefile +5 -0
- data/lib/json_select.rb +8 -13
- data/lib/json_select/ast/combination_selector.rb +5 -5
- data/lib/json_select/ast/complex_expr.rb +7 -7
- data/lib/json_select/ast/even_expr.rb +3 -3
- data/lib/json_select/ast/hash_selector.rb +3 -3
- data/lib/json_select/ast/odd_expr.rb +3 -3
- data/lib/json_select/ast/pseudo_selector.rb +9 -9
- data/lib/json_select/ast/simple_expr.rb +3 -3
- data/lib/json_select/ast/type_selector.rb +3 -3
- data/lib/json_select/selector.rb +140 -58
- data/lib/json_select/selector_parser.rb +272 -113
- data/lib/json_select/selector_parser.tt +21 -25
- data/lib/json_select/version.rb +2 -2
- data/spec/conformance_spec.rb +26 -12
- data/spec/fixtures/basic_children.ast +6 -0
- data/spec/fixtures/basic_children.output +1 -0
- data/spec/fixtures/basic_children.selector +1 -0
- data/spec/fixtures/basic_combination.ast +5 -0
- data/spec/fixtures/basic_combination.output +1 -0
- data/spec/fixtures/basic_combination.selector +1 -0
- data/spec/fixtures/basic_universal.output +2 -1
- data/spec/ruby_extensions_spec.rb +28 -0
- metadata +17 -3
- data/lib/json_select/parser.rb +0 -19
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -11,6 +11,11 @@ end
|
|
11
11
|
|
12
12
|
task :build_parser do
|
13
13
|
sh "bundle exec tt lib/json_select/selector_parser.tt -o lib/json_select/selector_parser.rb"
|
14
|
+
|
15
|
+
path = 'lib/json_select/selector_parser.rb'
|
16
|
+
src = File.read(path)
|
17
|
+
src.gsub!('JSONSelectSelector', 'JSONSelect::Selector')
|
18
|
+
File.open(path, 'w+') { |f| f.write src }
|
14
19
|
end
|
15
20
|
|
16
21
|
task :spec => :build_parser
|
data/lib/json_select.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class JSONSelect
|
2
|
+
|
3
3
|
require 'treetop'
|
4
|
-
|
4
|
+
|
5
5
|
require 'json_select/version'
|
6
6
|
require 'json_select/selector_parser'
|
7
7
|
require 'json_select/selector'
|
8
|
-
|
9
|
-
|
8
|
+
|
10
9
|
module Ast
|
11
10
|
require 'json_select/ast/combination_selector'
|
12
11
|
require 'json_select/ast/simple_selector'
|
@@ -15,21 +14,17 @@ module JSONSelect
|
|
15
14
|
require 'json_select/ast/hash_selector'
|
16
15
|
require 'json_select/ast/pseudo_selector'
|
17
16
|
require 'json_select/ast/universal_selector'
|
18
|
-
|
17
|
+
|
19
18
|
require 'json_select/ast/odd_expr'
|
20
19
|
require 'json_select/ast/even_expr'
|
21
20
|
require 'json_select/ast/simple_expr'
|
22
21
|
require 'json_select/ast/complex_expr'
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
ParseError = Class.new(RuntimeError)
|
26
|
-
|
27
|
-
def self.parse(selector)
|
28
|
-
JSONSelect::Parser.new(selector).parse
|
29
|
-
end
|
30
|
-
|
25
|
+
|
31
26
|
end
|
32
27
|
|
33
28
|
def JSONSelect(selector)
|
34
|
-
JSONSelect.
|
29
|
+
JSONSelect.new(selector)
|
35
30
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module JSONSelect::Ast::CombinationSelector
|
2
|
-
|
2
|
+
|
3
3
|
def to_ast
|
4
4
|
ast = [a.to_ast]
|
5
|
-
|
5
|
+
|
6
6
|
b.elements.each do |comb|
|
7
|
-
ast.push(
|
7
|
+
ast.push(:>) if comb.c.text_value.strip == '>'
|
8
8
|
ast.push(comb.d.to_ast)
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
ast
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
end
|
@@ -1,27 +1,27 @@
|
|
1
1
|
module JSONSelect::Ast::ComplexExpr
|
2
|
-
|
2
|
+
|
3
3
|
def to_ast
|
4
4
|
_a = ""
|
5
|
-
|
5
|
+
|
6
6
|
if a.text_value.size > 0
|
7
7
|
_a += a.text_value
|
8
8
|
else
|
9
9
|
_a += '+'
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
if b.text_value.size > 0
|
13
13
|
_a += b.text_value
|
14
14
|
else
|
15
15
|
_a += '1'
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
if c.text_value.size > 0
|
19
19
|
_b = c.text_value.to_i
|
20
20
|
else
|
21
21
|
_b = 0
|
22
22
|
end
|
23
|
-
|
24
|
-
{
|
23
|
+
|
24
|
+
{ :a => _a.to_i, :b => _b }
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
end
|
@@ -2,23 +2,23 @@ module JSONSelect::Ast::PseudoSelector
|
|
2
2
|
|
3
3
|
def to_ast
|
4
4
|
if respond_to?(:e)
|
5
|
-
ast = {
|
5
|
+
ast = { :pseudo_function => a.text_value, :a => 0 , :b => 0 }
|
6
6
|
ast.merge!(e.to_ast)
|
7
7
|
ast
|
8
8
|
else
|
9
9
|
case a.text_value
|
10
|
-
|
10
|
+
|
11
11
|
when 'first-child'
|
12
|
-
{
|
13
|
-
|
12
|
+
{ :pseudo_function => 'nth-child', :a => 0, :b => 1 }
|
13
|
+
|
14
14
|
when 'last-child'
|
15
|
-
{
|
16
|
-
|
15
|
+
{ :pseudo_function => 'nth-last-child', :a => 0, :b => 1 }
|
16
|
+
|
17
17
|
else
|
18
|
-
{
|
19
|
-
|
18
|
+
{ :pseudo_class => a.text_value }
|
19
|
+
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
end
|
data/lib/json_select/selector.rb
CHANGED
@@ -1,27 +1,84 @@
|
|
1
|
-
class JSONSelect
|
2
|
-
|
1
|
+
class JSONSelect
|
2
|
+
|
3
3
|
attr_reader :ast
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
|
5
|
+
@@parser_cache = {}
|
6
|
+
|
7
|
+
def self.reset_cache!
|
8
|
+
@@parser_cache.clear
|
7
9
|
end
|
8
|
-
|
10
|
+
|
11
|
+
def initialize(src, use_parser_cache=true)
|
12
|
+
case src
|
13
|
+
|
14
|
+
when String
|
15
|
+
ast = nil
|
16
|
+
|
17
|
+
if use_parser_cache
|
18
|
+
ast = @@parser_cache[src]
|
19
|
+
end
|
20
|
+
|
21
|
+
if ast
|
22
|
+
@ast = ast
|
23
|
+
|
24
|
+
else
|
25
|
+
parser = JSONSelect::SelectorParser.new
|
26
|
+
tree = parser.parse(src)
|
27
|
+
unless tree
|
28
|
+
raise JSONSelect::ParseError, parser.failure_reason
|
29
|
+
end
|
30
|
+
|
31
|
+
@ast = tree.to_ast
|
32
|
+
|
33
|
+
if use_parser_cache
|
34
|
+
@@parser_cache[src] = ast
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
when Array
|
39
|
+
@ast = src
|
40
|
+
|
41
|
+
else
|
42
|
+
raise ArgumentError, "Expected a string for ast"
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the first matching child in `object`
|
9
48
|
def match(object)
|
49
|
+
_each(@ast, object, nil, nil, nil) do |object|
|
50
|
+
return object
|
51
|
+
end
|
52
|
+
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :=~, :match
|
57
|
+
|
58
|
+
# Returns all matching children in `object`
|
59
|
+
def matches(object)
|
10
60
|
matches = []
|
11
|
-
|
61
|
+
|
12
62
|
_each(@ast, object, nil, nil, nil) do |object|
|
13
63
|
matches << object
|
14
64
|
end
|
15
|
-
|
65
|
+
|
16
66
|
matches
|
17
67
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
68
|
+
|
69
|
+
# Returns true if `object` has any matching children.
|
70
|
+
def test(object)
|
71
|
+
_each(@ast, object, nil, nil, nil) do |object|
|
72
|
+
return true
|
73
|
+
end
|
74
|
+
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
alias_method :===, :test
|
79
|
+
|
23
80
|
private
|
24
|
-
|
81
|
+
|
25
82
|
# function forEach(sel, obj, fun, id, num, tot) {
|
26
83
|
# var a = (sel[0] === ',') ? sel.slice(1) : [sel];
|
27
84
|
# var a0 = [];
|
@@ -51,40 +108,59 @@ private
|
|
51
108
|
def _each(selector, object, id, number, total, &block)
|
52
109
|
a0 = (selector[0] == ',' ? selector[1..-1] : [selector])
|
53
110
|
a1 = []
|
54
|
-
|
111
|
+
|
55
112
|
call = false
|
56
|
-
|
113
|
+
|
57
114
|
a0.each do |selector|
|
58
115
|
ok, extra = _match(object, selector, id, number, total)
|
59
|
-
|
116
|
+
|
60
117
|
call = true if ok
|
61
118
|
a1.concat extra
|
62
119
|
end
|
63
|
-
|
64
|
-
if
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
120
|
+
|
121
|
+
if a1.any?
|
122
|
+
case object
|
123
|
+
|
124
|
+
when Array
|
125
|
+
a1.unshift(',')
|
126
|
+
size = object.size
|
127
|
+
|
69
128
|
object.each_with_index do |child, idx|
|
70
129
|
_each(a1, child, nil, idx, size, &block)
|
71
130
|
end
|
72
|
-
|
73
|
-
|
74
|
-
|
131
|
+
|
132
|
+
when Hash
|
133
|
+
a1.unshift(',')
|
134
|
+
size = object.size
|
135
|
+
|
75
136
|
object.each_with_index do |(key, child), idx|
|
76
137
|
_each(a1, child, key, idx, size, &block)
|
77
138
|
end
|
139
|
+
|
140
|
+
else
|
141
|
+
if object.respond_to?(:json_select_each)
|
142
|
+
children = []
|
143
|
+
object.json_select_each do |key, value|
|
144
|
+
children << [key, value]
|
145
|
+
end
|
146
|
+
|
147
|
+
a1.unshift(',')
|
148
|
+
size = children.size
|
149
|
+
|
150
|
+
children.each_with_index do |(key, child), idx|
|
151
|
+
_each(a1, child, key, idx, size, &block)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
78
155
|
end
|
79
|
-
|
80
156
|
end
|
81
|
-
|
157
|
+
|
82
158
|
if call and block
|
83
159
|
block.call(object)
|
84
160
|
end
|
85
161
|
end
|
86
|
-
|
87
|
-
|
162
|
+
|
163
|
+
|
88
164
|
# function mn(node, sel, id, num, tot) {
|
89
165
|
# var sels = [];
|
90
166
|
# var cs = (sel[0] === '>') ? sel[1] : sel[0];
|
@@ -100,77 +176,83 @@ private
|
|
100
176
|
# m = (!((num - cs.b) % cs.a) && ((num*cs.a + cs.b) >= 0));
|
101
177
|
# }
|
102
178
|
# }
|
103
|
-
#
|
179
|
+
#
|
104
180
|
# // should we repeat this selector for descendants?
|
105
181
|
# if (sel[0] !== '>' && sel[0].pc !== ":root") sels.push(sel);
|
106
|
-
#
|
182
|
+
#
|
107
183
|
# if (m) {
|
108
184
|
# // is there a fragment that we should pass down?
|
109
185
|
# if (sel[0] === '>') { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
|
110
186
|
# else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
|
111
187
|
# }
|
112
|
-
#
|
188
|
+
#
|
113
189
|
# return [m, sels];
|
114
190
|
# }
|
115
191
|
def _match(object, selector, id, number, total)
|
116
192
|
selectors = []
|
117
|
-
current_selector = (selector[0] ==
|
193
|
+
current_selector = (selector[0] == :> ? selector[1] : selector[0])
|
118
194
|
match = true
|
119
|
-
|
120
|
-
if current_selector.key?(
|
121
|
-
match = (match and current_selector[
|
195
|
+
|
196
|
+
if current_selector.key?(:type)
|
197
|
+
match = (match and current_selector[:type] == _type_of(object))
|
122
198
|
end
|
123
|
-
|
124
|
-
if current_selector.key?(
|
125
|
-
match = (match and current_selector[
|
199
|
+
|
200
|
+
if current_selector.key?(:class)
|
201
|
+
match = (match and current_selector[:class] == id.to_s)
|
126
202
|
end
|
127
|
-
|
128
|
-
if match and current_selector.key?(
|
129
|
-
pseudo_function = current_selector[
|
130
|
-
|
203
|
+
|
204
|
+
if match and current_selector.key?(:pseudo_function)
|
205
|
+
pseudo_function = current_selector[:pseudo_function]
|
206
|
+
|
131
207
|
if pseudo_function == 'nth-last-child'
|
132
208
|
number = total - number
|
133
209
|
else
|
134
210
|
number += 1
|
135
211
|
end
|
136
|
-
|
137
|
-
if current_selector[
|
138
|
-
match = current_selector[
|
212
|
+
|
213
|
+
if current_selector[:a] == 0
|
214
|
+
match = (current_selector[:b] == number)
|
139
215
|
else
|
140
216
|
# WTF!
|
141
|
-
match = ((((number - current_selector[
|
217
|
+
match = ((((number - current_selector[:b]) % current_selector[:a]) == 0) && ((number * current_selector[:a] + current_selector[:b]) >= 0))
|
142
218
|
end
|
143
219
|
end
|
144
|
-
|
145
|
-
if selector[0]
|
220
|
+
|
221
|
+
if Hash === selector[0] and selector[0][:pseudo_class] != 'root'
|
146
222
|
selectors.push selector
|
147
223
|
end
|
148
|
-
|
224
|
+
|
149
225
|
if match
|
150
|
-
if selector[0] ==
|
151
|
-
if selector.length > 2
|
152
|
-
|
226
|
+
if selector[0] == :>
|
227
|
+
if selector.length > 2
|
228
|
+
match = false
|
153
229
|
selectors.push selector[2..-1]
|
154
230
|
end
|
155
231
|
elsif selector.length > 1
|
156
|
-
|
232
|
+
match = false
|
157
233
|
selectors.push selector[1..-1]
|
158
234
|
end
|
159
235
|
end
|
160
|
-
|
236
|
+
|
161
237
|
return [match, selectors]
|
162
238
|
end
|
163
|
-
|
239
|
+
|
164
240
|
def _type_of(object)
|
241
|
+
if object.respond_to?(:json_select_each)
|
242
|
+
return 'object'
|
243
|
+
end
|
244
|
+
|
165
245
|
case object
|
166
246
|
when Hash then 'object'
|
167
247
|
when Array then 'array'
|
168
248
|
when String then 'string'
|
249
|
+
when Symbol then 'string'
|
169
250
|
when Numeric then 'number'
|
170
251
|
when TrueClass then 'boolean'
|
171
252
|
when FalseClass then 'boolean'
|
172
253
|
when NilClass then 'null'
|
254
|
+
else raise "Invalid object of class #{object.class} for JSONSelect: #{object.inspect}"
|
173
255
|
end
|
174
256
|
end
|
175
|
-
|
257
|
+
|
176
258
|
end
|