bade 0.2.0 → 0.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
  SHA1:
3
- metadata.gz: ab630e2e6b7eec2139fbc4b14699985eefed370a
4
- data.tar.gz: a132ae2fd1e1fd2d39dc8a9957f1e410dfcc821b
3
+ metadata.gz: 2b9d41889101a2207790a83bb4ae4900892e766b
4
+ data.tar.gz: 5d3019af48cbd0119a6b8c4d44f2d74b76e86c84
5
5
  SHA512:
6
- metadata.gz: 2624ab09740f0ee43309ffef7de6d5508e3880576ee0c461902b5762012c096ce7e829dcab577f1913fc29755956dc0cee256168e8d45bb579c3e81e9f6e0478
7
- data.tar.gz: f859d3d06ca8bc9d1303c6afe7fa96f5a8e5a8a96d6c2d0bd67542392c2a9e4c29bb5736254786f051a6408b72db94ea41fabe415f7cdd10637ac3b26e2dea1b
6
+ metadata.gz: cd6c63f0df98925562ebb9d9624c7e13542a5a6f1d76c8ef236b74cccfe1376065ec8e53e2c802f44177c9b67c88fa7218baef70f33dfac3e59505f0370dfbf2
7
+ data.tar.gz: 72225de15eeb899be67127bd9331b690529843e03be7c7fcf4402554551332a525b07bd9313c28e9ce578ac4eb31640a318ca87388d7a62163dd89c5391607a2
@@ -7,21 +7,22 @@ require 'bade/version'
7
7
 
8
8
 
9
9
  Gem::Specification.new do |spec|
10
- spec.name = 'bade'
11
- spec.version = Bade::VERSION
12
- spec.authors = ['Roman Kříž']
13
- spec.email = ['samnung@gmail.com']
14
- spec.summary = %q{Minimalistic template engine for Ruby.}
15
- spec.homepage = 'https://github.com/epuber-io/bade'
16
- spec.license = 'MIT'
17
- spec.required_ruby_version = '>= 2.0'
10
+ spec.name = 'bade'
11
+ spec.version = Bade::VERSION
12
+ spec.authors = ['Roman Kříž']
13
+ spec.email = ['samnung@gmail.com']
14
+ spec.summary = 'Minimalistic template engine for Ruby.'
15
+ spec.homepage = 'https://github.com/epuber-io/bade'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 2.0'
18
18
 
19
- spec.files = Dir['bin/**/*'] + Dir['lib/**/*'] + %w(Bade.gemspec Gemfile README.md)
20
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
- spec.require_paths = ['lib']
19
+ spec.files = Dir['bin/**/*'] + Dir['lib/**/*'] + %w(Bade.gemspec Gemfile README.md)
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ['lib']
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1'
25
- spec.add_development_dependency 'rspec', '~> 3.2'
26
- spec.add_development_dependency 'rake', '~> 10.4'
24
+ spec.add_development_dependency 'bundler', '~> 1'
25
+ spec.add_development_dependency 'rspec', '~> 3.2'
26
+ spec.add_development_dependency 'rake', '~> 11'
27
+ spec.add_development_dependency 'rubocop', '~> 0.35'
27
28
  end
@@ -44,7 +44,7 @@ module Bade
44
44
  # @return [Bool]
45
45
  #
46
46
  def ==(other)
47
- return false unless Document === other
47
+ return false unless other.is_a?(Document)
48
48
 
49
49
  root == other.root && sub_documents == other.sub_documents
50
50
  end
@@ -10,14 +10,14 @@ module Bade
10
10
  #
11
11
  def xml_output
12
12
  case value
13
- when 'xml'
14
- '<?xml version="1.0" encoding="utf-8" ?>'
13
+ when 'xml'
14
+ '<?xml version="1.0" encoding="utf-8" ?>'
15
15
 
16
- when 'html'
17
- '<!DOCTYPE html>'
16
+ when 'html'
17
+ '<!DOCTYPE html>'
18
18
 
19
- else
20
- raise Parser::ParserInternalError 'Unknown doctype type'
19
+ else
20
+ raise Parser::ParserInternalError, 'Unknown doctype type'
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bade
4
+ module AST
5
+ class StaticTextNode < Node
6
+ # @return [String]
7
+ #
8
+ attr_accessor :value
9
+
10
+ # @return [Bool]
11
+ #
12
+ attr_accessor :escaped
13
+
14
+ def initialize(*args)
15
+ super
16
+
17
+ @escaped = false
18
+ end
19
+
20
+ # @param [ValueNode] other
21
+ #
22
+ def ==(other)
23
+ super && value == other.value && escaped == other.escaped
24
+ end
25
+ end
26
+ end
27
+ end
@@ -9,6 +9,7 @@ module Bade
9
9
  require_relative 'node/value_node'
10
10
  require_relative 'node/mixin_node'
11
11
  require_relative 'node/doctype_node'
12
+ require_relative 'node/static_text_node'
12
13
 
13
14
  class << self
14
15
  # @return [Hash<Symbol, Class>]
@@ -40,15 +41,13 @@ module Bade
40
41
  def create(type, lineno)
