synvert-core 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3972bef6a4c4b41b4211e2f45d889c6e3c71b08b1b2b039ca3511a620f79ccf
4
- data.tar.gz: 7ddecb20188351e72e59b4a72c67a45d395638ae7a20d3c4d46f3fef0184de3c
3
+ metadata.gz: e5bf36b2f9e986dd2baf0bbddf53f4671a3bf98af0db830c270960eb583b47ce
4
+ data.tar.gz: 7e8693858d73d11c09652076ee03d49fe69662386caff67cbd0a3cc37e47d0a2
5
5
  SHA512:
6
- metadata.gz: b89480e5d9d05fe0651b5cd9edd1a4408a52364d449d0632baf062dd40251670b8c7d98370fa831f6961454498e9fbfffa67011a4fabb45f21e6031933643c36
7
- data.tar.gz: c23844951b2938268289099f852f01d554e46a7aa80ad301011d05cc40a022864dd872a8097c71575a42e8770730dbfea2deea8d867af0412022506751cf2671
6
+ metadata.gz: ad0eb6125d82241c60f7f0e229b9735f7c253280f1e56d586dfd24e8afb9c25b2cf574ca9c21e252c485f529b1bdf5f6b26c2b59805781cdefd3bc07a76072d8
7
+ data.tar.gz: 37bd754087a17d55577a10338fef9da3c60733d8634e9c394666a826fe205dc7f895121570f1e9cca4e7e1284f0b3b11c16e532e197afe2cd0ce61424c5d1557
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.2.0 (2022-04-29)
4
+
5
+ * Remove comma in NQL array value
6
+ * Parse pseduo class without selector in NQL
7
+ * Parse multiple goto scope in NQL
8
+ * Parse `nil?` in NQL
9
+
3
10
  ## 1.1.1 (2022-04-27)
4
11
 
5
12
  * Parse empty string properly in node query language
@@ -13,7 +13,7 @@ class Array
13
13
  return child_direct_child_node if child_direct_child_node
14
14
 
15
15
  raise Synvert::Core::MethodNotSupported,
16
- "child_node_by_name is not handled for #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
16
+ "child_node_by_name is not handled for #{debug_info}, child_name: #{child_name}"
17
17
  end
18
18
 
19
19
  # Get the source range of child node.
@@ -35,7 +35,14 @@ class Array
35
35
  )
36
36
  else
37
37
  raise Synvert::Core::MethodNotSupported,
38
- "child_node_range is not handled for #{map(&:debug_info).join("\n")}, child_name: #{child_name}"
38
+ "child_node_range is not handled for #{debug_info}, child_name: #{child_name}"
39
39
  end
40
40
  end
41
+
42
+ # Return the debug info.
43
+ #
44
+ # @return [String] file, line, source and node.
45
+ def debug_info
46
+ map(&:debug_info).join("\n")
47
+ end
41
48
  end
@@ -28,7 +28,7 @@ module Synvert::Core::NodeQuery::Compiler
28
28
  end
29
29
 
30
30
  def to_s
31
- [@value, @rest].compact.join(', ')
31
+ [@value, @rest].compact.join(' ')
32
32
  end
33
33
  end
34
34
  end
@@ -5,10 +5,12 @@ module Synvert::Core::NodeQuery::Compiler
5
5
  class Expression
6
6
  # Initialize a Expression.
7
7
  # @param selector [Synvert::Core::NodeQuery::Compiler::Selector] the selector
8
+ # @param goto_scope [String] goto scope
8
9
  # @param rest [Synvert::Core::NodeQuery::Compiler::Expression] the rest expression
9
10
  # @param relationship [Symbol] the relationship between the selector and rest expression, it can be <code>:descendant</code>, <code>:child</code>, <code>:next_sibling</code>, <code>:subsequent_sibling</code> or <code>nil</code>.
10
- def initialize(selector: nil, rest: nil, relationship: nil)
11
+ def initialize(selector: nil, goto_scope: nil, rest: nil, relationship: nil)
11
12
  @selector = selector
