janeway-jsonpath 0.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +7 -0
  3. data/README.md +43 -0
  4. data/bin/janeway +130 -0
  5. data/lib/janeway/ast/array_slice_selector.rb +104 -0
  6. data/lib/janeway/ast/binary_operator.rb +101 -0
  7. data/lib/janeway/ast/boolean.rb +24 -0
  8. data/lib/janeway/ast/child_segment.rb +48 -0
  9. data/lib/janeway/ast/current_node.rb +71 -0
  10. data/lib/janeway/ast/descendant_segment.rb +44 -0
  11. data/lib/janeway/ast/error.rb +10 -0
  12. data/lib/janeway/ast/expression.rb +55 -0
  13. data/lib/janeway/ast/filter_selector.rb +61 -0
  14. data/lib/janeway/ast/function.rb +40 -0
  15. data/lib/janeway/ast/helpers.rb +27 -0
  16. data/lib/janeway/ast/identifier.rb +35 -0
  17. data/lib/janeway/ast/index_selector.rb +27 -0
  18. data/lib/janeway/ast/name_selector.rb +52 -0
  19. data/lib/janeway/ast/null.rb +23 -0
  20. data/lib/janeway/ast/number.rb +21 -0
  21. data/lib/janeway/ast/query.rb +41 -0
  22. data/lib/janeway/ast/root_node.rb +50 -0
  23. data/lib/janeway/ast/selector.rb +32 -0
  24. data/lib/janeway/ast/string_type.rb +21 -0
  25. data/lib/janeway/ast/unary_operator.rb +26 -0
  26. data/lib/janeway/ast/wildcard_selector.rb +46 -0
  27. data/lib/janeway/error.rb +23 -0
  28. data/lib/janeway/functions/count.rb +39 -0
  29. data/lib/janeway/functions/length.rb +33 -0
  30. data/lib/janeway/functions/match.rb +69 -0
  31. data/lib/janeway/functions/search.rb +63 -0
  32. data/lib/janeway/functions/value.rb +47 -0
  33. data/lib/janeway/functions.rb +62 -0
  34. data/lib/janeway/interpreter.rb +644 -0
  35. data/lib/janeway/lexer.rb +514 -0
  36. data/lib/janeway/location.rb +3 -0
  37. data/lib/janeway/parser.rb +608 -0
  38. data/lib/janeway/token.rb +39 -0
  39. data/lib/janeway/version.rb +5 -0
  40. data/lib/janeway.rb +51 -0
  41. metadata +92 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module Functions
