synvert-core 1.1.0 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 603dafd1ddc6f40f8ef1479d359e39e1f4dbe90067bcfe51f721b15e9571c4d0
4
- data.tar.gz: e423e49c531e6ba91f490657f34564f781f3313d82dbf9a863ee2c17985500a8
3
+ metadata.gz: 3c530d34ca0e2db5d9a295cb0cb48c375d13760f09290624805d742328a49103
4
+ data.tar.gz: 87cc4ef7ab9116bac2cbed78b9f71ab8d4ffa30281554d10319527a9aab94573
5
5
  SHA512:
6
- metadata.gz: e6177477cdf8c0cdd67d6dc533145017daebee61905b5e48b992261f6f458b8a949796aa86eb0cd8f161ffaa05b9325e1cd671b6d63fd645ece7f2101e67859c
7
- data.tar.gz: c1daa4cca1056d9266c01037217cd6570f0b7ee0228491e408d43c7bf8bf753a52b2dbfbf20d6aa568876d7e1d7ee11727db40adbc56e17fc603ae091804475a
6
+ metadata.gz: 1b864e1e350d3467fe12d2c5e26a5c5a0b685752e21802e3773edfbbb8fca8f7dec60fb545475a630a3f00c09359c66859b228cf7cb3d19aa6bf044df4848408
7
+ data.tar.gz: c8c5641b85080f03307fed835c05132488d140bb19b694c2b5b0958cb3d6377785ef1447c8b7fec59e7529f2ec610f6c78d72357b60f5fe4285d43d5071750fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.2.1 (2022-05-01)
4
+
5
+ * Selector always after a node type in NQL
6
+ * Define `pairs` method for `hash` ndoe
7
+
8
+ ## 1.2.0 (2022-04-29)
9
+
10
+ * Remove comma in NQL array value
11
+ * Parse pseduo class without selector in NQL
12
+ * Parse multiple goto scope in NQL
13
+ * Parse `nil?` in NQL
14
+
15
+ ## 1.1.1 (2022-04-27)
16
+
17
+ * Parse empty string properly in node query language
18
+ * Parse `[]` and `[]=` properly in node query language
19
+
3
20
  ## 1.1.0 (2022-04-26)
4
21
 
5
22
  * Dynamic define Node methods by `TYPE_CHILDREN` const
@@ -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
@@ -93,9 +93,21 @@ module Parser::AST
93
93
  end
94
94
 
95
95
  # Dyamically defined method
96
- # caller, key, left_value, message, name, parent_class, parent_const, receivr, rgith_value and value.
96
+ # caller, key, left_value, message, name, pairs, parent_class, parent_const, receivr, rgith_value and value.
97
97
  # based on const TYPE_CHILDREN.
98
- %i[caller key left_value message name parent_class parent_const receiver right_value value].each do |method_name|
98
+ %i[
99
+ caller
100
+ key
101
+ left_value
102
+ message
103
+ name
104
+ pairs
105
+ parent_class
106
+ parent_const
107
+ receiver
108
+ right_value
109
+ value
110
+ ].each do |method_name|
99
111
  define_method(method_name) do
100
112
  index = TYPE_CHILDREN[type]&.index(method_name)
101
113
  return children[index] if index
@@ -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
@@ -16,17 +16,6 @@ module Synvert::Core::NodeQuery::Compiler
16
16
  SIMPLE_VALID_OPERATORS
17
17
  end
18
18
 
19
- # Get the actual value of a node.
20
- # @param node [Parser::AST::Node] the node
21
- # @return [String] if node is a Parser::AST::Node, return the node source code, otherwise, return the string value.
22
- def actual_value(node)
23
- if node.is_a?(::Parser::AST::Node)
24
- node.to_source
25
- else
26
- node.to_s
27
- end
28
- end
29
-
30
19
  def to_s
31
20
  "\"#{@value}\""
32
21
  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] }
@@ -54,9 +60,14 @@ rules
54
60
  :KEY /in/i { @state = :VALUE; [:tIN, text] }
