yerba 0.9.1-aarch64-linux-gnu → 0.10.0-aarch64-linux-gnu
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/README.md +60 -16
- data/exe/aarch64-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +67 -32
- data/ext/yerba/yerba.c +296 -186
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/collection.rb +4 -4
- data/lib/yerba/document.rb +134 -0
- data/lib/yerba/formatting.rb +1 -0
- data/lib/yerba/location.rb +8 -1
- data/lib/yerba/map.rb +16 -6
- data/lib/yerba/node.rb +41 -0
- data/lib/yerba/query_result.rb +33 -10
- data/lib/yerba/scalar.rb +29 -0
- data/lib/yerba/selector.rb +142 -0
- data/lib/yerba/sequence.rb +38 -33
- data/lib/yerba/version.rb +1 -1
- data/lib/yerba.rb +1 -0
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/commands/get.rs +12 -12
- data/rust/src/commands/init.rs +8 -8
- data/rust/src/commands/insert.rs +4 -18
- data/rust/src/commands/location.rs +2 -0
- data/rust/src/commands/mod.rs +34 -6
- data/rust/src/commands/move_item.rs +1 -1
- data/rust/src/commands/move_key.rs +8 -8
- data/rust/src/commands/rename.rs +1 -1
- data/rust/src/commands/selectors.rs +1 -1
- data/rust/src/commands/set.rs +14 -6
- data/rust/src/document/condition.rs +25 -25
- data/rust/src/document/delete.rs +57 -43
- data/rust/src/document/get.rs +91 -82
- data/rust/src/document/insert.rs +77 -77
- data/rust/src/document/mod.rs +138 -64
- data/rust/src/document/nodes.rs +162 -0
- data/rust/src/document/schema.rs +8 -8
- data/rust/src/document/set.rs +49 -26
- data/rust/src/document/sort.rs +54 -54
- data/rust/src/document/style.rs +50 -49
- data/rust/src/document/unique.rs +5 -5
- data/rust/src/error.rs +8 -4
- data/rust/src/ffi.rs +272 -102
- data/rust/src/json.rs +62 -2
- data/rust/src/lib.rs +2 -1
- data/rust/src/main.rs +2 -1
- data/rust/src/selector.rs +48 -7
- data/rust/src/syntax.rs +449 -56
- data/rust/src/validation.rs +5 -3
- data/rust/src/yerbafile.rs +124 -76
- metadata +4 -2
data/lib/yerba/3.2/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/3.3/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/3.4/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/4.0/yerba.so
CHANGED
|
Binary file
|
data/lib/yerba/collection.rb
CHANGED
|
@@ -12,12 +12,12 @@ module Yerba
|
|
|
12
12
|
Dir.glob(@glob).each { |path| yield Document.new(path) }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def get(
|
|
16
|
-
self.class.get(@glob,
|
|
15
|
+
def get(selector)
|
|
16
|
+
self.class.get(@glob, selector)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def find(
|
|
20
|
-
self.class.find(@glob,
|
|
19
|
+
def find(selector, condition: nil, select: nil)
|
|
20
|
+
self.class.find(@glob, selector, condition: condition, select: select)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def find_by(...)
|
data/lib/yerba/document.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Yerba
|
|
4
4
|
class Document
|
|
5
|
+
include NodeTree
|
|
6
|
+
|
|
5
7
|
ROOT_SELECTOR = ""
|
|
6
8
|
|
|
7
9
|
def self.cache
|
|
@@ -76,6 +78,48 @@ module Yerba
|
|
|
76
78
|
File.mtime(@path) != @loaded_mtime
|
|
77
79
|
end
|
|
78
80
|
|
|
81
|
+
def [](selector)
|
|
82
|
+
return native_node_at(selector) unless selector.is_a?(String)
|
|
83
|
+
|
|
84
|
+
parsed = Selector.parse(selector)
|
|
85
|
+
matches = find_nodes(parsed)
|
|
86
|
+
return native_node_at(selector) if matches.nil?
|
|
87
|
+
|
|
88
|
+
matches = matches.map { |match| match&.dup }
|
|
89
|
+
parsed.wildcard? ? matches.compact : matches.first
|
|
90
|
+
end
|
|
91
|
+
alias node_at []
|
|
92
|
+
|
|
93
|
+
def value_at(selector)
|
|
94
|
+
return native_value_at(selector) unless selector.is_a?(String) || selector.is_a?(Selector)
|
|
95
|
+
|
|
96
|
+
parsed = Selector.parse(selector)
|
|
97
|
+
matches = find_nodes(parsed) unless parsed.wildcard?
|
|
98
|
+
return native_value_at(selector) if matches.nil?
|
|
99
|
+
|
|
100
|
+
node = matches.first
|
|
101
|
+
return nil if node.nil?
|
|
102
|
+
|
|
103
|
+
node.is_a?(Scalar) && node.tree_node? ? node.value : native_value_at(selector)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def nodes
|
|
107
|
+
generation = native_generation
|
|
108
|
+
return @nodes if @nodes_generation == generation
|
|
109
|
+
|
|
110
|
+
@nodes_generation = generation
|
|
111
|
+
@nodes = build_nodes(native_node_records)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def length_at(selector)
|
|
115
|
+
parsed = Selector.parse(selector)
|
|
116
|
+
matches = find_nodes(parsed) unless parsed.wildcard?
|
|
117
|
+
return Array(value_at(selector)).length if matches.nil?
|
|
118
|
+
|
|
119
|
+
children = matches.first&.tree_children
|
|
120
|
+
children.is_a?(Array) ? children.length : 0
|
|
121
|
+
end
|
|
122
|
+
|
|
79
123
|
def dig(*keys)
|
|
80
124
|
keys.reduce(self) { |node, key| node.nil? ? nil : node[key] }
|
|
81
125
|
end
|
|
@@ -177,6 +221,96 @@ module Yerba
|
|
|
177
221
|
|
|
178
222
|
private
|
|
179
223
|
|
|
224
|
+
def build_nodes(records)
|
|
225
|
+
built = []
|
|
226
|
+
|
|
227
|
+
records.each do |parent, type, selector, key_name, value, has_value, location, key_location|
|
|
228
|
+
key = Scalar.from_document(nil, nil, key_location, nil, value: key_name) if key_name
|
|
229
|
+
node = build_node(type, selector, location, key, value)
|
|
230
|
+
|
|
231
|
+
node.attach_tree(built.first || node, tree_container(type), !has_value) if type
|
|
232
|
+
add_child(built[parent], key_name, node) unless parent.negative?
|
|
233
|
+
|
|
234
|
+
built << node
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
built.each { |node| node&.tree_children&.freeze }
|
|
238
|
+
built.first
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build_node(type, selector, location, key, value)
|
|
242
|
+
case type
|
|
243
|
+
when :scalar
|
|
244
|
+
Scalar.from_document(self, selector, location, key, value: value)
|
|
245
|
+
when :map
|
|
246
|
+
Map.from_document(self, selector, location, key)
|
|
247
|
+
when :sequence
|
|
248
|
+
Sequence.from_document(self, selector, location, key)
|
|
249
|
+
else
|
|
250
|
+
native_node_at(selector)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def tree_container(type)
|
|
255
|
+
case type
|
|
256
|
+
when :map
|
|
257
|
+
{}
|
|
258
|
+
when :sequence
|
|
259
|
+
[]
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def add_child(parent, key_name, node)
|
|
264
|
+
children = parent.tree_children
|
|
265
|
+
|
|
266
|
+
if children.is_a?(Hash)
|
|
267
|
+
children[key_name] = node
|
|
268
|
+
else
|
|
269
|
+
children << node
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def find_nodes(selector)
|
|
274
|
+
return nil if selector.relative? || malformed?(selector.to_s) || nodes.nil?
|
|
275
|
+
|
|
276
|
+
wildcard = selector.wildcard?
|
|
277
|
+
|
|
278
|
+
selector.segments.reduce([nodes]) do |current, segment|
|
|
279
|
+
current.flat_map do |node|
|
|
280
|
+
children = node&.tree_node? ? node.tree_children : nil
|
|
281
|
+
matches = children && child_matches(children, segment)
|
|
282
|
+
return nil if matches.nil?
|
|
283
|
+
|
|
284
|
+
wildcard && children.is_a?(Hash) ? matches.reject { |match| valueless_entry?(match) } : matches
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def valueless_entry?(node)
|
|
290
|
+
node.valueless?
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def malformed?(selector)
|
|
294
|
+
selector.end_with?(".") || selector.include?("..") || (selector.include?("[") && !selector.include?("]"))
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def child_matches(children, segment)
|
|
298
|
+
case segment
|
|
299
|
+
when Selector::ALL_ITEMS
|
|
300
|
+
children if children.is_a?(Array)
|
|
301
|
+
when Selector::ALL_KEYS
|
|
302
|
+
children.values if children.is_a?(Hash)
|
|
303
|
+
when Integer
|
|
304
|
+
return nil unless children.is_a?(Array)
|
|
305
|
+
|
|
306
|
+
segment < children.length ? [children[segment]] : []
|
|
307
|
+
else
|
|
308
|
+
return nil unless children.is_a?(Hash)
|
|
309
|
+
|
|
310
|
+
children.key?(segment) ? [children[segment]] : []
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
180
314
|
def empty_root?
|
|
181
315
|
root.is_a?(Scalar) && root.value.nil?
|
|
182
316
|
end
|
data/lib/yerba/formatting.rb
CHANGED
data/lib/yerba/location.rb
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Yerba
|
|
4
|
-
Location = Data.define(
|
|
4
|
+
Location = Data.define(
|
|
5
|
+
:start_line, :start_column, :end_line, :end_column, :start_offset, :end_offset,
|
|
6
|
+
:start_character_column, :end_character_column
|
|
7
|
+
) do
|
|
8
|
+
def initialize(start_column:, end_column:, start_character_column: start_column, end_character_column: end_column, **)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
end
|
|
5
12
|
end
|
data/lib/yerba/map.rb
CHANGED
|
@@ -40,13 +40,14 @@ module Yerba
|
|
|
40
40
|
all_keys = keys
|
|
41
41
|
key_index = all_keys.index(key.to_s)
|
|
42
42
|
after_key = key_index&.positive? ? all_keys[key_index - 1] : nil
|
|
43
|
+
before_key = key_index&.zero? ? all_keys[1] : nil
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
document.insert(new_path, coerced, after: after_key, plain: true)
|
|
45
|
+
if after_key || before_key
|
|
46
|
+
document.delete(new_path)
|
|
47
|
+
document.insert(new_path, coerced, after: after_key, before: before_key, plain: true)
|
|
48
48
|
else
|
|
49
|
-
document.
|
|
49
|
+
document.set(new_path, coerce_value(value, style: :flow), plain: true)
|
|
50
|
+
document[new_path].collection_style = :block unless value.empty?
|
|
50
51
|
end
|
|
51
52
|
elsif document.exists?(new_path)
|
|
52
53
|
document.set(new_path, coerced, plain: is_collection)
|
|
@@ -93,6 +94,12 @@ module Yerba
|
|
|
93
94
|
return self
|
|
94
95
|
end
|
|
95
96
|
|
|
97
|
+
if (tree = current_children)
|
|
98
|
+
tree.each_key { |name| yield name, tree[name]&.dup }
|
|
99
|
+
|
|
100
|
+
return self
|
|
101
|
+
end
|
|
102
|
+
|
|
96
103
|
names = keys
|
|
97
104
|
values = document.get_all(@selector.empty? ? "*" : "#{@selector}.*")
|
|
98
105
|
|
|
@@ -124,6 +131,9 @@ module Yerba
|
|
|
124
131
|
|
|
125
132
|
def value_at(key)
|
|
126
133
|
if connected?
|
|
134
|
+
child = current_children&.[](key.to_s)
|
|
135
|
+
return child.value if child.is_a?(Scalar)
|
|
136
|
+
|
|
127
137
|
new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
|
|
128
138
|
document.value_at(new_path)
|
|
129
139
|
else
|
|
@@ -135,7 +145,7 @@ module Yerba
|
|
|
135
145
|
#
|
|
136
146
|
# When +key+ is given and the map is connected to a document, deletes that key
|
|
137
147
|
# from the map in the YAML document. When no +key+ is given and the map is
|
|
138
|
-
# connected, removes the entire map node at its selector
|
|
148
|
+
# connected, removes the entire map node at its selector. When not connected
|
|
139
149
|
# to a document, delegates to the underlying Hash.
|
|
140
150
|
#
|
|
141
151
|
# Returns +self+.
|
data/lib/yerba/node.rb
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Yerba
|
|
4
|
+
module NodeTree
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def tree_node?
|
|
8
|
+
!@tree.nil? && @tree.equal?(document.nodes)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def tree_children
|
|
12
|
+
@children
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valueless?
|
|
16
|
+
@valueless == true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def attach_tree(tree, children, valueless)
|
|
20
|
+
@tree = tree
|
|
21
|
+
@children = children
|
|
22
|
+
@valueless = valueless
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
4
26
|
module Node
|
|
27
|
+
include NodeTree
|
|
28
|
+
|
|
5
29
|
attr_reader :selector, :location, :key
|
|
6
30
|
|
|
7
31
|
def file_path
|
|
@@ -26,6 +50,17 @@ module Yerba
|
|
|
26
50
|
document.source(@selector)
|
|
27
51
|
end
|
|
28
52
|
|
|
53
|
+
def children
|
|
54
|
+
tree = current_children
|
|
55
|
+
|
|
56
|
+
case tree
|
|
57
|
+
when Hash
|
|
58
|
+
tree.transform_values { |child| child&.dup }
|
|
59
|
+
when Array
|
|
60
|
+
tree.map { |child| child&.dup }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
29
64
|
module ClassMethods
|
|
30
65
|
def from_document(document, selector, location = nil, key = nil, **attributes)
|
|
31
66
|
instance = allocate
|
|
@@ -58,6 +93,12 @@ module Yerba
|
|
|
58
93
|
|
|
59
94
|
private
|
|
60
95
|
|
|
96
|
+
def current_children
|
|
97
|
+
node = tree_node? ? self : document&.[](@selector)
|
|
98
|
+
|
|
99
|
+
node.tree_children if node.is_a?(Node) && node.tree_node?
|
|
100
|
+
end
|
|
101
|
+
|
|
61
102
|
def init_node(document, selector, location, key, file_path, line)
|
|
62
103
|
@document = document
|
|
63
104
|
@selector = selector
|
data/lib/yerba/query_result.rb
CHANGED
|
@@ -4,47 +4,70 @@ module Yerba
|
|
|
4
4
|
class QueryResult
|
|
5
5
|
include Enumerable
|
|
6
6
|
|
|
7
|
-
def initialize(sequence, indices)
|
|
7
|
+
def initialize(sequence, indices, &query)
|
|
8
8
|
@sequence = sequence
|
|
9
9
|
@indices = indices
|
|
10
|
+
@query = query
|
|
11
|
+
@tree = current_tree
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def each
|
|
13
15
|
return enum_for(:each) unless block_given?
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
current_indices.each { |index| yield @sequence[index] }
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def [](position)
|
|
19
|
-
index =
|
|
21
|
+
index = current_indices[position]
|
|
20
22
|
|
|
21
23
|
@sequence[index] if index
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def first
|
|
25
|
-
self[0]
|
|
26
|
+
def first(count = nil)
|
|
27
|
+
return self[0] if count.nil?
|
|
28
|
+
|
|
29
|
+
current_indices.first(count).map { |index| @sequence[index] }
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
def last
|
|
29
|
-
self[-1]
|
|
32
|
+
def last(count = nil)
|
|
33
|
+
return self[-1] if count.nil?
|
|
34
|
+
|
|
35
|
+
current_indices.last(count).map { |index| @sequence[index] }
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def length
|
|
33
|
-
|
|
39
|
+
current_indices.length
|
|
34
40
|
end
|
|
35
41
|
alias size length
|
|
36
42
|
alias count length
|
|
37
43
|
|
|
38
44
|
def empty?
|
|
39
|
-
|
|
45
|
+
current_indices.empty?
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
def indices
|
|
43
|
-
|
|
49
|
+
current_indices.dup
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
def inspect
|
|
47
53
|
"#<Yerba::QueryResult length=#{length}>"
|
|
48
54
|
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def current_indices
|
|
59
|
+
tree = current_tree
|
|
60
|
+
|
|
61
|
+
if @query && !tree.equal?(@tree)
|
|
62
|
+
@indices = @query.call
|
|
63
|
+
@tree = tree
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@indices
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def current_tree
|
|
70
|
+
@sequence&.document&.nodes
|
|
71
|
+
end
|
|
49
72
|
end
|
|
50
73
|
end
|
data/lib/yerba/scalar.rb
CHANGED
|
@@ -12,6 +12,11 @@ module Yerba
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def value
|
|
15
|
+
live = live_document if @selector
|
|
16
|
+
adopt_current_node(live) if live && !tree_node?
|
|
17
|
+
|
|
18
|
+
return @value.is_a?(String) ? @value.dup : @value if tree_node?
|
|
19
|
+
|
|
15
20
|
@value ||= document&.value_at(@selector)
|
|
16
21
|
end
|
|
17
22
|
|
|
@@ -72,8 +77,32 @@ module Yerba
|
|
|
72
77
|
end
|
|
73
78
|
end
|
|
74
79
|
|
|
80
|
+
protected
|
|
81
|
+
|
|
82
|
+
def stored_value
|
|
83
|
+
@value
|
|
84
|
+
end
|
|
85
|
+
|
|
75
86
|
private
|
|
76
87
|
|
|
88
|
+
def live_document
|
|
89
|
+
return @document if @document
|
|
90
|
+
|
|
91
|
+
document if @file_path && Document.cache.key?(@file_path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def adopt_current_node(live)
|
|
95
|
+
node = live[@selector]
|
|
96
|
+
|
|
97
|
+
if node.is_a?(Scalar) && node.tree_node?
|
|
98
|
+
@value = node.stored_value
|
|
99
|
+
attach_tree(live.nodes, nil, node.valueless?)
|
|
100
|
+
else
|
|
101
|
+
@value = nil
|
|
102
|
+
@tree = nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
77
106
|
def init_from(value: nil, quote_style: nil, **)
|
|
78
107
|
@value = value
|
|
79
108
|
@quote_style = quote_style
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Yerba
|
|
6
|
+
# A parsed selector, split into its segments.
|
|
7
|
+
#
|
|
8
|
+
# Yerba::Selector.parse("[3].talks[1]").segments # => [3, "talks", 1]
|
|
9
|
+
# Yerba::Selector.parse("[].speakers[]").segments # => [:all_items, "speakers", :all_items]
|
|
10
|
+
# Yerba::Selector.parse("database.*").segments # => ["database", :all_keys]
|
|
11
|
+
# Yerba::Selector.parse(".title").relative? # => true
|
|
12
|
+
#
|
|
13
|
+
# Keys are Strings, indexes Integers (negative from the end), `[]` is `:all_items` and `*` is `:all_keys`.
|
|
14
|
+
#
|
|
15
|
+
class Selector
|
|
16
|
+
ALL_KEYS = :all_keys
|
|
17
|
+
ALL_ITEMS = :all_items
|
|
18
|
+
CACHE_LIMIT = 10_000
|
|
19
|
+
|
|
20
|
+
attr_reader :segments
|
|
21
|
+
|
|
22
|
+
def self.parse(selector)
|
|
23
|
+
return selector if selector.is_a?(Selector)
|
|
24
|
+
|
|
25
|
+
string = selector.to_s
|
|
26
|
+
|
|
27
|
+
cache[string] ||= begin
|
|
28
|
+
cache.clear if cache.size >= CACHE_LIMIT
|
|
29
|
+
parsed = native_parse(string)
|
|
30
|
+
|
|
31
|
+
new(parsed["segments"].map { |segment| decode(segment) }, relative: parsed["relative"], source: string)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Builds a selector from segments: Strings (keys), Integers (indexes), `:all_items` or `:all_keys`.
|
|
36
|
+
#
|
|
37
|
+
# Yerba::Selector.build([3, "talks", 1]).to_s # => "[3].talks[1]"
|
|
38
|
+
#
|
|
39
|
+
def self.build(segments, relative: false)
|
|
40
|
+
new(segments, relative: relative)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.clear_cache!
|
|
44
|
+
@cache = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.cache
|
|
48
|
+
@cache ||= {}
|
|
49
|
+
end
|
|
50
|
+
private_class_method :cache
|
|
51
|
+
|
|
52
|
+
def self.decode(segment)
|
|
53
|
+
case segment
|
|
54
|
+
when Hash
|
|
55
|
+
segment["all"] == "keys" ? ALL_KEYS : ALL_ITEMS
|
|
56
|
+
else
|
|
57
|
+
segment
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
private_class_method :decode
|
|
61
|
+
|
|
62
|
+
def initialize(segments, relative: false, source: nil)
|
|
63
|
+
@segments = segments.dup.freeze
|
|
64
|
+
@relative = relative
|
|
65
|
+
@source = source
|
|
66
|
+
|
|
67
|
+
freeze
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def relative?
|
|
71
|
+
@relative
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def absolute?
|
|
75
|
+
!@relative
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def empty?
|
|
79
|
+
segments.empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def wildcard?
|
|
83
|
+
segments.any? { |segment| [ALL_ITEMS, ALL_KEYS].include?(segment) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# "[3].talks[1]" => "[3].talks"
|
|
87
|
+
def parent
|
|
88
|
+
self.class.build(segments[0...-1], relative: relative?)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Selector.parse("[3]").join("talks", 1) => "[3].talks[1]"
|
|
92
|
+
def join(*more)
|
|
93
|
+
self.class.build(segments + more, relative: relative?)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def dig(values)
|
|
97
|
+
raise ArgumentError, "Cannot dig a wildcard selector (#{self}), use Document#find or Collection.find instead" if wildcard?
|
|
98
|
+
|
|
99
|
+
segments.reduce(values) do |current, segment|
|
|
100
|
+
case current
|
|
101
|
+
when Array
|
|
102
|
+
segment.is_a?(Integer) ? current[segment] : nil
|
|
103
|
+
when Hash
|
|
104
|
+
segment.is_a?(Integer) ? nil : current[segment]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_s
|
|
110
|
+
@source || self.class.send(:native_build, JSON.generate(encoded_segments), relative?)
|
|
111
|
+
end
|
|
112
|
+
alias to_str to_s
|
|
113
|
+
|
|
114
|
+
def ==(other)
|
|
115
|
+
other.is_a?(Selector) && other.segments == segments && other.relative? == relative?
|
|
116
|
+
end
|
|
117
|
+
alias eql? ==
|
|
118
|
+
|
|
119
|
+
def hash
|
|
120
|
+
[self.class, segments, relative?].hash
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def inspect
|
|
124
|
+
"#<#{self.class.name} #{to_s.inspect} #{segments.inspect}>"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def encoded_segments
|
|
130
|
+
segments.map do |segment|
|
|
131
|
+
case segment
|
|
132
|
+
when ALL_ITEMS
|
|
133
|
+
{ all: "items" }
|
|
134
|
+
when ALL_KEYS
|
|
135
|
+
{ all: "keys" }
|
|
136
|
+
else
|
|
137
|
+
segment
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|