janeway-jsonpath 1.0.0 → 1.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/janeway/ast/array_slice_selector.rb +6 -90
  3. data/lib/janeway/ast/binary_operator.rb +57 -51
  4. data/lib/janeway/ast/child_segment.rb +2 -5
  5. data/lib/janeway/ast/current_node.rb +2 -15
  6. data/lib/janeway/ast/descendant_segment.rb +0 -3
  7. data/lib/janeway/ast/expression.rb +34 -5
  8. data/lib/janeway/ast/function.rb +15 -10
  9. data/lib/janeway/ast/name_selector.rb +12 -7
  10. data/lib/janeway/ast/root_node.rb +2 -15
  11. data/lib/janeway/ast/selector.rb +5 -6
  12. data/lib/janeway/ast/unary_operator.rb +12 -2
  13. data/lib/janeway/ast/wildcard_selector.rb +2 -2
  14. data/lib/janeway/enumerator.rb +2 -4
  15. data/lib/janeway/error.rb +6 -1
  16. data/lib/janeway/functions/count.rb +1 -8
  17. data/lib/janeway/functions/length.rb +1 -12
  18. data/lib/janeway/functions/match.rb +3 -8
  19. data/lib/janeway/functions/search.rb +3 -8
  20. data/lib/janeway/functions/value.rb +1 -7
  21. data/lib/janeway/functions.rb +75 -2
  22. data/lib/janeway/interpreter.rb +50 -31
  23. data/lib/janeway/interpreters/array_slice_selector_delete_if.rb +10 -13
  24. data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +56 -13
  25. data/lib/janeway/interpreters/binary_operator_interpreter.rb +18 -13
  26. data/lib/janeway/interpreters/descendant_segment_interpreter.rb +32 -13
  27. data/lib/janeway/interpreters/filter_selector_interpreter.rb +26 -34
  28. data/lib/janeway/interpreters/function_interpreter.rb +5 -1
  29. data/lib/janeway/interpreters/index_selector_delete_if.rb +0 -1
  30. data/lib/janeway/interpreters/iteration_helper.rb +19 -5
  31. data/lib/janeway/interpreters/root_node_delete_if.rb +57 -16
  32. data/lib/janeway/interpreters/tree_constructor.rb +0 -20
  33. data/lib/janeway/interpreters/wildcard_selector_delete_if.rb +16 -0
  34. data/lib/janeway/interpreters/yielder.rb +8 -2
  35. data/lib/janeway/lexer.rb +61 -78
  36. data/lib/janeway/location.rb +3 -1
  37. data/lib/janeway/normalized_path.rb +11 -2
  38. data/lib/janeway/parser.rb +46 -34
  39. data/lib/janeway/query.rb +47 -2
  40. data/lib/janeway/token.rb +2 -5
  41. data/lib/janeway/version.rb +1 -1
  42. metadata +2 -9
  43. data/lib/janeway/interpreters/array_slice_selector_deleter.rb +0 -41
  44. data/lib/janeway/interpreters/child_segment_deleter.rb +0 -19
  45. data/lib/janeway/interpreters/filter_selector_deleter.rb +0 -65
  46. data/lib/janeway/interpreters/index_selector_deleter.rb +0 -26
  47. data/lib/janeway/interpreters/name_selector_deleter.rb +0 -27
  48. data/lib/janeway/interpreters/root_node_deleter.rb +0 -34
  49. data/lib/janeway/interpreters/wildcard_selector_deleter.rb +0 -32
data/lib/janeway/query.rb CHANGED
@@ -43,6 +43,26 @@ module Janeway
43
43
  @root.singular_query?
44
44
  end
45
45
 
46
+ # Return a cached interpreter tree for the given type, building it via
47
+ # the block on the first call. Only stateless interpreter types
48
+ # (`:finder`, `:deleter`) benefit from caching — `:iterator` and
49
+ # `:delete_if` capture the caller's block, so their trees are
50
+ # per-call.
51
+ #
52
+ # No-op (yields fresh every time) if the Query is frozen — respecting
53
+ # the docstring's "can be frozen" contract.
54
+ #
55
+ # @param type [Symbol] :finder, :deleter, :iterator, :delete_if
56
+ # @yield build the tree if not cached
57
+ # @return [Interpreters::Base]
58
+ def interpreter_tree_for(type)
59
+ return yield unless %i[finder deleter].include?(type)
60
+ return yield if frozen?
61
+
62
+ @interpreter_tree_cache ||= {}
63
+ @interpreter_tree_cache[type] ||= yield
64
+ end
65
+
46
66
  # Return a list of the nodes in the AST.