41
42
  klass = registered_types[type]
42
43
 
43
- if klass.nil?
44
- raise ::KeyError, "Undefined node type #{type.inspect}"
45
- end
44
+ raise ::KeyError, "Undefined node type #{type.inspect}" if klass.nil?
46
45
 
47
46
  klass.new(type, lineno: lineno)
48
47
  end
49
48
  end
50
49
 
51
- register_type ValueNode, :text
50
+ register_type StaticTextNode, :static_text
52
51
  register_type ValueNode, :newline
53
52
  register_type ValueNode, :code
54
53
  register_type ValueNode, :output
@@ -21,7 +21,8 @@ module Bade
21
21
  when Document
22
22
  node_to_s(root.root, 0)
23
23
  else
24
- raise AttributeError, "Root attribute passed into initializer must be subclass of #{Node} or #{Document}, is #{root.class}!"
24
+ msg = "Root attribute passed into initializer must be subclass of #{Node} or #{Document}, is #{root.class}!"
25
+ raise AttributeError, msg
25
26
  end
26
27
  end
27
28
 
@@ -46,7 +47,7 @@ module Bade
46
47
  other = node.name
47
48
  when KeyValueNode
48
49
  other = "#{node.name}:#{node.value}"
49
- when ValueNode
50
+ when ValueNode, StaticTextNode
50
51
  escaped_sign = if node.escaped
51
52
  '& '
52
53
  elsif node.escaped.nil?
@@ -63,7 +64,7 @@ module Bade
63
64
  raise "Unknown node class #{node.class} of type #{node.type} for serializing"
64
65
  end
65
66
 
66
- other = ' ' + other if other && other.length > 0
67
+ other = ' ' + other if other && !other.empty?
67
68
 
68
69
  "#{indent}(#{type_s}#{other}#{children_s})"
69
70
  end
@@ -12,14 +12,14 @@ module Bade
12
12
  CURRENT_INDENT_NAME = :__indent
13
13
  BASE_INDENT_NAME = :__base_indent
14
14
 
15
- DEFAULT_BLOCK_NAME = 'default_block'
15
+ DEFAULT_BLOCK_NAME = 'default_block'.freeze
16
16
 
17
17
  # @param [Document] document
18
18
  #
19
19
  # @return [String]
20
20
  #
21
21
  def self.document_to_lambda_string(document)
22
- generator = self.new
22
+ generator = new
23
23
  generator.generate_lambda_string(document)
24
24
  end
25
25
 
@@ -37,7 +37,7 @@ module Bade
37
37
  buff_code ''
38
38
  buff_code "lambda do |#{NEW_LINE_NAME}: \"\\n\", #{BASE_INDENT_NAME}: ' '|"
39
39
 
40
- code_indent {
40
+ code_indent do
41
41
  buff_code "self.#{NEW_LINE_NAME} = #{NEW_LINE_NAME}"
42
42
  buff_code "self.#{BASE_INDENT_NAME} = #{BASE_INDENT_NAME}"
43
43
 
@@ -46,7 +46,7 @@ module Bade
46
46
  buff_code "output = #{BUFF_NAME}.join"
47
47
  buff_code 'self.__reset'
48
48
  buff_code 'output'
49
- }
49
+ end
50
50
 
51
51
  buff_code 'end'
52
52
 
@@ -58,13 +58,19 @@ module Bade
58
58
 
59
59
  # @param [String] text
60
60
  #