13
+ @goto_scope = goto_scope
12
14
  @rest = rest
13
15
  @relationship = relationship
14
16
  end
@@ -30,49 +32,85 @@ module Synvert::Core::NodeQuery::Compiler
30
32
  # @param node [Parser::AST::Node] node to match
31
33
  # @param descendant_match [Boolean] whether to match in descendant node
32
34
  # @return [Array<Parser::AST::Node>] matching nodes.
33
- def query_nodes(node, descendant_match: true)
34
- matching_nodes = find_nodes_without_relationship(node, descendant_match: descendant_match)
35
- if @relationship.nil?
36
- return matching_nodes
37
- end
35
+ def query_nodes(node, descendant_match = true)
36
+ return find_nodes_by_goto_scope(node) if @goto_scope
38
37
 
39
- expression_nodes = matching_nodes.map do |matching_node|
40
- case @relationship
41
- when :descendant
42
- nodes = []
43
- matching_node.recursive_children { |child_node|
44
- nodes += @rest.query_nodes(child_node, descendant_match: false)
45
- }
46
- nodes
47
- when :child
48
- matching_node.children.map { |child_node| @rest.query_nodes(child_node, descendant_match: false) }
49
- .flatten
50
- when :next_sibling
51
- @rest.query_nodes(matching_node.siblings.first, descendant_match: false)
52
- when :subsequent_sibling
53
- matching_node.siblings.map { |sibling_node| @rest.query_nodes(sibling_node, descendant_match: false) }
54
- .flatten
55
- end
56
- end.flatten
38
+ return find_nodes_by_relationship(node) if @relationship
39
+
40
+ matching_nodes = find_nodes_without_relationship(node, descendant_match)
41
+ return matching_nodes if @rest.nil?
42
+
43
+ matching_nodes.map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
44
+ .flatten
57
45
  end
58
46
 
59
47
  def to_s
60
48
  return @selector.to_s unless @rest
61
49
 
62
50
  result = []
63
- result << @selector if @selector
51
+ result << @selector.to_s if @selector
52
+ result << "<#{@goto_scope}>" if @goto_scope
64
53
  case @relationship
65
- when :child then result << '>'
66
- when :subsequent_sibling then result << '~'
67
- when :next_sibling then result << '+'
54
+ when :child then result << "> #{@rest}"
55
+ when :subsequent_sibling then result << "~ #{@rest}"
56
+ when :next_sibling then result << "+ #{@rest}"
57
+ when :has then result << ":has(#{@rest})"
58
+ when :not_has then result << ":not_has(#{@rest})"
59
+ else result << @rest.to_s
68
60
  end
69
- result << @rest
70
- result.map(&:to_s).join(' ')
61
+ result.join(' ')
71
62
  end
72
63
 
73
64
  private
74
65
 
75
- def find_nodes_without_relationship(node, descendant_match: true)
66
+ # Find nodes by @goto_scope
67
+ # @param node [Parser::AST::Node] node to match
68
+ def find_nodes_by_goto_scope(node)
69
+ @goto_scope.split('.').each { |scope| node = node.send(scope) }
70
+ @rest.query_nodes(node, false)
71
+ end
72
+
73
+ # Find ndoes by @relationship
74
+ # @param node [Parser::AST::Node] node to match
75
+ def find_nodes_by_relationship(node)
76
+ case @relationship
77
+ when :child
78
+ if node.is_a?(::Array)
79
+ return node.map { |each_node| find_nodes_by_rest(each_node) }
80
+ .flatten
81
+ else
82
+ return node.children.map { |each_node| find_nodes_by_rest(each_node) }
83
+ .flatten
84
+ end
85
+ when :next_sibling
86
+ return find_nodes_by_rest(node.siblings.first)
87
+ when :subsequent_sibling
88
+ return node.siblings.map { |each_node| find_nodes_by_rest(each_node) }
89
+ .flatten
90
+ when :has
91
+ return @rest.match?(node) ? [node] : []
92
+ when :not_has
93
+ return !@rest.match?(node) ? [node] : []
94
+ end
95
+ end
96
+
97
+ # Find nodes by @rest
98
+ # @param node [Parser::AST::Node] node to match
99
+ # @param descendant_match [Boolean] whether to match in descendant node
100
+ def find_nodes_by_rest(node, descendant_match = false)
101
+ @rest.query_nodes(node, descendant_match)
102
+ end
103
+
104
+ # Find nodes with nil relationship.
105
+ # @param node [Parser::AST::Node] node to match
106
+ # @param descendant_match [Boolean] whether to match in descendant node
107
+ def find_nodes_without_relationship(node, descendant_match = true)
108
+ if node.is_a?(::Array)
109
+ return node.map { |each_node|
110
+ find_nodes_without_relationship(each_node, descendant_match)
111
+ }.flatten
112
+ end
113
+
76
114
  return [node] unless @selector