47
67
  # The AST of a jsonpath query is a straight line, so this is expressible as an array.
48
68
  # The only part of the AST with branches is inside a filter selector, but that doesn't show up here.
@@ -76,10 +96,23 @@ module Janeway
76
96
  result.flatten.join("\n")
77
97
  end
78
98
 
79
- # Deep copy the query
99
+ # Deep copy the query.
100
+ #
101
+ # Only the top-level linked list of segments/selectors is cloned — that is
102
+ # the only part `pop` mutates. Nested contents (filter expressions,
103
+ # function parameters, child-segment members) are shared with the original,
104
+ # which is safe because the interpreter never mutates the AST.
105
+ #
106
+ # The tree cache is intentionally NOT shared with the dup: cached trees
107
+ # reference the original's top-level chain and are invalid for a dup that
108
+ # may later be popped.
109
+ #
80
110
  # @return [Query]
81
111
  def dup
82
- Parser.parse(to_s)
112
+ cloned = node_list.map(&:dup)
113
+ cloned.each_cons(2) { |a, b| a.next = b }
114
+ cloned.last.next = nil
115
+ Query.new(cloned.first, @jsonpath)
83
116
  end
84
117
 
85
118
  # Delete the last element from the chain of selectors.
@@ -104,5 +137,17 @@ module Janeway
104
137
  # Return the last selector
105
138
  nodes.last
106
139
  end
140
+
141
+ # Return a data-free split of this singular query into a "parent" Query
142
+ # plus the leaf selector — the parent addresses one level up from what
143
+ # `self` addresses. Used by Enumerator#insert to locate the container
144
+ # that a new value should be inserted into.
145
+ #
146
+ # @return [Array(Query, AST::Selector)] [parent_query, leaf_selector]
147
+ def parent_query
148
+ parent = dup
149
+ leaf = parent.pop
150
+ [parent, leaf]
151
+ end
107
152
  end
108
153
  end
data/lib/janeway/token.rb CHANGED
@@ -7,10 +7,7 @@ module Janeway
7
7
  class Token
8
8
  extend Forwardable
9
9
 
10
- attr_reader :type, :lexeme, :location
11
-
12
- # write-access so '-' operator can modify number value
13
- attr_accessor :literal
10
+ attr_reader :type, :lexeme, :literal, :location
14
11
 
15
12
  def_delegators :@location, :line, :col, :length
16
13
 
@@ -31,7 +28,7 @@ module Janeway
31
28
  when Integer, String then @literal == other
32
29
  when Symbol then @type == other
33
30
  when Token
34
- @type == other.type && @lexeme == other.lexem && @literal = other.literal
31
+ @type == other.type && @lexeme == other.lexeme && @literal == other.literal
35
32
  else
36
33
  raise ArgumentError, "don't know how to compare Token with #{other.inspect}"
37
34
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Janeway
4
4
  # Version for janeway-jsonpath gem
5
- VERSION = '1.0.0'
5
+ VERSION = '1.1.0'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: janeway-jsonpath
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fraser Hanson
@@ -62,33 +62,26 @@ files:
62
62
  - lib/janeway/functions/value.rb
63
63
  - lib/janeway/interpreter.rb
64
64
  - lib/janeway/interpreters/array_slice_selector_delete_if.rb
65
- - lib/janeway/interpreters/array_slice_selector_deleter.rb
66
65
  - lib/janeway/interpreters/array_slice_selector_interpreter.rb
67
66
  - lib/janeway/interpreters/base.rb
68
67
  - lib/janeway/interpreters/binary_operator_interpreter.rb
69
68
  - lib/janeway/interpreters/child_segment_delete_if.rb
70
- - lib/janeway/interpreters/child_segment_deleter.rb
71
69
  - lib/janeway/interpreters/child_segment_interpreter.rb
72
70
  - lib/janeway/interpreters/current_node_interpreter.rb
73
71
  - lib/janeway/interpreters/descendant_segment_interpreter.rb
74
72
  - lib/janeway/interpreters/filter_selector_delete_if.rb
75
- - lib/janeway/interpreters/filter_selector_deleter.rb
76
73
  - lib/janeway/interpreters/filter_selector_interpreter.rb