5
+ # 2.4.7. search() Function Extension
6
+ # Parameters:
7
+ # 1. ValueType (string)
8
+ # 2. ValueType (string conforming to [RFC9485])
9
+ # Result:
10
+ # LogicalType
11
+ #
12
+ # The search() function extension provides a way to check whether a given
13
+ # string contains a substring that matches a given regular expression,
14
+ # which is in the form described in [RFC9485].
15
+ #
16
+ # $[?search(@.author, "[BR]ob")]
17
+ #
18
+ # Its arguments are instances of ValueType (possibly taken from a singular
19
+ # query, as for the first argument in the example above). If the first
20
+ # argument is not a string or the second argument is not a string
21
+ # conforming to [RFC9485], the result is LogicalFalse. Otherwise, the
22
+ # string that is the first argument is searched for a substring that
23
+ # matches the I-Regexp contained in the string that is the second argument;
24
+ # the result is LogicalTrue if at least one such substring exists and is
25
+ # LogicalFalse otherwise.
26
+ #
27
+ # tl;dr: How is this different from the match function?
28
+ # "match" must match the entire string, "search" matches a substring.
29
+ def parse_function_search
30
+ consume # function
31
+ raise "expect group_start token, found #{current}" unless current.type == :group_start
32
+
33
+ consume # (
34
+
35
+ # Read first parameter
36
+ parameters = []
37
+ parameters << parse_function_parameter
38
+
39
+ # Consume comma
40
+ if current.type == :union
41
+ consume # ,
42
+ else
43
+ raise "expect comma token, found #{current}"
44
+ end
45
+
46
+ # Read second parameter (the regexp)
47
+ # This could be a string, in which case it is available now.
48
+ # Otherwise it is an expression that takes the regexp from the input document,
49
+ # and the iregexp will not be available until interpretation.
50
+ parameters << parse_function_parameter
51
+ raise "expect group_end token, found #{current}" unless current.type == :group_end
52
+
53
+ AST::Function.new('search', parameters) do |str, str_iregexp|
54
+ if str.is_a?(String) && str_iregexp.is_a?(String)
55
+ regexp = translate_iregex_to_ruby_regex(str_iregexp, anchor: false)
56
+ regexp.match?(str)
57
+ else
58
+ false # result defined by RFC9535
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module Functions
5
+ # 2.4.8. value() Function Extension
6
+
7
+ # Parameters:
8
+ # 1. NodesType
9
+ # Result:
10
+ # ValueType
11
+ #
12
+ # The value() function extension provides a way to convert an instance of
13
+ # NodesType to a value and make that available for further processing in
14
+ # the filter expression:
15
+ #
16
+ # @example $[?value(@..color) == "red"]
17
+ #
18
+ # Its only argument is an instance of NodesType (possibly taken from a
19
+ # filter-query, as in the example above). The result is an instance of
20
+ # ValueType.
21
+ #
22
+ # If the argument contains a single node, the result is the value of the node.
23
+ #
24
+ # If the argument is the empty nodelist or contains multiple nodes, the result is Nothing.
25
+ #
26
+ # Note: A singular query may be used anywhere where a ValueType is
27
+ # expected, so there is no need to use the value() function extension with a singular query.
28
+ def parse_function_value
29
+ consume # function
30
+ raise "expect group_start token, found #{current}" unless current.type == :group_start
31
+
32
+ consume # (
33
+
34
+ # Read parameter
35
+ parameters = [parse_function_parameter]
36
+ raise "expect group_end token, found #{current}" unless current.type == :group_end
37
+
38
+ AST::Function.new('value', parameters) do |nodes|
39
+ if nodes.is_a?(Array) && nodes.size == 1
40
+ nodes.first
41
+ else
42
+ :nothing
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ast/function'
4
+
5
+ module Janeway
6
+ # Mixin to provide JSONPath function handlers for Parser
7
+ module Functions
8
+ # Convert IRegexp format to ruby regexp equivalent, following the instructions in rfc9485.
9
+ # @see https://www.rfc-editor.org/rfc/rfc9485.html#name-pcre-re2-and-ruby-regexps
10
+ # @param iregex [String]
11
+ # @param anchor [Boolean] add anchors to match the string start and end
12
+ # @return [Regexp]
13
+ def translate_iregex_to_ruby_regex(iregex, anchor: true)
14
+ # * For any unescaped dots (.) outside character classes (first
15
+ # alternative of charClass production), replace the dot with [^\n\r].
16
+ chars = iregex.chars
17
+ in_char_class = false
18
+ indexes = []
19
+ chars.each_with_index do |char, i|
20
+ # FIXME: does not handle escaped '[', ']', or '.'
21
+ case char
22
+ when '[' then in_char_class = true
23
+ when ']' then in_char_class = false
24
+ when '.'
25
+ next if in_char_class || chars[i-1] == '\\' # escaped dot
26
+
27
+ indexes << i # replace this dot
28
+ end
29
+ end
30
+ indexes.reverse_each do |i|
31
+ chars[i] = '[^\n\r]'
32
+ end
33
+
34
+ # * Enclose the regexp in \A(?: and )\z.
35
+ regex_str = anchor ? format('\A(?:%s)\z', chars.join) : chars.join
36
+ Regexp.new(regex_str)
37
+ end
38
+
39
+ # All jsonpath function parameters are one of these accepted types.
40
+ # Parse the function parameter and return the result.
41
+ # @return [String, AST::CurrentNode, AST::RootNode]
42
+ def parse_function_parameter
43
+ result =
44
+ case current.type
45
+ when :string then parse_string
46
+ when :current_node then parse_current_node
47
+ when :root then parse_root
48
+ else
49
+ # Invalid, no function uses this.
50
+ # Instead of crashing here, accept it and let the function return an empty result.
51
+ parse_expr
52
+ end
53
+ consume
54
+ result
55
+ end
56
+ end
57
+ end
58
+
59
+ # Require function definitions
60
+ Dir.children("#{__dir__}/functions/").each do |path|
61
+ require_relative "functions/#{path[0..-4]}"
62
+ end