jmespath-ruby 0.1.2 → 0.1.3
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/CHANGELOG.md +19 -1
- data/README.md +177 -4
- data/completions/_jmespath +50 -0
- data/completions/jmespath.bash +178 -0
- data/docs/guides/compilation.md +310 -0
- data/docs/guides/completion.md +69 -0
- data/docs/guides/errors.md +200 -3
- data/docs/guides/functions.md +33 -0
- data/docs/guides/search.md +23 -0
- data/lib/jmespath/ast.rb +6 -1
- data/lib/jmespath/cli/argument_parser.rb +31 -0
- data/lib/jmespath/cli/arguments.rb +80 -0
- data/lib/jmespath/cli/failure_diagnostic.rb +1 -0
- data/lib/jmespath/cli/function_listing.rb +48 -0
- data/lib/jmespath/cli/help.rb +43 -0
- data/lib/jmespath/cli/interactive_session.rb +106 -0
- data/lib/jmespath/cli.rb +112 -16
- data/lib/jmespath/data_access.rb +12 -9
- data/lib/jmespath/diagnostic.rb +4 -3
- data/lib/jmespath/errors.rb +14 -0
- data/lib/jmespath/explanation.rb +95 -0
- data/lib/jmespath/expression.rb +93 -7
- data/lib/jmespath/failure_capture.rb +65 -0
- data/lib/jmespath/functions/coercions.rb +12 -5
- data/lib/jmespath/functions/conversion.rb +19 -1
- data/lib/jmespath/functions/function_call.rb +1 -1
- data/lib/jmespath/functions/json_value.rb +50 -0
- data/lib/jmespath/functions/references.rb +11 -10
- data/lib/jmespath/functions/signature.rb +6 -4
- data/lib/jmespath/functions.rb +12 -6
- data/lib/jmespath/input.rb +37 -0
- data/lib/jmespath/normalizer.rb +22 -5
- data/lib/jmespath/parser/projections.rb +5 -5
- data/lib/jmespath/parser.rb +2 -2
- data/lib/jmespath/plan.rb +17 -2
- data/lib/jmespath/search_result.rb +23 -0
- data/lib/jmespath/syntax_diagnostic.rb +32 -0
- data/lib/jmespath/tracing/collector.rb +109 -0
- data/lib/jmespath/tracing/evaluator.rb +113 -0
- data/lib/jmespath/tracing/field_lookup.rb +49 -0
- data/lib/jmespath/version.rb +1 -1
- data/lib/jmespath.rb +43 -8
- data/sig/jmespath.rbs +47 -3
- metadata +17 -1
data/lib/jmespath/parser.rb
CHANGED
|
@@ -65,10 +65,10 @@ module JMESPath
|
|
|
65
65
|
node(kind, nil, children)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
def node(kind, value = nil, children = [], start: nil)
|
|
68
|
+
def node(kind, value = nil, children = [], start: nil, synthetic: false)
|
|
69
69
|
result = AST::Node.new(
|
|
70
70
|
kind: kind, value: value, children: children,
|
|
71
|
-
start: start || children.first&.start || previous.start, finish: previous.finish
|
|
71
|
+
start: start || children.first&.start || previous.start, finish: previous.finish, synthetic: synthetic
|
|
72
72
|
)
|
|
73
73
|
syntax!("Expression exceeds nesting limit") if result.depth > @max_depth
|
|
74
74
|
result
|
data/lib/jmespath/plan.rb
CHANGED
|
@@ -12,12 +12,20 @@ module JMESPath
|
|
|
12
12
|
}.freeze
|
|
13
13
|
|
|
14
14
|
def self.build(node, options = Options::DEFAULT)
|
|
15
|
-
Builder.new(options)
|
|
15
|
+
builder = Builder.new(options)
|
|
16
|
+
operation = builder.build(node)
|
|
17
|
+
native_functions = builder.native_functions&.freeze
|
|
18
|
+
builder.freeze
|
|
19
|
+
yield native_functions
|
|
20
|
+
operation
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
# @api private
|
|
19
24
|
class Builder
|
|
25
|
+
attr_reader :native_functions
|
|
26
|
+
|
|
20
27
|
def initialize(options)
|
|
28
|
+
@native_functions = nil
|
|
21
29
|
@functions = options.functions
|
|
22
30
|
@disable_visit_errors = options.disable_visit_errors
|
|
23
31
|
@evaluator = if @functions.equal?(Functions.default) && !@disable_visit_errors
|
|
@@ -25,7 +33,6 @@ module JMESPath
|
|
|
25
33
|
else
|
|
26
34
|
Evaluator.new(functions: @functions, disable_visit_errors: @disable_visit_errors)
|
|
27
35
|
end
|
|
28
|
-
freeze
|
|
29
36
|
end
|
|
30
37
|
|
|
31
38
|
def build(node)
|
|
@@ -110,6 +117,14 @@ module JMESPath
|
|
|
110
117
|
return function_call(name, node.children, source_range)
|
|
111
118
|
end
|
|
112
119
|
|
|
120
|
+
build_native_string(node, name, source_range)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def build_native_string(node, name, source_range)
|
|
124
|
+
# @type var native_functions: Array[AST::Node]
|
|
125
|
+
native_functions = @native_functions || []
|
|
126
|
+
native_functions << node
|
|
127
|
+
@native_functions = native_functions
|
|
113
128
|
string_function(name, build(node.children.fetch(0)), source_range)
|
|
114
129
|
end
|
|
115
130
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
# Immutable outcome of an expression search, retaining the original value or error.
|
|
5
|
+
class SearchResult
|
|
6
|
+
# @return [Object] the successful value, or nil on failure
|
|
7
|
+
attr_reader :value
|
|
8
|
+
# @return [StandardError, nil] the original library-owned failure
|
|
9
|
+
attr_reader :error
|
|
10
|
+
|
|
11
|
+
# @api private
|
|
12
|
+
def initialize(value:, error:)
|
|
13
|
+
@value = value
|
|
14
|
+
@error = error
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Boolean] whether evaluation succeeded, including a null result
|
|
19
|
+
def success?
|
|
20
|
+
error.nil?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
# Immutable syntax failure data returned by {JMESPath.validate}.
|
|
5
|
+
class SyntaxDiagnostic
|
|
6
|
+
# @return [String] reason without coordinates
|
|
7
|
+
attr_reader :reason
|
|
8
|
+
# @return [String] expression stored by the parser
|
|
9
|
+
attr_reader :expression
|
|
10
|
+
# @return [Integer, nil] zero-based byte offset, or nil for unknown locations
|
|
11
|
+
attr_reader :position
|
|
12
|
+
# @return [Integer, nil] one-based line, or nil for unknown locations
|
|
13
|
+
attr_reader :line
|
|
14
|
+
# @return [Integer, nil] one-based Unicode character column, or nil for unknown locations
|
|
15
|
+
attr_reader :column
|
|
16
|
+
|
|
17
|
+
# @api private
|
|
18
|
+
def initialize(error)
|
|
19
|
+
@reason = error.reason.dup.freeze
|
|
20
|
+
@expression = error.expression.dup.freeze
|
|
21
|
+
@position = error.position if error.location_known?
|
|
22
|
+
@line = error.line if error.location_known?
|
|
23
|
+
@column = error.column if error.location_known?
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Boolean] whether parsing reported a source location
|
|
28
|
+
def location_known?
|
|
29
|
+
!position.nil?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
# @api private
|
|
5
|
+
module Tracing
|
|
6
|
+
# @api private
|
|
7
|
+
class Collector
|
|
8
|
+
def initialize(root:, limit:)
|
|
9
|
+
@ids = {} # : Hash[AST::Node, Integer]
|
|
10
|
+
.compare_by_identity
|
|
11
|
+
index(root)
|
|
12
|
+
@limit = limit
|
|
13
|
+
@events = []
|
|
14
|
+
@contexts = {}
|
|
15
|
+
@references = {}
|
|
16
|
+
@truncated = false
|
|
17
|
+
@closed = false
|
|
18
|
+
@mutex = Mutex.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def operation_id(node)
|
|
22
|
+
@ids.fetch(node)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def within(node, index)
|
|
26
|
+
thread = Thread.current
|
|
27
|
+
stack = @mutex.synchronize do
|
|
28
|
+
unless @closed
|
|
29
|
+
context = (@contexts[thread] ||= [])
|
|
30
|
+
# @type var frame: trace_context
|
|
31
|
+
frame = { operation_id: operation_id(node), index: index }.freeze
|
|
32
|
+
context << frame
|
|
33
|
+
context
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
return yield unless stack
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
yield
|
|
40
|
+
ensure
|
|
41
|
+
@mutex.synchronize do
|
|
42
|
+
stack.pop
|
|
43
|
+
@contexts.delete(thread) if stack.empty?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reference(node, &)
|
|
49
|
+
index = @mutex.synchronize do
|
|
50
|
+
unless @closed
|
|
51
|
+
id = operation_id(node)
|
|
52
|
+
count = @references.fetch(id, 0)
|
|
53
|
+
@references[id] = count + 1
|
|
54
|
+
count
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
return yield if index.nil?
|
|
58
|
+
|
|
59
|
+
within(node, index, &)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def emit(type, node, details = {})
|
|
63
|
+
@mutex.synchronize do
|
|
64
|
+
return if @closed
|
|
65
|
+
|
|
66
|
+
if @events.length >= @limit
|
|
67
|
+
@truncated = true
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
# @type var event: trace_event
|
|
71
|
+
event = {
|
|
72
|
+
type: type.dup.freeze, operation_id: operation_id(node),
|
|
73
|
+
context: @contexts.fetch(Thread.current, []).dup.freeze,
|
|
74
|
+
details: details.transform_values { |value| value.dup.freeze }.freeze
|
|
75
|
+
}.freeze
|
|
76
|
+
@events << event
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def finish(value)
|
|
81
|
+
@mutex.synchronize do
|
|
82
|
+
# @type var report: trace_report
|
|
83
|
+
report = { value: value, events: @events.freeze, truncated: @truncated }.freeze
|
|
84
|
+
clear
|
|
85
|
+
report
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def close
|
|
90
|
+
@mutex.synchronize { clear }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def index(node)
|
|
96
|
+
@ids[node] = @ids.length
|
|
97
|
+
Explanation.syntax_children(node).each { |child| index(child) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def clear
|
|
101
|
+
@closed = true
|
|
102
|
+
@ids.clear
|
|
103
|
+
@contexts.clear
|
|
104
|
+
@references.clear
|
|
105
|
+
@events = []
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
module Tracing
|
|
5
|
+
# @api private
|
|
6
|
+
class Evaluator < JMESPath::Evaluator
|
|
7
|
+
def initialize(config:, collector:, root:, planned:, native_functions:)
|
|
8
|
+
@collector = collector
|
|
9
|
+
@native_functions = planned ? native_functions : nil
|
|
10
|
+
@compiled_functions = {} # : Hash[AST::Node, bool]
|
|
11
|
+
.compare_by_identity
|
|
12
|
+
index_plan(root) if planned
|
|
13
|
+
@compiled_functions.freeze
|
|
14
|
+
super(functions: config.functions, disable_visit_errors: config.disable_visit_errors)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def evaluate(node, data)
|
|
18
|
+
return super unless node.kind == :field
|
|
19
|
+
|
|
20
|
+
result, reason = FieldLookup.call(data, node.value.to_s)
|
|
21
|
+
@collector.emit("field_null", node, name: node.value.to_s, reason: reason) if reason
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def index_plan(node)
|
|
28
|
+
return unless Plan::BUILDERS.key?(node.kind)
|
|
29
|
+
|
|
30
|
+
@compiled_functions[node] = true if node.kind == :function
|
|
31
|
+
node.children.each { |child| index_plan(child) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def function(node, data)
|
|
35
|
+
return super unless @compiled_functions.key?(node) && !@disable_visit_errors
|
|
36
|
+
|
|
37
|
+
arguments = node.children.map { |child| evaluate(child, data) }
|
|
38
|
+
return native_string(node, arguments.fetch(0)) if @native_functions&.include?(node)
|
|
39
|
+
|
|
40
|
+
@functions.call_with_context(node.value.to_s, arguments, source_range: node.start...node.finish)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def native_string(node, value)
|
|
44
|
+
if value.is_a?(String)
|
|
45
|
+
node.value == "length" ? value.length : value.reverse
|
|
46
|
+
else
|
|
47
|
+
Functions.default.call_with_context(node.value.to_s, [value], source_range: node.start...node.finish)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reference(node, _data)
|
|
52
|
+
ExpressionReference.new do |value|
|
|
53
|
+
@collector.reference(node) { evaluate(node.children.fetch(0), value) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def project(node, values)
|
|
58
|
+
return nil unless values
|
|
59
|
+
|
|
60
|
+
index = -1
|
|
61
|
+
child = node.children.fetch(1)
|
|
62
|
+
result = values.map do |value|
|
|
63
|
+
index += 1
|
|
64
|
+
project_item(node, child, value, index)
|
|
65
|
+
end
|
|
66
|
+
result.compact!
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def project_item(node, child, value, index)
|
|
71
|
+
@collector.within(node, index) do
|
|
72
|
+
result = evaluate(child, value)
|
|
73
|
+
@collector.emit("projection_omission", node) if nil.equal?(result)
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def filter_projection(node, data)
|
|
79
|
+
values = DataAccess.array(evaluate(node.children.fetch(0), data))
|
|
80
|
+
indexes = [] # : Array[Integer]
|
|
81
|
+
index = -1
|
|
82
|
+
selected = values&.reject do |value|
|
|
83
|
+
index += 1
|
|
84
|
+
rejected = filter_rejected?(node, value, index)
|
|
85
|
+
indexes << index unless rejected
|
|
86
|
+
rejected
|
|
87
|
+
end
|
|
88
|
+
return nil unless selected
|
|
89
|
+
|
|
90
|
+
project_selected(node, selected, indexes)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def filter_rejected?(node, value, index)
|
|
94
|
+
@collector.within(node, index) do
|
|
95
|
+
rejected = Types.falsey?(evaluate(node.children.fetch(2), value))
|
|
96
|
+
@collector.emit("filter_exclusion", node) if rejected
|
|
97
|
+
rejected
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def project_selected(node, selected, indexes)
|
|
102
|
+
position = -1
|
|
103
|
+
child = node.children.fetch(1)
|
|
104
|
+
selected.map! do |value|
|
|
105
|
+
position += 1
|
|
106
|
+
project_item(node, child, value, indexes.fetch(position))
|
|
107
|
+
end
|
|
108
|
+
selected.compact!
|
|
109
|
+
selected
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JMESPath
|
|
4
|
+
module Tracing
|
|
5
|
+
# @api private
|
|
6
|
+
module FieldLookup
|
|
7
|
+
def self.call(value, name)
|
|
8
|
+
mapping = value.is_a?(Hash) ? value : DataAccess.object(value)
|
|
9
|
+
return [nil, "non_object"] unless mapping
|
|
10
|
+
|
|
11
|
+
initial = mapping[name]
|
|
12
|
+
return [initial, nil] unless initial.nil?
|
|
13
|
+
|
|
14
|
+
result, reason = fallback(mapping, name)
|
|
15
|
+
return [result, nil] unless nil.equal?(result)
|
|
16
|
+
|
|
17
|
+
[nil, classify(mapping, initial, reason)]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.fallback(mapping, name)
|
|
21
|
+
present = false
|
|
22
|
+
mapping.each_key do |key|
|
|
23
|
+
return [mapping[key], "null"] if key.is_a?(Symbol) && key.name == name
|
|
24
|
+
|
|
25
|
+
# Core checks avoid invoking custom key predicates or equality methods.
|
|
26
|
+
# rubocop:disable-next Style/CaseEquality
|
|
27
|
+
present = true if String === key && String.instance_method(:==).bind_call(key, name)
|
|
28
|
+
end
|
|
29
|
+
[nil, present ? "null" : "missing"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.classify(mapping, initial, reason)
|
|
33
|
+
nil.equal?(initial) && native_lookup?(mapping) ? reason : "unknown"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.native_lookup?(mapping)
|
|
37
|
+
# A Struct can return a duck mapping; avoid invoking its own type predicate.
|
|
38
|
+
# rubocop:disable-next Style/CaseEquality
|
|
39
|
+
return false unless Hash === mapping
|
|
40
|
+
|
|
41
|
+
return false if Hash.instance_method(:compare_by_identity?).bind_call(mapping)
|
|
42
|
+
|
|
43
|
+
%i[[] each_key].all? do |name|
|
|
44
|
+
Object.instance_method(:method).bind_call(mapping, name).owner == Hash
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/jmespath/version.rb
CHANGED
data/lib/jmespath.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "jmespath/version"
|
|
4
4
|
require_relative "jmespath/errors"
|
|
5
|
+
require_relative "jmespath/failure_capture"
|
|
6
|
+
require_relative "jmespath/syntax_diagnostic"
|
|
5
7
|
require_relative "jmespath/diagnostic"
|
|
6
8
|
require_relative "jmespath/token"
|
|
7
9
|
require_relative "jmespath/lexer"
|
|
@@ -20,7 +22,12 @@ require_relative "jmespath/options"
|
|
|
20
22
|
require_relative "jmespath/input"
|
|
21
23
|
require_relative "jmespath/cache"
|
|
22
24
|
require_relative "jmespath/plan"
|
|
25
|
+
require_relative "jmespath/explanation"
|
|
26
|
+
require_relative "jmespath/search_result"
|
|
23
27
|
require_relative "jmespath/expression"
|
|
28
|
+
require_relative "jmespath/tracing/collector"
|
|
29
|
+
require_relative "jmespath/tracing/field_lookup"
|
|
30
|
+
require_relative "jmespath/tracing/evaluator"
|
|
24
31
|
|
|
25
32
|
# Search JSON-compatible Ruby values with JMESPath expressions.
|
|
26
33
|
module JMESPath
|
|
@@ -44,15 +51,17 @@ module JMESPath
|
|
|
44
51
|
# JMESPath.search("items[*].name", {items: [{name: "Ada"}]}) #=> ["Ada"]
|
|
45
52
|
# @return [Object] the query result, including nil
|
|
46
53
|
def search(expression, data, options = Options::EMPTY, **keywords)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
FailureCapture.application do
|
|
55
|
+
config = if options.equal?(Options::EMPTY) && keywords.empty?
|
|
56
|
+
Options::DEFAULT
|
|
57
|
+
else
|
|
58
|
+
Options.new(options, keywords)
|
|
59
|
+
end
|
|
60
|
+
raise ArgumentError, "Expression must be a String" unless expression.is_a?(String)
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
compiled = compile_syntax(expression, config.cache_expressions, config.max_depth)
|
|
63
|
+
compiled.evaluate(Input.load(data), config)
|
|
64
|
+
end
|
|
56
65
|
end
|
|
57
66
|
|
|
58
67
|
# Parse an expression for repeated searches.
|
|
@@ -75,6 +84,32 @@ module JMESPath
|
|
|
75
84
|
config.equal?(Options::DEFAULT) ? compiled : compiled.configured(config)
|
|
76
85
|
end
|
|
77
86
|
|
|
87
|
+
# Validate syntax without evaluating data or checking function availability.
|
|
88
|
+
# @param expression [String] query text
|
|
89
|
+
# @param max_depth [Integer] maximum nesting from 1 through 128
|
|
90
|
+
# @raise [ArgumentError] expression type or depth is invalid
|
|
91
|
+
# @example Validate syntax without an input document
|
|
92
|
+
# JMESPath.validate("items[*].name") #=> []
|
|
93
|
+
# failure = JMESPath.validate("items[?active").fetch(0)
|
|
94
|
+
# failure.reason #=> "Expected rbracket, received eof"
|
|
95
|
+
# failure.position #=> 13
|
|
96
|
+
# failure.line #=> 1
|
|
97
|
+
# failure.column #=> 14
|
|
98
|
+
# @return [Array<SyntaxDiagnostic>] frozen array containing zero or one immutable diagnostic
|
|
99
|
+
def validate(expression, max_depth: 128)
|
|
100
|
+
raise ArgumentError, "Expression must be a String" unless expression.is_a?(String)
|
|
101
|
+
unless max_depth.is_a?(Integer) && (1..128).cover?(max_depth)
|
|
102
|
+
raise ArgumentError, "max_depth must be an integer from 1 through 128"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Parser.new(expression, max_depth: max_depth).parse
|
|
106
|
+
# @type var diagnostics: Array[SyntaxDiagnostic]
|
|
107
|
+
diagnostics = []
|
|
108
|
+
diagnostics.freeze
|
|
109
|
+
rescue Errors::SyntaxError => e
|
|
110
|
+
[SyntaxDiagnostic.new(e)].freeze
|
|
111
|
+
end
|
|
112
|
+
|
|
78
113
|
private
|
|
79
114
|
|
|
80
115
|
def compile_syntax(expression, cache, depth)
|
data/sig/jmespath.rbs
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
module JMESPath
|
|
2
|
+
type trace_context = { operation_id: Integer, index: Integer }
|
|
3
|
+
type trace_event = { type: String, operation_id: Integer, context: Array[trace_context], details: Hash[Symbol, json] }
|
|
4
|
+
type trace_report = { value: value, events: Array[trace_event], truncated: bool }
|
|
2
5
|
VERSION: String
|
|
3
6
|
type value = Object?
|
|
4
7
|
type json = nil | bool | Integer | Float | String | Array[json] | Hash[String, json]
|
|
8
|
+
type explanation_details = Hash[Symbol, json]
|
|
9
|
+
type explanation_child = { role: String, node: explanation_node }
|
|
10
|
+
type explanation_node = {
|
|
11
|
+
id: Integer,
|
|
12
|
+
operation: String,
|
|
13
|
+
source_range: [Integer, Integer]?,
|
|
14
|
+
details: explanation_details,
|
|
15
|
+
children: Array[explanation_child]
|
|
16
|
+
}
|
|
17
|
+
type explanation = { source: String, root: explanation_node }
|
|
5
18
|
|
|
6
19
|
def self.search: (String, value, ?Hash[Symbol, value], **value) -> value
|
|
7
20
|
def self.compile: (String, ?cache_expressions: bool, ?max_depth: Integer, ?functions: Functions, ?disable_visit_errors: bool) -> Expression
|
|
21
|
+
def self.validate: (String, ?max_depth: Integer) -> Array[SyntaxDiagnostic]
|
|
22
|
+
|
|
23
|
+
class SyntaxDiagnostic
|
|
24
|
+
attr_reader reason: String
|
|
25
|
+
attr_reader expression: String
|
|
26
|
+
attr_reader position: Integer?
|
|
27
|
+
attr_reader line: Integer?
|
|
28
|
+
attr_reader column: Integer?
|
|
29
|
+
def initialize: (Errors::SyntaxError) -> void
|
|
30
|
+
def location_known?: () -> bool
|
|
31
|
+
end
|
|
8
32
|
|
|
9
33
|
module Errors
|
|
10
34
|
class Error < StandardError
|
|
11
35
|
end
|
|
12
36
|
class RuntimeError < Error
|
|
13
37
|
end
|
|
38
|
+
class NullResultError < RuntimeError
|
|
39
|
+
attr_reader expression: String
|
|
40
|
+
def initialize: (expression: String) -> void
|
|
41
|
+
end
|
|
14
42
|
class SyntaxError < Error
|
|
15
43
|
attr_reader reason: String
|
|
16
44
|
attr_reader expression: String
|
|
@@ -53,13 +81,23 @@ module JMESPath
|
|
|
53
81
|
end
|
|
54
82
|
|
|
55
83
|
module Diagnostic
|
|
56
|
-
def self.format: (Errors::SyntaxError) -> String
|
|
84
|
+
def self.format: (Errors::SyntaxError | SyntaxDiagnostic) -> String
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class SearchResult
|
|
88
|
+
attr_reader value: value
|
|
89
|
+
attr_reader error: StandardError?
|
|
90
|
+
def success?: () -> bool
|
|
57
91
|
end
|
|
58
92
|
|
|
59
93
|
class Expression
|
|
60
94
|
attr_reader source: String
|
|
61
95
|
def search: (value, ?Hash[Symbol, value], **value) -> value
|
|
96
|
+
def try_search: (value, ?Hash[Symbol, value], **value) -> SearchResult
|
|
97
|
+
def fetch: (value, ?Hash[Symbol, value], **value) -> Object
|
|
98
|
+
def trace: (value, ?Hash[Symbol, value], ?limit: Integer, **value) -> trace_report
|
|
62
99
|
alias call search
|
|
100
|
+
def explain: () -> explanation
|
|
63
101
|
end
|
|
64
102
|
|
|
65
103
|
class Functions
|
|
@@ -82,11 +120,17 @@ module JMESPath
|
|
|
82
120
|
interface _Reader
|
|
83
121
|
def read: () -> String?
|
|
84
122
|
end
|
|
85
|
-
#
|
|
123
|
+
# Input stream protocol for CLI.run in JSON Lines and interactive modes.
|
|
124
|
+
# Interactive readers may expose tty?; only terminal input receives prompts.
|
|
125
|
+
interface _LineReader
|
|
126
|
+
def gets: () -> String?
|
|
127
|
+
end
|
|
128
|
+
# Output stream protocol used by CLI.run. Interactive mode uses optional flush.
|
|
129
|
+
# Terminal prompts require print and flush on the diagnostic stream.
|
|
86
130
|
interface _Writer
|
|
87
131
|
def puts: (String) -> void
|
|
88
132
|
end
|
|
89
133
|
module CLI
|
|
90
|
-
def self.run: (Array[String], ?stdin: _Reader, ?stdout: _Writer, ?stderr: _Writer) -> Integer
|
|
134
|
+
def self.run: (Array[String], ?stdin: _Reader | _LineReader, ?stdout: _Writer, ?stderr: _Writer) -> Integer
|
|
91
135
|
end
|
|
92
136
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jmespath-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
@@ -64,9 +64,12 @@ files:
|
|
|
64
64
|
- README.md
|
|
65
65
|
- SECURITY.md
|
|
66
66
|
- VERSIONING.md
|
|
67
|
+
- completions/_jmespath
|
|
68
|
+
- completions/jmespath.bash
|
|
67
69
|
- docs/guides/comparison.md
|
|
68
70
|
- docs/guides/compatibility.md
|
|
69
71
|
- docs/guides/compilation.md
|
|
72
|
+
- docs/guides/completion.md
|
|
70
73
|
- docs/guides/errors.md
|
|
71
74
|
- docs/guides/functions.md
|
|
72
75
|
- docs/guides/performance.md
|
|
@@ -77,7 +80,12 @@ files:
|
|
|
77
80
|
- lib/jmespath/ast.rb
|
|
78
81
|
- lib/jmespath/cache.rb
|
|
79
82
|
- lib/jmespath/cli.rb
|
|
83
|
+
- lib/jmespath/cli/argument_parser.rb
|
|
84
|
+
- lib/jmespath/cli/arguments.rb
|
|
80
85
|
- lib/jmespath/cli/failure_diagnostic.rb
|
|
86
|
+
- lib/jmespath/cli/function_listing.rb
|
|
87
|
+
- lib/jmespath/cli/help.rb
|
|
88
|
+
- lib/jmespath/cli/interactive_session.rb
|
|
81
89
|
- lib/jmespath/data_access.rb
|
|
82
90
|
- lib/jmespath/diagnostic.rb
|
|
83
91
|
- lib/jmespath/errors.rb
|
|
@@ -87,13 +95,16 @@ files:
|
|
|
87
95
|
- lib/jmespath/evaluation/scalars.rb
|
|
88
96
|
- lib/jmespath/evaluation/slices.rb
|
|
89
97
|
- lib/jmespath/evaluator.rb
|
|
98
|
+
- lib/jmespath/explanation.rb
|
|
90
99
|
- lib/jmespath/expression.rb
|
|
91
100
|
- lib/jmespath/expression_reference.rb
|
|
101
|
+
- lib/jmespath/failure_capture.rb
|
|
92
102
|
- lib/jmespath/functions.rb
|
|
93
103
|
- lib/jmespath/functions/coercions.rb
|
|
94
104
|
- lib/jmespath/functions/collections.rb
|
|
95
105
|
- lib/jmespath/functions/conversion.rb
|
|
96
106
|
- lib/jmespath/functions/function_call.rb
|
|
107
|
+
- lib/jmespath/functions/json_value.rb
|
|
97
108
|
- lib/jmespath/functions/references.rb
|
|
98
109
|
- lib/jmespath/functions/scalar.rb
|
|
99
110
|
- lib/jmespath/functions/signature.rb
|
|
@@ -107,7 +118,12 @@ files:
|
|
|
107
118
|
- lib/jmespath/parser/expressions.rb
|
|
108
119
|
- lib/jmespath/parser/projections.rb
|
|
109
120
|
- lib/jmespath/plan.rb
|
|
121
|
+
- lib/jmespath/search_result.rb
|
|
122
|
+
- lib/jmespath/syntax_diagnostic.rb
|
|
110
123
|
- lib/jmespath/token.rb
|
|
124
|
+
- lib/jmespath/tracing/collector.rb
|
|
125
|
+
- lib/jmespath/tracing/evaluator.rb
|
|
126
|
+
- lib/jmespath/tracing/field_lookup.rb
|
|
111
127
|
- lib/jmespath/types.rb
|
|
112
128
|
- lib/jmespath/version.rb
|
|
113
129
|
- sig/jmespath.rbs
|