77
74
  - lib/janeway/interpreters/function_interpreter.rb
78
75
  - lib/janeway/interpreters/index_selector_delete_if.rb
79
- - lib/janeway/interpreters/index_selector_deleter.rb
80
76
  - lib/janeway/interpreters/index_selector_interpreter.rb
81
77
  - lib/janeway/interpreters/iteration_helper.rb
82
78
  - lib/janeway/interpreters/name_selector_delete_if.rb
83
- - lib/janeway/interpreters/name_selector_deleter.rb
84
79
  - lib/janeway/interpreters/name_selector_interpreter.rb
85
80
  - lib/janeway/interpreters/root_node_delete_if.rb
86
- - lib/janeway/interpreters/root_node_deleter.rb
87
81
  - lib/janeway/interpreters/root_node_interpreter.rb
88
82
  - lib/janeway/interpreters/tree_constructor.rb
89
83
  - lib/janeway/interpreters/unary_operator_interpreter.rb
90
84
  - lib/janeway/interpreters/wildcard_selector_delete_if.rb
91
- - lib/janeway/interpreters/wildcard_selector_deleter.rb
92
85
  - lib/janeway/interpreters/wildcard_selector_interpreter.rb
93
86
  - lib/janeway/interpreters/yielder.rb
94
87
  - lib/janeway/lexer.rb
@@ -116,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
109
  - !ruby/object:Gem::Version
117
110
  version: '0'
118
111
  requirements: []
119
- rubygems_version: 3.6.9
112
+ rubygems_version: 4.0.8
120
113
  specification_version: 4
121
114
  summary: jsonpath parser which implements the finalized IETF standard of Goessner