77
115
 
78
116
  nodes = []
@@ -33,21 +33,24 @@ module Synvert::Core::NodeQuery::Compiler
33
33
  end
34
34
 
35
35
  def to_s
36
- str = ".#{@node_type}#{@attribute_list}"
37
- return str if !@index && !@pseudo_class
38
-
39
- return "#{str}:#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
40
-
41
- case @index
42
- when 0
43
- str + ':first-child'
44
- when -1
45
- str + ':last-child'
46
- when (1..)
47
- str + ":nth-child(#{@index + 1})"
48
- else # ...-1
49
- str + ":nth-last-child(#{-@index})"
36
+ result = []
37
+ result << ".#{@node_type}" if @node_type
38
+ result << @attribute_list.to_s if @attribute_list
39
+ result << ":#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
40
+ if @index
41
+ result <<
42
+ case @index
43
+ when 0
44
+ ':first-child'
45
+ when -1
46
+ ':last-child'
47
+ when (1..)
48
+ ":nth-child(#{@index + 1})"
49
+ else # ...-1
50
+ ":nth-last-child(#{-@index})"
51
+ end
50
52
  end
53
+ result.join('')
51
54
  end
52
55
  end
53
56
  end
@@ -7,6 +7,8 @@ macros
7
7
  CLOSE_ARRAY /\)/
8
8
  OPEN_SELECTOR /\(/
9
9
  CLOSE_SELECTOR /\)/
10
+ OPEN_GOTO_SCOPE /</
11
+ CLOSE_GOTO_SCOPE />/
10
12
  OPEN_DYNAMIC_ATTRIBUTE /{{/
11
13
  CLOSE_DYNAMIC_ATTRIBUTE /}}/
12
14
  NODE_TYPE /\.[a-z]+/
@@ -39,7 +41,11 @@ rules
39
41
  /\+/ { [:tNEXT_SIBLING, text] }
40
42
  /#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
41
43
  /#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
44
+ /#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
42
45
  /#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
46
+ :GOTO_SCOPE /\s+/
47
+ :GOTO_SCOPE /#{IDENTIFIER}/ { [:tIDENTIFIER, text] }
48
+ :GOTO_SCOPE /#{CLOSE_GOTO_SCOPE}/ { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
43
49
  :KEY /\s+/
44
50
  :KEY /!=/ { @state = :VALUE; [:tNOT_EQUAL, text] }
45
51
  :KEY /=~/ { @state = :VALUE; [:tMATCH, text] }
@@ -61,6 +67,7 @@ rules
61
67
  :VALUE /#{OPEN_DYNAMIC_ATTRIBUTE}/ { @state = :DYNAMIC_ATTRIBUTE; [:tOPEN_DYNAMIC_ATTRIBUTE, text] }
62
68
  :VALUE /#{OPEN_ARRAY}/ { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
63
69
  :VALUE /#{CLOSE_ATTRIBUTE}/ { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
70
+ :VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
64
71
  :VALUE /#{NIL}/ { [:tNIL, nil] }
65
72
  :VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
66
73
  :VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
