synvert-core 1.1.0 → 1.2.1

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: 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