55
61
  :KEY /#{IDENTIFIER}/ { [:tKEY, text] }
56
62
  :VALUE /\s+/
63
+ :VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
64
+ :VALUE /\[\]/ { [:tIDENTIFIER_VALUE, text] }
65
+ :VALUE /:\[\]=/ { [:tSYMBOL, text[1..-1].to_sym] }
66
+ :VALUE /:\[\]/ { [:tSYMBOL, text[1..-1].to_sym] }
57
67
  :VALUE /#{OPEN_DYNAMIC_ATTRIBUTE}/ { @state = :DYNAMIC_ATTRIBUTE; [:tOPEN_DYNAMIC_ATTRIBUTE, text] }
58
68
  :VALUE /#{OPEN_ARRAY}/ { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
59
69
  :VALUE /#{CLOSE_ATTRIBUTE}/ { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
70
+ :VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
60
71
  :VALUE /#{NIL}/ { [:tNIL, nil] }
61
72
  :VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
62
73
  :VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
@@ -72,8 +83,8 @@ rules
72
83
  :DYNAMIC_ATTRIBUTE /#{CLOSE_DYNAMIC_ATTRIBUTE}/ { @state = :VALUE; [:tCLOSE_DYNAMIC_ATTRIBUTE, text] }
73
84
  :DYNAMIC_ATTRIBUTE /#{IDENTIFIER}/ { [:tDYNAMIC_ATTRIBUTE, text] }
74
85
  :ARRAY_VALUE /\s+/
75
- :ARRAY_VALUE /,/ { [:tCOMMA, text] }
76
86
  :ARRAY_VALUE /#{CLOSE_ARRAY}/ { @state = :VALUE; [:tCLOSE_ARRAY, text] }
87
+ :ARRAY_VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
77
88
  :ARRAY_VALUE /#{NIL}/ { [:tNIL, nil] }
78
89
  :ARRAY_VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
79
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
@@ -191,12 +207,22 @@ class Synvert::Core::NodeQuery::Lexer
191
207
  case
192
208
  when ss.skip(/\s+/) then
193
209
  # do nothing
210
+ when text = ss.scan(/\[\]=/) then
211
+ action { [:tIDENTIFIER_VALUE, text] }
212
+ when text = ss.scan(/\[\]/) then
213
+ action { [:tIDENTIFIER_VALUE, text] }
214
+ when text = ss.scan(/:\[\]=/) then
215
+ action { [:tSYMBOL, text[1..-1].to_sym] }
216
+ when text = ss.scan(/:\[\]/) then
217
+ action { [:tSYMBOL, text[1..-1].to_sym] }
194
218
  when text = ss.scan(/#{OPEN_DYNAMIC_ATTRIBUTE}/) then
195
219
  action { @state = :DYNAMIC_ATTRIBUTE; [:tOPEN_DYNAMIC_ATTRIBUTE, text] }
196
220
  when text = ss.scan(/#{OPEN_ARRAY}/) then
197
221
  action { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
198
222
  when text = ss.scan(/#{CLOSE_ATTRIBUTE}/) then
199
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] }
200
226
  when ss.skip(/#{NIL}/) then
201
227
  action { [:tNIL, nil] }
202
228
  when ss.skip(/#{TRUE}/) then
@@ -239,10 +265,10 @@ class Synvert::Core::NodeQuery::Lexer
239
265
  case
240
266
  when ss.skip(/\s+/) then
241
267
  # do nothing
242
- when text = ss.scan(/,/) then
243
- action { [:tCOMMA, text] }
244
268
  when text = ss.scan(/#{CLOSE_ARRAY}/) then
245
269
  action { @state = :VALUE; [:tCLOSE_ARRAY, text] }
270
+ when text = ss.scan(/#{NIL}\?/) then
271
+ action { [:tIDENTIFIER_VALUE, text] }
246
272
  when ss.skip(/#{NIL}/) then
247
273
  action { [:tNIL, nil] }
248
274
  when ss.skip(/#{TRUE}/) then