janeway-jsonpath 0.5.0 → 1.0.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +84 -6
  3. data/bin/janeway +77 -28
  4. data/lib/janeway/ast/child_segment.rb +9 -7
  5. data/lib/janeway/ast/expression.rb +2 -1
  6. data/lib/janeway/ast/filter_selector.rb +0 -2
  7. data/lib/janeway/ast/function.rb +2 -0
  8. data/lib/janeway/ast/selector.rb +17 -18
  9. data/lib/janeway/ast.rb +31 -0
  10. data/lib/janeway/enumerator.rb +161 -5
  11. data/lib/janeway/interpreter.rb +11 -5
  12. data/lib/janeway/interpreters/array_slice_selector_delete_if.rb +57 -0
  13. data/lib/janeway/interpreters/array_slice_selector_deleter.rb +1 -1
  14. data/lib/janeway/interpreters/child_segment_delete_if.rb +20 -0
  15. data/lib/janeway/interpreters/child_segment_deleter.rb +1 -1
  16. data/lib/janeway/interpreters/descendant_segment_interpreter.rb +1 -1
  17. data/lib/janeway/interpreters/filter_selector_delete_if.rb +73 -0
  18. data/lib/janeway/interpreters/index_selector_delete_if.rb +42 -0
  19. data/lib/janeway/interpreters/index_selector_interpreter.rb +3 -1
  20. data/lib/janeway/interpreters/iteration_helper.rb +45 -0
  21. data/lib/janeway/interpreters/name_selector_delete_if.rb +42 -0
  22. data/lib/janeway/interpreters/root_node_delete_if.rb +34 -0
  23. data/lib/janeway/interpreters/tree_constructor.rb +31 -1
  24. data/lib/janeway/interpreters/wildcard_selector_delete_if.rb +61 -0
  25. data/lib/janeway/interpreters/yielder.rb +7 -33
  26. data/lib/janeway/lexer.rb +46 -40
  27. data/lib/janeway/normalized_path.rb +12 -5
  28. data/lib/janeway/parser.rb +2 -0
  29. data/lib/janeway/query.rb +38 -0
  30. data/lib/janeway/version.rb +2 -1
  31. data/lib/janeway.rb +61 -35
  32. metadata +12 -3
@@ -11,6 +11,7 @@ module Janeway
11
11
  # It should be created for a single query and then discarded.
12
12
  class Interpreter
13
13
  attr_reader :jsonpath, :output
14
+
14
15
  include Interpreters
15
16
 
16
17
  # Interpret a query on the given input, return result
@@ -25,7 +26,7 @@ module Janeway
25
26
  # @param query [Query] abstract syntax tree of the jsonpath query
26
27
  def initialize(query, as: :finder, &block)
27
28
  raise ArgumentError, "expect Query, got #{query.inspect}" unless query.is_a?(Query)
28
- unless %i[finder iterator deleter].include?(as)
29
+ unless %i[finder iterator deleter delete_if].include?(as)
29
30
  raise ArgumentError, "invalid interpreter type: #{as.inspect}"
30
31
  end
31
32
 
@@ -49,10 +50,6 @@ module Janeway
49
50
  # @param input [Array, Hash] object to be searched
50
51
  # @return [Object]
51
52
  def interpret(input)
52
- unless input.is_a?(Hash) || input.is_a?(Array)
53
- return [] # can't query on any other types, but need to check because a string is also valid json
54
- end
55
-
56
53
  @root.interpret(nil, nil, input, [])
57
54
  rescue StandardError => e
58
55
  # Error during interpretation. Convert it to a Janeway::Error and include the query in the message
@@ -80,6 +77,7 @@ module Janeway
80
77
  case @type
81
78
  when :iterator then interpreters.push(Yielder.new(&block))
82
79
  when :deleter then interpreters.push make_deleter(interpreters.pop)