@@ -76,8 +83,8 @@ rules
76
83
  :DYNAMIC_ATTRIBUTE /#{CLOSE_DYNAMIC_ATTRIBUTE}/ { @state = :VALUE; [:tCLOSE_DYNAMIC_ATTRIBUTE, text] }
77
84
  :DYNAMIC_ATTRIBUTE /#{IDENTIFIER}/ { [:tDYNAMIC_ATTRIBUTE, text] }
78
85
  :ARRAY_VALUE /\s+/
79
- :ARRAY_VALUE /,/ { [:tCOMMA, text] }
80
86
  :ARRAY_VALUE /#{CLOSE_ARRAY}/ { @state = :VALUE; [:tCLOSE_ARRAY, text] }
87
+ :ARRAY_VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
81
88
  :ARRAY_VALUE /#{NIL}/ { [:tNIL, nil] }
82
89
  :ARRAY_VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
83
90
  :ARRAY_VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
@@ -20,6 +20,8 @@ class Synvert::Core::NodeQuery::Lexer
20
20
  CLOSE_ARRAY = /\)/
21
21
  OPEN_SELECTOR = /\(/
22
22
  CLOSE_SELECTOR = /\)/
23
+ OPEN_GOTO_SCOPE = /</
24
+ CLOSE_GOTO_SCOPE = />/
23
25
  OPEN_DYNAMIC_ATTRIBUTE = /{{/
24
26
  CLOSE_DYNAMIC_ATTRIBUTE = /}}/
25
27
  NODE_TYPE = /\.[a-z]+/
@@ -149,12 +151,26 @@ class Synvert::Core::NodeQuery::Lexer
149
151
  action { [:tOPEN_SELECTOR, text] }
150
152
  when text = ss.scan(/#{CLOSE_SELECTOR}/) then
151
153
  action { [:tCLOSE_SELECTOR, text] }
154
+ when text = ss.scan(/#{OPEN_GOTO_SCOPE}/) then
155
+ action { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
152
156
  when text = ss.scan(/#{OPEN_ATTRIBUTE}/) then
153
157
  action { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
154
158
  else
155
159
  text = ss.string[ss.pos .. -1]
156
160
  raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
157
161
  end
162
+ when :GOTO_SCOPE then
163
+ case
164
+ when ss.skip(/\s+/) then
165
+ # do nothing
166
+ when text = ss.scan(/#{IDENTIFIER}/) then
167
+ action { [:tIDENTIFIER, text] }
168
+ when text = ss.scan(/#{CLOSE_GOTO_SCOPE}/) then
169
+ action { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
170
+ else
171
+ text = ss.string[ss.pos .. -1]
172
+ raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
173
+ end
158
174
  when :KEY then
159
175
  case
160
176
  when ss.skip(/\s+/) then
@@ -205,6 +221,8 @@ class Synvert::Core::NodeQuery::Lexer
205
221
  action { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
206
222
  when text = ss.scan(/#{CLOSE_ATTRIBUTE}/) then
207
223
  action { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
224
+ when text = ss.scan(/#{NIL}\?/) then
225
+ action { [:tIDENTIFIER_VALUE, text] }
208
226
  when ss.skip(/#{NIL}/) then
209
227
  action { [:tNIL, nil] }
210
228
  when ss.skip(/#{TRUE}/) then
@@ -247,10 +265,10 @@ class Synvert::Core::NodeQuery::Lexer
247
265
  case
248
266
  when ss.skip(/\s+/) then
249
267
  # do nothing
250
- when text = ss.scan(/,/) then
251
- action { [:tCOMMA, text] }
252
268
  when text = ss.scan(/#{CLOSE_ARRAY}/) then
253
269
  action { @state = :VALUE; [:tCLOSE_ARRAY, text] }
270
+ when text = ss.scan(/#{NIL}\?/) then
271
+ action { [:tIDENTIFIER_VALUE, text] }
254
272
  when ss.skip(/#{NIL}/) then
255
273
  action { [:tNIL, nil] }
256
274
  when ss.skip(/#{TRUE}/) then