122
115
  JSONPath
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'array_slice_selector_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Interprets array slice selector and deletes matching values
8
- class ArraySliceSelectorDeleter < ArraySliceSelectorInterpreter
9
- # Delete values matched by the array slice selector
10
- #
11
- # @param input [Array, Hash] the results of processing so far
12
- # @param _parent [Array, Hash] parent of the input object
13
- # @param _root [Array, Hash] the entire input
14
- # @param _path [Array<String>] elements of normalized path to the current input
15
- # @return [Array]
16
- def interpret(input, _parent, _root, _path)
17
- return [] unless input.is_a?(Array)
18
- return [] if selector&.step&.zero? # RFC: When step is 0, no elements are selected.
19
-
20
- # Calculate the upper and lower indices of the target range
21
- lower = selector.lower_index(input.size)
22
- upper = selector.upper_index(input.size)
23
-
24
- # Convert bounds and step to index values.
25
- # Omit the final index, since no value is collected for that.
26
- # Delete indexes from largest to smallest, so that deleting an index does
27
- # not change the remaining indexes
28
- results = []
29
- if selector.step.positive?
30
- indexes = lower.step(to: upper - 1, by: selector.step).to_a
31
- indexes.reverse_each { |i| results << input.delete_at(i) }
32
- results.reverse
33
- else
34
- indexes = upper.step(to: lower + 1, by: selector.step).to_a
35
- indexes.each { |i| results << input.delete_at(i) }
36
- results
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'child_segment_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Child segment interpreter with selectors that delete matching elements
8
- class ChildSegmentDeleter < ChildSegmentInterpreter
9
- # @param child_segment [AST::ChildSegment]
10
- def initialize(child_segment)
11
- super
12
- @selectors =
13
- child_segment.map do |expr|
14
- TreeConstructor.ast_node_to_deleter(expr)
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'filter_selector_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Interprets a filter selector, and deletes matching values
8
- class FilterSelectorDeleter < FilterSelectorInterpreter
9
- # Interpret selector on the input.
10
- # @param input [Hash] the results of processing so far
11
- # @param root [Array, Hash] the entire input
12
- # @param _path [Array<String>] elements of normalized path to the current input
13
- def interpret_hash(input, root, _path)
14
- # Apply filter expressions to the input data
15
- results = []
16
- input.each do |key, value|
17
- # Run filter and interpret result
18
- result = @expr.interpret(value, nil, root, [])
19
- case result
20
- when TrueClass then results << value # comparison test - pass
21
- when FalseClass then next # comparison test - fail
22
- when Array
23
- next if result.empty?
24
-
25
- results << value # existence test - node list
26
- else
27
- results << value # existence test. Null values here == success.
28
- end
29
- input.delete(key)
30
- end
31
- results
32
- end
33
-
34
- # Interpret selector on the input.
35
- # @param input [Array] the results of processing so far
36
- # @param root [Array, Hash] the entire input
37
- # @param _path [Array<String>] elements of normalized path to the current input
38
- def interpret_array(input, root, _path)
39
- # Apply filter expressions to the input data
40
- results = []
41
-
42
- # Iterate in reverse order so that deletion does not alter the remaining indexes
43
- i = input.size
44
- input.reverse_each do |value|
45
- i -= 1 # calculate array index
46
-
47
- # Run filter and interpret result
48
- result = @expr.interpret(value, nil, root, [])
49
- case result
50
- when TrueClass then results << value # comparison test - pass
51
- when FalseClass then next # comparison test - fail
52
- when Array
53
- next if result.empty?
54
-
55
- results << value # existence test - node list
56
- else
57
- results << value # existence test. Null values here == success.
58
- end
59
- input.delete_at(i)
60
- end
61
- results.reverse
62
- end
63
- end
64
- end
65
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'index_selector_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Interprets an index selector, and deletes the matched value.
8
- class IndexSelectorDeleter < IndexSelectorInterpreter
9
- # Interpret selector on the given input.
10
- # @param input [Array, Hash] the results of processing so far
11
- # @param _parent [Array, Hash] parent of the input object
12
- # @param _root [Array, Hash] the entire input
13
- # @param _path [Array<String>] elements of normalized path to the current input
14
- def interpret(input, _parent, _root, _path)
15
- return [] unless input.is_a?(Array)
16
-
17
- index = selector.value
18
- result = input.fetch(index) # raises IndexError if no such index
19
- input.delete_at(index) # returns nil if deleted value is nil, or if no value was deleted
20
- [result]
21
- rescue IndexError
22
- []
23
- end
24
- end
25
- end
26
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'name_selector_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Interprets a name selector, and deletes the matched values.
8
- class NameSelectorDeleter < NameSelectorInterpreter
9
- # Interpret selector on the given input.
10
- # @param input [Array, Hash] the results of processing so far
11
- # @param _parent [Array, Hash] parent of the input object
12
- # @param _root [Array, Hash] the entire input
13
- # @param _path [Array<String>] elements of normalized path to the current input
14
- def interpret(input, _parent, _root, _path)
15
- return [] unless input.is_a?(Hash) && input.key?(name)
16
-
17
- [input.delete(name)]
18
- end
19
-
20
- # Return hash representation of this interpreter
21
- # @return [Hash]
22
- def as_json
23
- { type: type, value: name, next: @next&.as_json }
24
- end
25
- end
26
- end
27
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'root_node_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Interprets the root node for deletion
8
- class RootNodeDeleter < RootNodeInterpreter
9
- # Delete all values from the root node.
10
- #
11
- # TODO: unclear, for deletion is there a difference between queries '$' and '$.*'?
12
- #
13
- # @param _input [Array, Hash] the results of processing so far
14
- # @param _parent [Array, Hash] parent of the input object
15
- # @param root [Array, Hash] the entire input
16
- # @param _path [Array<String>] elements of normalized path to the current input
17
- # @return [Array] deleted elements
18
- def interpret(_input, _parent, root, _path)
19
- case root
20
- when Array
21
- results = root.dup
22
- root.clear
23
- results
24
- when Hash
25
- results = root.values
26
- root.clear
27
- results
28
- else
29
- []
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'wildcard_selector_interpreter'
4
-
5
- module Janeway
6
- module Interpreters
7
- # Interprets a wildcard selector, and deletes the results.
8
- class WildcardSelectorDeleter < WildcardSelectorInterpreter
9
- # Delete all elements from the input
10
- #
11
- # @param input [Array, Hash] the results of processing so far
12
- # @param _parent [Array, Hash] parent of the input object
13
- # @param _root [Array, Hash] the entire input
14
- # @param _path [Array<String>] elements of normalized path to the current input
15
- # @return [Array] deleted elements
16
- def interpret(input, _parent, _root, _path)
17
- case input
18
- when Array
19
- results = input.dup
20
- input.clear
21
- results
22
- when Hash
23
- results = input.values
24
- input.clear
25
- results
26
- else
27
- []
28
- end
29
- end
30
- end
31
- end
32
- end