80
+ when :delete_if then interpreters.push make_delete_if(interpreters.pop, &block)
83
81
  end
84
82
 
85
83
  # Link interpreters together
@@ -114,6 +112,14 @@ module Janeway
114
112
  TreeConstructor.ast_node_to_deleter(interpreter.node)
115
113
  end
116
114
 
115
+ # Make a DeleteIf that will delete the results matched by a Selector,
116
+ # after yielding to a block for approval.
117
+ #
118
+ # @param interpreter [Interpreters::Base] interpeter subclass
119
+ def make_delete_if(interpreter, &block)
120
+ TreeConstructor.ast_node_to_delete_if(interpreter.node, &block)
121
+ end
122
+
117
123
  # Return an Interpreter::Error with the specified message, include the query.
118
124
  #
119
125
  # @param msg [String] error message
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'array_slice_selector_interpreter'
4
+ require_relative 'iteration_helper'
5
+
6
+ module Janeway
7
+ module Interpreters
8
+ # Delete values that match the array slice selector and yield true from the block
9
+ class ArraySliceSelectorDeleteIf < ArraySliceSelectorInterpreter
10
+ include IterationHelper
11
+
12
+ # @param node [AST::Expression]
13
+ def initialize(node, &block)
14
+ super(node)
15
+ @block = block
16
+
17
+ # Make a proc that yields the correct number of values to a block
18
+ @yield_proc = make_yield_proc(&block)
19
+ end
20
+
21
+ # Delete values at the indices matched by the array slice selector
22
+ #
23
+ # @param input [Array, Hash] the results of processing so far
24
+ # @param _parent [Array, Hash] parent of the input object
25
+ # @param _root [Array, Hash] the entire input
26
+ # @param path [Array<String>] elements of normalized path to the current input
27
+ # @return [Array]
28
+ def interpret(input, _parent, _root, path)
29
+ return [] unless input.is_a?(Array)
30
+ return [] if selector&.step&.zero? # RFC: When step is 0, no elements are selected.
31
+
32
+ # Calculate the upper and lower indices of the target range
33
+ lower = selector.lower_index(input.size)
34
+ upper = selector.upper_index(input.size)
35
+
36
+ # Convert bounds and step to index values.
37
+ # Omit the final index, since no value is collected for that.
38
+ # Delete indexes from largest to smallest, so that deleting an index does
39
+ # not change the remaining indexes
40
+ results = []
41
+ if selector.step.positive?
42
+ indexes = lower.step(to: upper - 1, by: selector.step).to_a
43
+ indexes.reverse_each do |i|
44
+ next unless @yield_proc.call(input[i], input, path + [i])
45
+
46
+ results << input.delete_at(i)
47
+ end
48
+ results.reverse
49
+ else
50
+ indexes = upper.step(to: lower + 1, by: selector.step).to_a
51
+ indexes.each { |i| results << input.delete_at(i) }
52
+ results
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -6,7 +6,7 @@ module Janeway
6
6
  module Interpreters
7
7
  # Interprets array slice selector and deletes matching values
8
8
  class ArraySliceSelectorDeleter < ArraySliceSelectorInterpreter
9
- # Delete values at the indices matched by the array slice selector
9
+ # Delete values matched by the array slice selector
10
10
  #
11
11
  # @param input [Array, Hash] the results of processing so far
12
12
  # @param _parent [Array, Hash] parent of the input object