61
- def buff_print_text(text, indent: false, new_line: false)
62
- buff_print_value(%Q{%Q{#{text}}}) if text.length > 0
61
+ def buff_print_text(text, indent: false, new_line: false) # rubocop:disable Lint/UnusedMethodArgument
62
+ buff_print_value("%Q{#{text}}") unless text.empty?
63
+ end
64
+
65
+ # @param [String] text
66
+ #
67
+ def buff_print_static_text(text)
68
+ buff_print_value("'#{text.gsub("'", "\\'")}'") unless text.empty?
63
69
  end
64
70
 
65
71
  def buff_print_value(value)
66
72
  # buff_code %Q{#{BUFF_NAME} << #{CURRENT_INDENT_NAME}} if indent
67
- buff_code(%Q{#{BUFF_NAME} << #{value}})
73
+ buff_code("#{BUFF_NAME} << #{value}")
68
74
  end
69
75
 
70
76
  def buff_code(text)
@@ -93,80 +99,70 @@ module Bade
93
99
  # @param nodes [Array<Node>]
94
100
  #
95
101
  def visit_nodes(nodes)
96
- nodes.each { |node|
102
+ nodes.each do |node|
97
103
  visit_node(node)
98
- }
104
+ end
99
105
  end
100
106
 
101
107
  # @param current_node [Node]
102
108
  #
103
109
  def visit_node(current_node)
104
110
  case current_node.type
105
- when :root
106
- visit_node_children(current_node)
107
-
108
- when :text
109
- buff_print_text current_node.value
110
-
111
- when :tag
112
- visit_tag(current_node)
113
-
114
- when :code
115
- buff_code(current_node.value)
116
-
117
- when :html_comment
118
- buff_print_text '<!-- '
119
- visit_node_children(current_node)
120
- buff_print_text ' -->'
121
-
122
- when :comment
123
- comment_text = '#' + current_node.children.map(&:value).join("\n#")
124
- buff_code(comment_text)
125
-
126
- when :doctype
127
- buff_print_text current_node.xml_output
128
-
129
- when :mixin_decl
130
- params = formatted_mixin_params(current_node)
131
- buff_code "#{MIXINS_NAME}['#{current_node.name}'] = __create_mixin('#{current_node.name}', &lambda { |#{params}|"
132
-
133
- code_indent {
134
- blocks_name_declaration(current_node)
135
- visit_nodes(current_node.children - current_node.params)
136
- }
137
-
138
- buff_code '})'
139
-
140
- when :mixin_call
141
- params = formatted_mixin_params(current_node)
142
- buff_code "#{MIXINS_NAME}['#{current_node.name}'].call!(#{params})"
143
-
144
- when :output
145
- data = current_node.value
146
- output_code = if current_node.escaped
147
- "\#{__html_escaped(#{data})}"
148
- else
149
- "\#{#{data}}"
150
- end
151
- buff_print_text output_code
152
-
153
- when :newline
154
- # buff_print_value(NEW_LINE_NAME)
155
-
156
- when :import
157
- base_path = File.expand_path(current_node.value, File.dirname(@document.file_path))
158
- load_path = if base_path.end_with?('.rb') && File.exist?(base_path)
159
- base_path
160
- elsif File.exist?("#{base_path}.rb")
161
- "#{base_path}.rb"
111
+ when :root
112
+ visit_node_children(current_node)
113
+
114
+ when :static_text
115
+ buff_print_static_text(current_node.value)
116
+
117
+ when :tag
118
+ visit_tag(current_node)
119
+
120
+ when :code
121
+ buff_code(current_node.value)
122
+
123
+ when :html_comment
124
+ buff_print_text '<!-- '
125
+ visit_node_children(current_node)
126
+ buff_print_text ' -->'
127
+
128
+ when :comment
129
+ comment_text = '#' + current_node.children.map(&:value).join("\n#")
130
+ buff_code(comment_text)
131
+
132
+ when :doctype
133
+ buff_print_text current_node.xml_output
134
+
135
+ when :mixin_decl
136
+ visit_block_decl(current_node)
137
+
138
+ when :mixin_call
139
+ params = formatted_mixin_params(current_node)
140
+ buff_code "#{MIXINS_NAME}['#{current_node.name}'].call!(#{params})"
141
+
142
+ when :output
143
+ data = current_node.value
144
+ output_code = if current_node.escaped
145
+ "\#{__html_escaped(#{data})}"
162
146
  else
163
- nil # other cases are handled in Renderer
147
+ "\#{#{data}}"
164
148
  end
149
+ buff_print_text output_code
165
150
 
166
- buff_code "load('#{load_path}')" unless load_path.nil?
151
+ when :newline
152
+ # buff_print_value(NEW_LINE_NAME)
167
153
 
168
- else
169
- raise "Unknown type #{current_node.type}"
154
+ when :import
155
+ base_path = File.expand_path(current_node.value, File.dirname(@document.file_path))
156
+ load_path = if base_path.end_with?('.rb') && File.exist?(base_path)
157
+ base_path
158
+ elsif File.exist?("#{base_path}.rb")
159
+ "#{base_path}.rb"
160
+ end
161
+
162
+ buff_code "load('#{load_path}')" unless load_path.nil?
163
+
164
+ else
165
+ raise "Unknown type #{current_node.type}"
170
166
  end
171
167
  end
172
168
 
@@ -180,19 +176,15 @@ module Bade
180
176
 
181
177
  text = "<#{current_node.name}"
182
178
 
183
- if attributes.length > 0
184
- text += "#{attributes}"
185
- end
179
+ text += attributes.to_s unless attributes.empty?
186
180
 
187
- other_than_new_lines = children_wo_attributes.any? { |node|
188
- node.type != :newline
189
- }
181
+ other_than_new_lines = children_wo_attributes.any? { |n| n.type != :newline }
190
182
 
191
- if other_than_new_lines
192
- text += '>'
193
- else
194
- text += '/>'
195
- end
183
+ text += if other_than_new_lines
184
+ '>'
185
+ else
186
+ '/>'
187
+ end
196
188
 
197
189
  conditional_nodes = current_node.children.select { |n| n.type == :output && n.conditional }
198
190
 
@@ -223,7 +215,7 @@ module Bade
223
215
  visit_node(last_node) if is_last_newline
224
216
  end
225
217
 
226
- unless conditional_nodes.empty?
218
+ unless conditional_nodes.empty? # rubocop:disable Style/GuardClause
227
219
  @code_indent -= 1
228
220
 
229
221
  buff_code 'end'
@@ -239,16 +231,14 @@ module Bade
239
231
  xml_attributes = []
240
232
 
241
233
  tag_node.attributes.each do |attr|
242
- unless all_attributes.include?(attr.name)
243
- xml_attributes << attr.name
244
- end
234
+ xml_attributes << attr.name unless all_attributes.include?(attr.name)
245
235
 
246
236
  all_attributes[attr.name] << attr.value
247
237
  end
248
238
 
249
239
  xml_attributes.map do |attr_name|
250
240
  joined = all_attributes[attr_name].join('), (')
251
- %Q{\#{__tag_render_attribute('#{attr_name}', (#{joined}))}}
241
+ "\#{__tag_render_attribute('#{attr_name}', (#{joined}))}"
252
242
  end.join
253
243
  end
254
244
 
@@ -274,7 +264,7 @@ module Bade
274
264
  blocks = mixin_node.blocks
275
265
 
276
266
  other_children = (mixin_node.children - mixin_node.blocks - mixin_node.params)
277
- if other_children.reject { |n| n.type == :newline }.count > 0
267
+ if other_children.count { |n| n.type != :newline } > 0
278
268
  def_block_node = AST::NodeRegistrator.create(:mixin_block, mixin_node.lineno)
279
269
  def_block_node.name = DEFAULT_BLOCK_NAME
280
270
  def_block_node.children = other_children
@@ -282,7 +272,7 @@ module Bade
282
272
  blocks << def_block_node
283
273
  end
284
274
 
285
- if blocks.length > 0
275
+ if !blocks.empty?
286
276
  buff_code '__blocks = {}'
287
277
 
288
278
  blocks.each do |block|
@@ -347,6 +337,22 @@ module Bade
347
337
  end
348
338
  end
349
339
 
340
+ # @param [MixinDeclarationNode] current_node
341
+ #
342
+ # @return [nil]
343
+ #
344
+ def visit_block_decl(current_node)
345
+ params = formatted_mixin_params(current_node)
346
+ buff_code "#{MIXINS_NAME}['#{current_node.name}'] = __create_mixin('#{current_node.name}', &lambda { |#{params}|"
347
+
348
+ code_indent do
349
+ blocks_name_declaration(current_node)
350
+ visit_nodes(current_node.children - current_node.params)
351
+ end
352
+
353
+ buff_code '})'
354
+ end
355
+
350
356
 
351
357
 
352
358
  # @param [String] str
@@ -8,7 +8,11 @@ require_relative 'ruby_extensions/string'
8
8
  require_relative 'ruby_extensions/array'
9
9
 
10
10
  module Bade
11
+ # Class to parse input string into AST::Document
12
+ #
11
13
  class Parser
14
+ # Error representing syntax error in specific file, line and column
15
+ #
12
16
  class SyntaxError < StandardError
13
17
  attr_reader :error, :file, :line, :lineno, :column
14
18
 
@@ -23,16 +27,19 @@ module Bade
23
27
  def to_s
24
28
  line = @line.lstrip
25
29
  column = @column + line.size - @line.size
26
- %{#{error}
27
- #{file}, Line #{lineno}, Column #{@column}
28
- #{line}
29
- #{' ' * column}^
30
- }
30
+ <<-MSG.strip_heredoc
31
+ #{error}
32
+ #{file}, Line #{lineno}, Column #{@column}
33
+ #{line}
34
+ #{' ' * column}^
35
+ MSG
31
36
  end
32
37
  end
33
38
 
34
39
  class ParserInternalError < StandardError; end
35
40
 
41
+ require_relative 'parser/parser_constants'
42
+
36
43
  # @type @stacks [Array<Bade::Node>]
37
44
 
38
45
  # @return [Array<String>]
@@ -52,7 +59,7 @@ module Bade
52
59
  @tabsize = tabsize
53
60
  @file_path = file_path
54
61
 
55
- @tab_re = /\G((?: {#{tabsize}})*) {0,#{tabsize-1}}\t/
62
+ @tab_re = /\G((?: {#{tabsize}})*) {0,#{tabsize - 1}}\t/
56
63
  @tab = '\1' + ' ' * tabsize
57
64
 
58
65
  reset
@@ -67,7 +74,7 @@ module Bade
67
74
 
68
75
  @dependency_paths = []
69
76
 
70
- if str.kind_of? Array
77
+ if str.is_a?(Array)
71
78
  reset(str, [[@root]])
72
79
  else
73
80
  reset(str.split(/\r?\n/, -1), [[@root]]) # -1 is for not suppressing empty lines
@@ -80,62 +87,6 @@ module Bade
80
87
  @document
81
88
  end
82
89
 
83
-
84
-
85
- WORD_RE = ''.respond_to?(:encoding) ? '\p{Word}' : '\w'
86
- NAME_RE_STRING = "(#{WORD_RE}(?:#{WORD_RE}|:|-|_)*)"
87
-
88
- ATTR_NAME_RE_STRING = "\\A\\s*#{NAME_RE_STRING}"
89
- CODE_ATTR_RE = /#{ATTR_NAME_RE_STRING}\s*&?:\s*/
90
-
91
- TAG_RE = /\A#{NAME_RE_STRING}/
92
- CLASS_TAG_RE = /\A\.#{NAME_RE_STRING}/
93
- ID_TAG_RE = /\A##{NAME_RE_STRING}/
94
-
95
- def reset(lines = nil, stacks = nil)
96
- # Since you can indent however you like in Slim, we need to keep a list
97
- # of how deeply indented you are. For instance, in a template like this:
98
- #
99
- # doctype # 0 spaces
100
- # html # 0 spaces
101
- # head # 1 space
102
- # title # 4 spaces
103
- #
104
- # indents will then contain [0, 1, 4] (when it's processing the last line.)
105
- #
106
- # We uses this information to figure out how many steps we must "jump"
107
- # out when we see an de-indented line.
108
- @indents = [0]
109
-
110
- # Whenever we want to output something, we'll *always* output it to the
111
- # last stack in this array. So when there's a line that expects
112
- # indentation, we simply push a new stack onto this array. When it
113
- # processes the next line, the content will then be outputted into that
114
- # stack.
115
- @stacks = stacks
116
-
117
- @lineno = 0
118
- @lines = lines
119
-
120
- # @return [String]
121
- @line = @orig_line = nil
122
- end
123
-
124
- def next_line
125
- if @lines.empty?
126
- @orig_line = @line = nil
127
-
128
- last_newlines = remove_last_newlines
129
- @root.children += last_newlines
130
-
131
- nil
132
- else
133
- @orig_line = @lines.shift
134
- @lineno += 1
135
- @line = @orig_line.dup
136
- end
137
- end
138
-
139
90
  # Calculate indent for line
140
91
  #
141
92
  # @param [String] line
@@ -151,9 +102,8 @@ module Bade
151
102
  # @param [Symbol] type
152
103
  #
153
104
  def append_node(type, indent: @indents.length, add: false, value: nil)
154
- while indent >= @stacks.length
155
- @stacks << @stacks.last.dup
156
- end
105
+ # add necessary stack items to match required indent
106
+ @stacks << @stacks.last.dup while indent >= @stacks.length
157
107
 
158
108
  parent = @stacks[indent].last
159
109
  node = AST::NodeRegistrator.create(type, @lineno)
@@ -161,9 +111,7 @@ module Bade
161
111
 
162
112
  node.value = value unless value.nil?
163
113
 
164
- if add
165
- @stacks[indent] << node
166
- end
114
+ @stacks[indent] << node if add
167
115
 
168
116
  node
169
117
  end
@@ -176,332 +124,18 @@ module Bade
176
124
  last_node.children.pop(last_newlines_count)
177
125
  end
178
126
 
179
- def parse_line
180
- if @line.strip.length == 0
181
- append_node(:newline) unless @lines.empty?
182
- return
183
- end
184
-
185
- indent = get_indent(@line)
186
-
187
- # left strip
188
- @line.remove_indent!(indent, @tabsize)
189
-
190
- # If there's more stacks than indents, it means that the previous
191
- # line is expecting this line to be indented.
192
- expecting_indentation = @stacks.length > @indents.length
193
-
194
- if indent > @indents.last
195
- @indents << indent
196
- else
197
- # This line was *not* indented more than the line before,
198
- # so we'll just forget about the stack that the previous line pushed.
199
- if expecting_indentation
200
- last_newlines = remove_last_newlines
201
-
202
- @stacks.pop
203
-
204
- new_node = @stacks.last.last
205
- new_node.children += last_newlines
206
- end
207
-
208
- # This line was deindented.
209
- # Now we're have to go through the all the indents and figure out
210
- # how many levels we've deindented.
211
- while indent < @indents.last
212
- last_newlines = remove_last_newlines
213
-
214
- @indents.pop
215
- @stacks.pop
216
-
217
- new_node = @stacks.last.last
218
- new_node.children += last_newlines
219
- end
220
-
221
- # Remove old stacks we don't need
222
- while not @stacks[indent].nil? and indent < @stacks[indent].length - 1
223
- last_newlines = remove_last_newlines
224
-
225
- @stacks[indent].pop
226
-
227
- new_node = @stacks.last.last
228
- new_node.children += last_newlines
229
- end
230
-
231
- # This line's indentation happens lie "between" two other line's
232
- # indentation:
233
- #
234
- # hello
235
- # world
236
- # this # <- This should not be possible!
237
- syntax_error('Malformed indentation') if indent != @indents.last
238
- end
239
-
240
- parse_line_indicators
241
- end
242
-
243
- module LineIndicatorRegexps
244
- IMPORT = /\Aimport /
245
- MIXIN_DECL = /\Amixin #{NAME_RE_STRING}/
246
- MIXIN_CALL = /\A\+#{NAME_RE_STRING}/
247
- BLOCK_DECLARATION = /\Ablock #{NAME_RE_STRING}/
248
- HTML_COMMENT = /\A\/\/! /
249
- NORMAL_COMMENT = /\A\/\//
250
- TEXT_BLOCK_START = /\A\|( ?)/
251
- INLINE_HTML = /\A</
252
- CODE_BLOCK = /\A-/
253
- OUTPUT_BLOCK = /\A(\??)(&?)=/
254
- DOCTYPE = /\Adoctype\s/i
255
- TAG_CLASS_START_BLOCK = /\A\./
256
- TAG_ID_START_BLOCK = /\A#/
257
- end
258
-
259
- module ParseRubyCodeRegexps
260
- END_NEW_LINE = /\A\s*\n/
261
- END_PARAMS_ARG = /\A\s*[,)]/
262
- end
263
-
264
- def parse_line_indicators(add_newline: true)
265
- case @line
266
- when LineIndicatorRegexps::IMPORT
267
- @line = $'
268
- parse_import
269
-
270
- when LineIndicatorRegexps::MIXIN_DECL
271
- # Mixin declaration
272
- @line = $'
273
- parse_mixin_declaration($1)
274
-
275
- when LineIndicatorRegexps::MIXIN_CALL
276
- # Mixin call
277
- @line = $'
278
- parse_mixin_call($1)
279
-
280
- when LineIndicatorRegexps::BLOCK_DECLARATION
281
- @line = $'
282
- if @stacks.last.last.type == :mixin_call
283
- node = append_node(:mixin_block, add: true)
284
- node.name = $1
285
- else
286
- # keyword block used outside of mixin call
287
- parse_tag($&)
288
- end
289
-
290
- when LineIndicatorRegexps::HTML_COMMENT
291
- # HTML comment
292
- append_node(:html_comment, add: true)
293
- parse_text_block $', @indents.last + @tabsize
294
-
295
- when LineIndicatorRegexps::NORMAL_COMMENT
296
- # Comment
297
- append_node(:comment, add: true)
298
- parse_text_block $', @indents.last + @tabsize
299
-
300
- when LineIndicatorRegexps::TEXT_BLOCK_START
301
- # Found a text block.
302
- parse_text_block $', @indents.last + @tabsize
303
-
304
- when LineIndicatorRegexps::INLINE_HTML
305
- # Inline html
306
- append_node(:text, value: @line)
307
-
308
- when LineIndicatorRegexps::CODE_BLOCK
309
- # Found a code block.
310
- append_node(:code, value: $'.strip)
311
-
312
- when LineIndicatorRegexps::OUTPUT_BLOCK
313
- # Found an output block.
314
- # We expect the line to be broken or the next line to be indented.
315
- @line = $'
316
- output_node = append_node(:output)
317
- output_node.conditional = $1.length == 1
318
- output_node.escaped = $2.length == 1
319
- output_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_NEW_LINE)
320
-
321
- when LineIndicatorRegexps::DOCTYPE
322
- # Found doctype declaration
323
- append_node(:doctype, value: $'.strip)
324
-
325
- when TAG_RE
326
- # Found a HTML tag.
327
- @line = $' if $1
328
- parse_tag($&)
329
-
330
- when LineIndicatorRegexps::TAG_CLASS_START_BLOCK
331
- # Found class name -> implicit div
332
- parse_tag 'div'
333
-
334
- when LineIndicatorRegexps::TAG_ID_START_BLOCK
335
- # Found id name -> implicit div
336
- parse_tag 'div'
337
-
338
- else
339
- syntax_error 'Unknown line indicator'
340
- end
341
-
342
- append_node(:newline) if add_newline && !@lines.empty?
343
- end
344
-
345
127
  def parse_import
346
- path = eval(@line)
128
+ # TODO: change this to something better
129
+ path = eval(@line) # rubocop:disable Lint/Eval
347
130
  append_node(:import, value: path)
348
131
 
349
132
  @dependency_paths << path unless @dependency_paths.include?(path)
350
133
  end
351
134
 
352
- module MixinRegexps
353
- TEXT_START = /\A /
354
- BLOCK_EXPANSION = /\A:\s+/
355
- OUTPUT_CODE = /\A(&?)=/
356
-
357
- PARAMS_END = /\A\s*\)/
358
-
359
- PARAMS_END_SPACES = /^\s*$/
360
- PARAMS_ARGS_DELIMITER = /\A\s*,/
361
-
362
- PARAMS_PARAM_NAME = /\A\s*#{NAME_RE_STRING}/
363
- PARAMS_BLOCK_NAME = /\A\s*&#{NAME_RE_STRING}/
364
- PARAMS_KEY_PARAM_NAME = CODE_ATTR_RE
365
- end
366
-
367
- def parse_mixin_call(mixin_name)
368
- mixin_name = fixed_trailing_colon(mixin_name)
369
-
370
- mixin_node = append_node(:mixin_call, add: true)
371
- mixin_node.name = mixin_name
372
-
373
- parse_mixin_call_params
374
-
375
- case @line
376
- when MixinRegexps::TEXT_START
377
- @line = $'
378
- parse_text
379
-
380
- when MixinRegexps::BLOCK_EXPANSION
381
- # Block expansion
382
- @line = $'
383
- parse_line_indicators(add_newline: false)
384
-
385
- when MixinRegexps::OUTPUT_CODE
386
- # Handle output code
387
- parse_line_indicators(add_newline: false)
388
-
389
- when ''
390
- # nothing
391
-
392
- else
393
- syntax_error "Unknown symbol after mixin calling, line = `#{@line}'"
394
- end
395
- end
396
-
397
- def parse_mixin_call_params
398
- # between tag name and attribute must not be space
399
- # and skip when is nothing other
400
- if @line.start_with?('(')
401
- @line.remove_first!
402
- else
403
- return
404
- end
405
-
406
- while true
407
- case @line
408
- when MixinRegexps::PARAMS_KEY_PARAM_NAME
409
- @line = $'
410
- attr_node = append_node(:mixin_key_param)
411
- attr_node.name = fixed_trailing_colon($1)
412
- attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
413
-
414
- when MixinRegexps::PARAMS_ARGS_DELIMITER
415
- # args delimiter
416
- @line = $'
417
- next
418
-
419
- when MixinRegexps::PARAMS_END_SPACES
420
- # spaces and/or end of line
421
- next_line
422
- next
423
-
424
- when MixinRegexps::PARAMS_END
425
- # Find ending delimiter
426
- @line = $'
427
- break
428
-
429
- else
430
- attr_node = append_node(:mixin_param)
431
- attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
432
- end
433
- end
434
- end
435
-
436
- def parse_mixin_declaration(mixin_name)
437
- mixin_node = append_node(:mixin_decl, add: true)
438
- mixin_node.name = mixin_name
439
-
440
- parse_mixin_declaration_params
441
- end
442
-
443
- def parse_mixin_declaration_params
444
- # between tag name and attribute must not be space
445
- # and skip when is nothing other
446
- if @line.start_with?('(')
447
- @line.remove_first!
448
- else
449
- return
450
- end
451
-
452
- while true
453
- case @line
454
- when MixinRegexps::PARAMS_KEY_PARAM_NAME
455
- # Value ruby code
456
- @line = $'
457
- attr_node = append_node(:mixin_key_param)
458
- attr_node.name = fixed_trailing_colon($1)
459
- attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
460
-
461
- when MixinRegexps::PARAMS_PARAM_NAME
462
- @line = $'
463
- append_node(:mixin_param, value: $1)
464
-
465
- when MixinRegexps::PARAMS_BLOCK_NAME
466
- @line = $'
467
- append_node(:mixin_block_param, value: $1)
468
-
469
- when MixinRegexps::PARAMS_ARGS_DELIMITER
470
- # args delimiter
471
- @line = $'
472
- next
473
-
474
- when MixinRegexps::PARAMS_END
475
- # Find ending delimiter
476
- @line = $'
477
- break
478
-
479
- else
480
- syntax_error('wrong mixin attribute syntax')
481
- end
482
- end
483
- end
484
-
485
- def parse_text
486
- text = @line
487
- text = text.gsub(/&\{/, '#{ __html_escaped ')
488
- append_node(:text, value: text)
489
- end
490
-
491
-
492
- module TagRegexps
493
- BLOCK_EXPANSION = /\A:\s+/
494
- OUTPUT_CODE = LineIndicatorRegexps::OUTPUT_BLOCK
495
- TEXT_START = /\A /
496
-
497
- PARAMS_ARGS_DELIMITER = /\A\s*,/
498
- PARAMS_END = /\A\s*\)/
499
- end
500
-
501
135
  # @param value [String]
502
136
  #
503
137
  def fixed_trailing_colon(value)
504
- if String === value && value.end_with?(':')
138
+ if value.is_a?(String) && value.end_with?(':')
505
139
  value = value.remove_last
506
140
  @line.prepend(':')
507
141
  end
@@ -509,205 +143,6 @@ module Bade
509
143
  value
510
144
  end
511
145
 
512
-
513
- # @param [String] tag tag name
514
- #
515
- def parse_tag(tag)
516
- tag = fixed_trailing_colon(tag)
517
-
518
- if tag.is_a?(AST::Node)
519
- tag_node = tag
520
- else
521
- tag_node = append_node(:tag, add: true)
522
- tag_node.name = tag
523
- end
524
-
525
- parse_tag_attributes
526
-
527
- case @line
528
- when TagRegexps::BLOCK_EXPANSION
529
- # Block expansion
530
- @line = $'
531
- parse_line_indicators(add_newline: false)
532
-
533
- when TagRegexps::OUTPUT_CODE
534
- # Handle output code
535
- parse_line_indicators(add_newline: false)
536
-
537
- when CLASS_TAG_RE
538
- # Class name
539
- @line = $'
540
-
541
- attr_node = append_node(:tag_attr)
542
- attr_node.name = 'class'
543
- attr_node.value = fixed_trailing_colon($1).single_quote
544
-
545
- parse_tag tag_node
546
-
547
- when ID_TAG_RE
548
- # Id name
549
- @line = $'
550
-
551
- attr_node = append_node(:tag_attr)
552
- attr_node.name = 'id'
553
- attr_node.value = fixed_trailing_colon($1).single_quote
554
-
555
- parse_tag tag_node
556
-
557
- when TagRegexps::TEXT_START
558
- # Text content
559
- @line = $'
560
- parse_text
561
-
562
- when ''
563
- # nothing
564
-
565
- else
566
- syntax_error "Unknown symbol after tag definition #{@line}"
567
- end
568
- end
569
-
570
- def parse_tag_attributes
571
- # Check to see if there is a delimiter right after the tag name
572
-
573
- # between tag name and attribute must not be space
574
- # and skip when is nothing other
575
- if @line.start_with?('(')
576
- @line.remove_first!
577
- else
578
- return
579
- end
580
-
581
- while true
582
- case @line
583
- when CODE_ATTR_RE
584
- # Value ruby code
585
- @line = $'
586
- attr_node = append_node(:tag_attr)
587
- attr_node.name = $1
588
- attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
589
-
590
- when TagRegexps::PARAMS_ARGS_DELIMITER
591
- # args delimiter
592
- @line = $'
593
- next
594
-
595
- when TagRegexps::PARAMS_END
596
- # Find ending delimiter
597
- @line = $'
598
- break
599
-
600
- else
601
- # Found something where an attribute should be
602
- @line.lstrip!
603
- syntax_error('Expected attribute') unless @line.empty?
604
-
605
- # Attributes span multiple lines
606
- append_node(:newline)
607
- syntax_error('Expected closing tag attributes delimiter `)`') if @lines.empty?
608
- next_line
609
- end
610
- end
611
- end
612
-
613
- def parse_text_block(first_line, text_indent = nil)
614
- if !first_line || first_line.empty?
615
- text_indent = nil
616
- else
617
- @line = first_line
618
- parse_text
619
- end
620
-
621
- until @lines.empty?
622
- if @lines.first.blank?
623
- next_line
624
- append_node(:newline)
625
- else
626
- indent = get_indent(@lines.first)
627
- break if indent <= @indents.last
628
-
629
- next_line
630
-
631
- @line.remove_indent!(text_indent ? text_indent : indent, @tabsize)
632
-
633
- parse_text
634
-
635
- # The indentation of first line of the text block
636
- # determines the text base indentation.
637
- text_indent ||= indent
638
- end
639
- end
640
- end
641
-
642
- # Parse ruby code, ended with outer delimiters
643
- #
644
- # @param [String, Regexp] outer_delimiters
645
- #
646
- # @return [Void] parsed ruby code
647
- #
648
- def parse_ruby_code(outer_delimiters)
649
- code = String.new
650
- end_re = if Regexp === outer_delimiters
651
- outer_delimiters
652
- else
653
- /\A\s*[#{Regexp.escape outer_delimiters.to_s}]/
654
- end
655
- delimiters = []
656
-
657
- until @line.empty? or (delimiters.count == 0 and @line =~ end_re)
658
- char = @line[0]
659
-
660
- # backslash escaped delimiter
661
- if char == '\\' && RUBY_ALL_DELIMITERS.include?(@line[1])
662
- code << @line.slice!(0, 2)
663
- next
664
- end
665
-
666
- case char
667
- when RUBY_START_DELIMITERS_RE
668
- if RUBY_NOT_NESTABLE_DELIMITERS.include?(char) && delimiters.last == char
669
- # end char of not nestable delimiter
670
- delimiters.pop
671
- else
672
- # diving
673
- delimiters << char
674
- end
675
-
676
- when RUBY_END_DELIMITERS_RE
677
- # rising
678
- if char == RUBY_DELIMITERS_REVERSE[delimiters.last]
679
- delimiters.pop
680
- end
681
- end
682
-
683
- code << @line.slice!(0)
684
- end
685
-
686
- unless delimiters.empty?
687
- syntax_error('Unexpected end of ruby code')
688
- end
689
-
690
- code.strip
691
- end
692
-
693
- RUBY_DELIMITERS_REVERSE = {
694
- '(' => ')',
695
- '[' => ']',
696
- '{' => '}'
697
- }.freeze
698
-
699
- RUBY_QUOTES = %w(' ").freeze
700
-
701
- RUBY_NOT_NESTABLE_DELIMITERS = RUBY_QUOTES
702
-
703
- RUBY_START_DELIMITERS = (%w(\( [ {) + RUBY_NOT_NESTABLE_DELIMITERS).freeze
704
- RUBY_END_DELIMITERS = (%w(\) ] }) + RUBY_NOT_NESTABLE_DELIMITERS).freeze
705
- RUBY_ALL_DELIMITERS = (RUBY_START_DELIMITERS + RUBY_END_DELIMITERS).uniq.freeze
706
-
707
- RUBY_START_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_START_DELIMITERS.join}]/
708
- RUBY_END_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_END_DELIMITERS.join}]/
709
-
710
-
711
146
  # ----------- Errors ---------------
712
147
 
713
148
  # Raise specific error
@@ -715,8 +150,14 @@ module Bade
715
150
  # @param [String] message
716
151
  #
717
152
  def syntax_error(message)
718
- raise SyntaxError.new(message, file_path, @orig_line, @lineno,
719
- @orig_line && @line ? @orig_line.size - @line.size : 0)
153
+ column = @orig_line && @line ? @orig_line.size - @line.size : 0
154
+ raise SyntaxError.new(message, file_path, @orig_line, @lineno, column)
720
155
  end
156
+
157
+ require_relative 'parser/parser_lines'
158
+ require_relative 'parser/parser_tag'
159
+ require_relative 'parser/parser_mixin'
160
+ require_relative 'parser/parser_ruby_code'
161
+ require_relative 'parser/parser_text'
721
162
  end
722
163
  end