json_select 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.
- 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
|