@@ -0,0 +1,20 @@
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 if
8
+ # the given block yields a truthy value
9
+ class ChildSegmentDeleteIf < ChildSegmentInterpreter
10
+ # @param child_segment [AST::ChildSegment]
11
+ def initialize(child_segment, &block)
12
+ super(child_segment)
13
+ @selectors =
14
+ child_segment.map do |expr|
15
+ TreeConstructor.ast_node_to_delete_if(expr, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -5,7 +5,7 @@ require_relative 'child_segment_interpreter'
5
5
  module Janeway
6
6
  module Interpreters
7
7
  # Child segment interpreter with selectors that delete matching elements
8
- class ChildSegmentDeleter < ChildSegmentInterpreter
8
+ class ChildSegmentDeleter < ChildSegmentInterpreter
9
9
  # @param child_segment [AST::ChildSegment]
10
10
  def initialize(child_segment)
11
11
  super
@@ -37,7 +37,7 @@ module Janeway
37
37
  end
38
38
  # basic types are ignored, they will be added by one of the above
39
39
 
40
- results.flatten(1).compact
40
+ results.flatten(1)
41
41
  end
42
42
  end
43
43
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'filter_selector_interpreter'
4
+ require_relative 'iteration_helper'
5
+
6
+ module Janeway
7
+ module Interpreters
8
+ # Interprets a filter selector by deleting matching values for which the block returns a truthy value
9
+ class FilterSelectorDeleteIf < FilterSelectorInterpreter
10
+ include IterationHelper
11
+
12
+ # @param selector [AST::FilterSelector]
13
+ def initialize(selector, &block)
14
+ super(selector)
15
+ @block = block
16
+
17
+ # Make a proc that yields the correct number of values to a block
18
+ @yield_proc = make_yield_proc(&block)
19
+ end
20
+
21
+ # Interpret selector on the input.
22
+ # @param input [Hash] the results of processing so far
23
+ # @param root [Array, Hash] the entire input
24
+ # @param path [Array<String>] elements of normalized path to the current input
25
+ def interpret_hash(input, root, path)
26
+ # Apply filter expressions to the input data
27
+ results = []
28
+ input.each do |key, value|
29
+ # Run filter and interpret result
30
+ result = @expr.interpret(value, nil, root, [])
31
+ case result
32
+ when FalseClass then next # comparison test - fail
33
+ when Array then next if result.empty?
34
+ end
35
+
36
+ # filter test passed, next yield value to block
37
+ next unless @yield_proc.call(value, input, path + [key])
38
+
39
+ results << input.delete(key)
40
+ end
41
+ results
42
+ end
43
+
44
+ # Interpret selector on the input.
45
+ # @param input [Array] the results of processing so far
46
+ # @param root [Array, Hash] the entire input
47
+ # @param path [Array<String>] elements of normalized path to the current input
48
+ def interpret_array(input, root, path)
49
+ # Apply filter expressions to the input data
50
+ results = []
51
+
52
+ # Iterate in reverse order so that deletion does not alter the remaining indexes
53
+ i = input.size
54
+ input.reverse_each do |value|
55
+ i -= 1 # calculate array index
56
+
57
+ # Run filter and interpret result
58
+ result = @expr.interpret(value, nil, root, [])
59
+ case result
60
+ when FalseClass then next # comparison test - fail
61
+ when Array then next if result.empty?
62
+ end
63
+
64
+ # filter test passed, next yield value to block
65
+ next unless @yield_proc.call(value, input, path + [i])
66
+
67
+ results << input.delete_at(i)
68
+ end
69
+ results.reverse
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'index_selector_interpreter'
4
+ require_relative 'iteration_helper'
5
+
6
+ module Janeway
7
+ module Interpreters
8
+ # Interprets an index selector, and deletes the matched value if the block returns true.
9
+ class IndexSelectorDeleteIf < IndexSelectorInterpreter
10
+ include IterationHelper
11
+
12
+ # @param selector [AST::IndexSelector]
13
+ def initialize(selector, &block)
14
+ super(selector)
15
+ @block = block
16
+
17
+ # Make a proc that yields the correct number of values to a block
18
+ @yield_proc = make_yield_proc(&block)
19
+ end
20
+
21
+ # Interpret selector on the given input.
22
+ # @param input [Array, Hash] the results of processing so far
23
+ # @param _parent [Array, Hash] parent of the input object
24
+ # @param _root [Array, Hash] the entire input
25
+ # @param path [Array<String>] elements of normalized path to the current input
26
+ def interpret(input, _parent, _root, path)
27
+ return [] unless input.is_a?(Array)
28
+
29
+ index = selector.value
30
+ result = input.fetch(index) # raises IndexError if no such index
31
+ index += input.size if index.negative? # yield positive index for the normalize path
32
+ return unless @yield_proc.call(input[index], input, path + [index])
33
+
34
+ index += input.size if index.negative?
35
+ input.delete_at(index) # returns nil if deleted value is nil, or if no value was deleted
36
+ [result]
37
+ rescue IndexError
38
+ []
39
+ end
40
+ end
41
+ end
42
+ end
@@ -31,7 +31,9 @@ module Janeway
31
31
  result = input.fetch(selector.value) # raises IndexError if no such index
32
32
  return [result] unless @next
33
33
 
34
- @next.interpret(result, input, root, path + [selector.value])
34
+ index = selector.value
35
+ index += input.size if index.negative?
36
+ @next.interpret(result, input, root, path + [index])
35
37
  rescue IndexError
36
38
  []
37
39
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../normalized_path'
4
+
5
+ module Janeway
6
+ module Interpreters
7
+ # Mixin for interpreter classes that yield to a block
8
+ module IterationHelper
9
+ # Returns a Proc that yields the correct number of parameters to a block
10
+ #
11
+ # block.arity is -1 when no block is given, and an enumerator is being returned
12
+ # @return [Proc] which takes 3 parameters
13
+ def make_yield_proc(&block)
14
+ if block.arity.negative?
15
+ # Yield just the value to an enumerator, to enable instance method calls on
16
+ # matched values like this: enum.delete_if(&:even?)
17
+ proc { |value, _parent, _path| @block.call(value) }
18
+ elsif block.arity > 3
19
+ # Only do the work of constructing the normalized path when it is actually used
20
+ proc { |value, parent, path| @block.call(value, parent, path.last, normalized_path(path)) }
21
+ else
22
+ # block arity is 1, 2 or 3. Send all 3 parameters regardless.
23
+ proc { |value, parent, path| @block.call(value, parent, path.last) }
24
+ end
25
+ end
26
+
27
+ # Convert the list of path elements into a normalized query string.
28
+ #
29
+ # This form uses a subset of jsonpath that unambiguously points to a value
30
+ # using only name and index selectors.
31
+ # @see https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths
32
+ #
33
+ # Name selectors must use bracket notation, not shorthand.
34
+ #
35
+ # @param components [Array<String, Integer>]
36
+ # @return [String]
37
+ def normalized_path(components)
38
+ # First component is the root identifer, the remaining components are
39
+ # all index selectors or name selectors.
40
+ # Handle the root identifier separately, because .normalize does not handle those.
41
+ '$' + components[1..].map { NormalizedPath.normalize(_1) }.join
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'name_selector_interpreter'
4
+ require_relative 'iteration_helper'
5
+
6
+ module Janeway
7
+ module Interpreters
8
+ # Interprets a name selector, and deletes the matched values if the block returns a truthy value.
9
+ class NameSelectorDeleteIf < NameSelectorInterpreter
10
+ include IterationHelper
11
+
12
+ # @param selector [AST::NameSelector]
13
+ def initialize(selector, &block)
14
+ super(selector)
15
+ @block = block
16
+
17
+ # Make a proc that yields the correct number of values to a block
18
+ @yield_proc = make_yield_proc(&block)
19
+ end
20
+
21
+ # Interpret selector on the given input.
22
+ # Delete matching value for which the block returns truthy value.
23
+ #
24
+ # @param input [Array, Hash] the results of processing so far
25
+ # @param _parent [Array, Hash] parent of the input object
26
+ # @param _root [Array, Hash] the entire input
27
+ # @param path [Array] elements of normalized path to the current input
28
+ def interpret(input, _parent, _root, path)
29
+ return [] unless input.is_a?(Hash) && input.key?(name)
30
+ return [] unless @yield_proc.call(input[name], input, path + [name])
31
+
32
+ [input.delete(name)]
33
+ end
34
+
35
+ # Return hash representation of this interpreter
36
+ # @return [Hash]
37
+ def as_json
38
+ { type: type, value: name }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,34 @@
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
@@ -17,6 +17,12 @@ module Janeway
17
17
  alias_method :node, :interpret
18
18
  end
19
19
 
20
+ # Return an interpreter for the given AST node.
21
+ # This interpreter forwards matcheed values to the next interpreter, or
22
+ # returns them if there is no next interpreter.
23
+ #
24
+ # @param expr [AST::Expression]
25
+ # @return [Interprteters::Base]
20
26
  def self.ast_node_to_interpreter(expr)
21
27
  case expr
22
28
  when AST::RootNode then Interpreters::RootNodeInterpreter.new(expr)
@@ -38,6 +44,11 @@ module Janeway
38
44
  end
39
45
  end
40
46
 
47
+ # Return a Deleter interpreter for the given AST node.
48
+ # This interpreter deletes matched values.
49
+ #
50
+ # @param expr [AST::Expression]
51
+ # @return [Interprteters::Base]
41
52
  def self.ast_node_to_deleter(expr)
42
53
  case expr
43
54
  when AST::IndexSelector then IndexSelectorDeleter.new(expr)
@@ -47,12 +58,31 @@ module Janeway
47
58
  when AST::WildcardSelector then WildcardSelectorDeleter.new(expr)
48
59
  when AST::ChildSegment then ChildSegmentDeleter.new(expr)
49
60
  when AST::RootNode then RootNodeDeleter.new(expr)
50
-
51
61
  when nil then nil # caller has no @next node
52
62
  else
53
63
  raise "Unknown AST expression: #{expr.inspect}"
54
64
  end
55
65
  end
66
+
67
+ # Return a DeleteIf interpreter for the given AST node.
68
+ # This interpreter deletes matched values, but only after
69
+ # yielding to a block that returns a truthy value.
70
+ #
71
+ # @param expr [AST::Expression]
72
+ # @return [Interprteters::Base]
73
+ def self.ast_node_to_delete_if(expr, &block)
74
+ case expr
75
+ when AST::IndexSelector then IndexSelectorDeleteIf.new(expr, &block)
76
+ when AST::ArraySliceSelector then ArraySliceSelectorDeleteIf.new(expr, &block)
77
+ when AST::NameSelector then NameSelectorDeleteIf.new(expr, &block)
78
+ when AST::FilterSelector then FilterSelectorDeleteIf.new(expr, &block)
79
+ when AST::WildcardSelector then WildcardSelectorDeleteIf.new(expr, &block)
80
+ when AST::ChildSegment then ChildSegmentDeleteIf.new(expr, &block)
81
+ when AST::RootNode then RootNodeDeleteIf.new(expr, &block)
82
+ else
83
+ raise "Unknown AST expression: #{expr.inspect}"
84
+ end
85
+ end
56
86
  end
57
87
  end
58
88
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'wildcard_selector_interpreter'
4
+ require_relative 'iteration_helper'
5
+
6
+ module Janeway
7
+ module Interpreters
8
+ # Interprets a wildcard selector by deleting array / hash values for which a block returns true.
9
+ class WildcardSelectorDeleteIf < WildcardSelectorInterpreter
10
+ include IterationHelper
11
+
12
+ # @param selector [AST::WildcardSelector]
13
+ def initialize(selector, &block)
14
+ super(selector)
15
+ @block = block
16
+
17
+ # Make a proc that yields the correct number of values to a block
18
+ @yield_proc = make_yield_proc(&block)
19
+ end
20
+
21
+ # Delete all elements from the input
22
+ #
23
+ # @param input [Array, Hash] the results of processing so far
24
+ # @param _parent [Array, Hash] parent of the input object
25
+ # @param _root [Array, Hash] the entire input
26
+ # @param path [Array<String>] elements of normalized path to the current input
27
+ # @return [Array] deleted elements
28
+ def interpret(input, _parent, _root, path)
29
+ case input
30
+ when Array then maybe_delete_array_values(input, path)
31
+ when Hash then maybe_delete_hash_values(input, path)
32
+ else []
33
+ end
34
+ end
35
+
36
+ # @param input [Array]
37
+ # @param path [Array]
38
+ def maybe_delete_array_values(input, path)
39
+ results = []
40
+ (input.size - 1).downto(0).each do |i|
41
+ next unless @yield_proc.yield(input[i], input, path + [i])
42
+
43
+ results << input.delete_at(i)
44
+ end
45
+ results.reverse
46
+ end
47
+
48
+ # @param input [Hash]
49
+ # @param path [Array]
50
+ def maybe_delete_hash_values(input, path)
51
+ results = []
52
+ input.each do |key, value|
53
+ next unless @yield_proc.yield(value, input, path + [key])
54
+
55
+ results << input.delete(key)
56
+ end
57
+ results
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,31 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
- require_relative '../normalized_path'
4
+ require_relative 'iteration_helper'
5
5
 
6
6
  module Janeway
7
7
  module Interpreters
8
8
  # Yields each input value.
9
9
  #
10
- # It is inserted at the end of the "real" selectors in the AST, to receive and yield the output.
10
+ # This is inserted at the end of the "real" selectors in the AST, to receive and yield the output.
11
11
  # This is a supporting class for the Janeway.each method.
12
12
  class Yielder
13
+ include IterationHelper
14
+
13
15
  def initialize(&block)
14
16
  @block = block
15
17
 
16
- # Decide how many parameters to yield to this block.
17
- # block.arity is -1 when no block was given, and an enumerator is being returned from #each
18
- @yield_to_block =
19
- if block.arity.negative?
20
- # Yield values only to an enumerator.
21
- proc { |value, _parent, _path| @block.call(value) }
22
- elsif block.arity > 3
23
- # Only do the work of constructing the normalized path when it is actually used
24
- proc { |value, parent, path| @block.call(value, parent, path.last, normalized_path(path)) }
25
- else
26
- # block arity is 1, 2 or 3. Send all 3.
27
- proc { |value, parent, path| @block.call(value, parent, path.last) }
28
- end
18
+ # Make a proc that yields the correct number of values to a block
19
+ @yield_proc = make_yield_proc(&block)
29
20
  end
30
21
 
31
22
  # Yield each input value
@@ -37,27 +28,10 @@ module Janeway
37
28
  # @yieldparam [Object] matched value
38
29
  # @return [Object] input as node list
39
30
  def interpret(input, parent, _root, path)
40
- @yield_to_block.call(input, parent, path)
31
+ @yield_proc.call(input, parent, path)
41
32
  input.is_a?(Array) ? input : [input]
42
33
  end
43
34
 
44
- # Convert the list of path elements into a normalized query string.
45
- #
46
- # This form uses a subset of jsonpath that unambiguously points to a value
47
- # using only name and index selectors.
48
- # @see https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths
49
- #
50
- # Name selectors must use bracket notation, not shorthand.
51
- #
52
- # @param components [Array<String, Integer>]
53
- # @return [String]
54
- def normalized_path(components)
55
- # First component is the root identifer, the remaining components are
56
- # all index selectors or name selectors.
57
- # Handle the root identifier separately, because .normalize does not handle those.
58
- '$' + components[1..].map { NormalizedPath.normalize(_1) }.join
59
- end
60
-
61
35
  # Dummy method from Interpreters::Base, allow child segment interpreter to disable the
62
36
  # non-exist 'next' link.
63
37
  # @return [void]