bade 0.2.0 → 0.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
  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