bade 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Bade.gemspec +1 -0
- data/Gemfile +3 -0
- data/README.md +3 -2
- data/lib/bade.rb +1 -0
- data/lib/bade/ast/document.rb +53 -0
- data/lib/bade/ast/node.rb +50 -0
- data/lib/bade/ast/node/doctype_node.rb +25 -0
- data/lib/bade/ast/node/key_value_node.rb +21 -0
- data/lib/bade/ast/node/mixin_node.rb +45 -0
- data/lib/bade/ast/node/tag_node.rb +23 -0
- data/lib/bade/ast/node/value_node.rb +32 -0
- data/lib/bade/ast/node_registrator.rb +82 -0
- data/lib/bade/ast/string_serializer.rb +72 -0
- data/lib/bade/generator.rb +353 -6
- data/lib/bade/parser.rb +199 -129
- data/lib/bade/precompiled.rb +61 -0
- data/lib/bade/renderer.rb +151 -31
- data/lib/bade/ruby_extensions/array.rb +45 -0
- data/lib/bade/ruby_extensions/string.rb +50 -20
- data/lib/bade/runtime.rb +2 -0
- data/lib/bade/runtime/block.rb +56 -10
- data/lib/bade/runtime/mixin.rb +43 -0
- data/lib/bade/runtime/render_binding.rb +53 -9
- data/lib/bade/version.rb +2 -1
- metadata +16 -14
- data/lib/bade/document.rb +0 -33
- data/lib/bade/generator/html_generator.rb +0 -80
- data/lib/bade/generator/ruby_generator.rb +0 -336
- data/lib/bade/node.rb +0 -116
- data/lib/bade/node/doctype_node.rb +0 -21
- data/lib/bade/node/key_value_node.rb +0 -10
- data/lib/bade/node/mixin_node.rb +0 -69
- data/lib/bade/node/tag_node.rb +0 -29
- data/lib/bade/ruby_extensions/object.rb +0 -11
data/lib/bade/generator.rb
CHANGED
@@ -1,16 +1,363 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'runtime'
|
4
|
+
|
5
|
+
require_relative 'ast/document'
|
2
6
|
|
3
7
|
module Bade
|
4
8
|
class Generator
|
5
|
-
|
6
|
-
|
9
|
+
BUFF_NAME = :__buff
|
10
|
+
MIXINS_NAME = :__mixins
|
11
|
+
NEW_LINE_NAME = :__new_line
|
12
|
+
CURRENT_INDENT_NAME = :__indent
|
13
|
+
BASE_INDENT_NAME = :__base_indent
|
14
|
+
|
15
|
+
DEFAULT_BLOCK_NAME = 'default_block'
|
16
|
+
|
17
|
+
# @param [Document] document
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
#
|
21
|
+
def self.document_to_lambda_string(document)
|
22
|
+
generator = self.new
|
23
|
+
generator.generate_lambda_string(document)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Bade::AST::Document] document
|
27
|
+
#
|
28
|
+
# @return [String] string to parse with Ruby
|
29
|
+
#
|
30
|
+
def generate_lambda_string(document)
|
31
|
+
@document = document
|
32
|
+
@buff = []
|
33
|
+
@indent = 0
|
34
|
+
@code_indent = 0
|
35
|
+
|
36
|
+
buff_code '# frozen_string_literal: true' # so it can be faster on Ruby 2.3+
|
37
|
+
buff_code ''
|
38
|
+
buff_code "lambda do |#{NEW_LINE_NAME}: \"\\n\", #{BASE_INDENT_NAME}: ' '|"
|
39
|
+
|
40
|
+
code_indent {
|
41
|
+
buff_code "self.#{NEW_LINE_NAME} = #{NEW_LINE_NAME}"
|
42
|
+
buff_code "self.#{BASE_INDENT_NAME} = #{BASE_INDENT_NAME}"
|
43
|
+
|
44
|
+
visit_document(document)
|
45
|
+
|
46
|
+
buff_code "output = #{BUFF_NAME}.join"
|
47
|
+
buff_code 'self.__reset'
|
48
|
+
buff_code 'output'
|
49
|
+
}
|
50
|
+
|
51
|
+
buff_code 'end'
|
52
|
+
|
53
|
+
|
54
|
+
@document = nil
|
55
|
+
|
56
|
+
@buff.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [String] text
|
60
|
+
#
|
61
|
+
def buff_print_text(text, indent: false, new_line: false)
|
62
|
+
buff_print_value(%Q{%Q{#{text}}}) if text.length > 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def buff_print_value(value)
|
66
|
+
# buff_code %Q{#{BUFF_NAME} << #{CURRENT_INDENT_NAME}} if indent
|
67
|
+
buff_code(%Q{#{BUFF_NAME} << #{value}})
|
68
|
+
end
|
69
|
+
|
70
|
+
def buff_code(text)
|
71
|
+
@buff << ' ' * @code_indent + text
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# @param document [Bade::Document]
|
76
|
+
#
|
77
|
+
def visit_document(document)
|
78
|
+
document.sub_documents.each do |sub_document|
|
79
|
+
visit_document(sub_document)
|
80
|
+
end
|
81
|
+
|
82
|
+
buff_code("# ----- start file #{document.file_path}") unless document.file_path.nil?
|
83
|
+
visit_node(document.root)
|
84
|
+
buff_code("# ----- end file #{document.file_path}") unless document.file_path.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param current_node [Node]
|
88
|
+
#
|
89
|
+
def visit_node_children(current_node)
|
90
|
+
visit_nodes(current_node.children)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param nodes [Array<Node>]
|
94
|
+
#
|
95
|
+
def visit_nodes(nodes)
|
96
|
+
nodes.each { |node|
|
97
|
+
visit_node(node)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param current_node [Node]
|
102
|
+
#
|
103
|
+
def visit_node(current_node)
|
104
|
+
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"
|
162
|
+
else
|
163
|
+
nil # other cases are handled in Renderer
|
164
|
+
end
|
165
|
+
|
166
|
+
buff_code "load('#{load_path}')" unless load_path.nil?
|
167
|
+
|
168
|
+
else
|
169
|
+
raise "Unknown type #{current_node.type}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# @param [TagNode] current_node
|
174
|
+
#
|
175
|
+
# @return [nil]
|
176
|
+
#
|
177
|
+
def visit_tag(current_node)
|
178
|
+
attributes = formatted_attributes(current_node)
|
179
|
+
children_wo_attributes = (current_node.children - current_node.attributes)
|
180
|
+
|
181
|
+
text = "<#{current_node.name}"
|
7
182
|
|
8
|
-
|
183
|
+
if attributes.length > 0
|
184
|
+
text += "#{attributes}"
|
185
|
+
end
|
186
|
+
|
187
|
+
other_than_new_lines = children_wo_attributes.any? { |node|
|
188
|
+
node.type != :newline
|
189
|
+
}
|
190
|
+
|
191
|
+
if other_than_new_lines
|
192
|
+
text += '>'
|
193
|
+
else
|
194
|
+
text += '/>'
|
195
|
+
end
|
196
|
+
|
197
|
+
conditional_nodes = current_node.children.select { |n| n.type == :output && n.conditional }
|
198
|
+
|
199
|
+
unless conditional_nodes.empty?
|
200
|
+
buff_code "if (#{conditional_nodes.map(&:value).join(') && (')})"
|
201
|
+
|
202
|
+
@code_indent += 1
|
203
|
+
end
|
204
|
+
|
205
|
+
buff_print_text(text, new_line: true, indent: true)
|
206
|
+
|
207
|
+
if other_than_new_lines
|
208
|
+
last_node = children_wo_attributes.last
|
209
|
+
is_last_newline = !last_node.nil? && last_node.type == :newline
|
210
|
+
nodes = if is_last_newline
|
211
|
+
children_wo_attributes[0...-1]
|
212
|
+
else
|
213
|
+
children_wo_attributes
|
214
|
+
end
|
215
|
+
|
216
|
+
code_indent do
|
217
|
+
visit_nodes(nodes)
|
218
|
+
end
|
219
|
+
|
220
|
+
buff_print_text("</#{current_node.name}>", new_line: true, indent: true)
|
221
|
+
|
222
|
+
# print new line after the tag
|
223
|
+
visit_node(last_node) if is_last_newline
|
224
|
+
end
|
225
|
+
|
226
|
+
unless conditional_nodes.empty?
|
227
|
+
@code_indent -= 1
|
228
|
+
|
229
|
+
buff_code 'end'
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# @param [TagNode] tag_node
|
9
234
|
#
|
10
|
-
# @return [
|
235
|
+
# @return [String] formatted attributes
|
11
236
|
#
|
12
|
-
def
|
237
|
+
def formatted_attributes(tag_node)
|
238
|
+
all_attributes = Hash.new { |hash, key| hash[key] = [] }
|
239
|
+
xml_attributes = []
|
240
|
+
|
241
|
+
tag_node.attributes.each do |attr|
|
242
|
+
unless all_attributes.include?(attr.name)
|
243
|
+
xml_attributes << attr.name
|
244
|
+
end
|
13
245
|
|
246
|
+
all_attributes[attr.name] << attr.value
|
247
|
+
end
|
248
|
+
|
249
|
+
xml_attributes.map do |attr_name|
|
250
|
+
joined = all_attributes[attr_name].join('), (')
|
251
|
+
%Q{\#{__tag_render_attribute('#{attr_name}', (#{joined}))}}
|
252
|
+
end.join
|
253
|
+
end
|
254
|
+
|
255
|
+
# Method for indenting generated code, indent is raised only in passed block
|
256
|
+
#
|
257
|
+
# @return [nil]
|
258
|
+
#
|
259
|
+
def code_indent(plus = 1)
|
260
|
+
@code_indent += plus
|
261
|
+
yield
|
262
|
+
@code_indent -= plus
|
263
|
+
end
|
264
|
+
|
265
|
+
# @param [MixinCommonNode] mixin_node
|
266
|
+
#
|
267
|
+
# @return [String] formatted params
|
268
|
+
#
|
269
|
+
def formatted_mixin_params(mixin_node)
|
270
|
+
params = mixin_node.params
|
271
|
+
result = []
|
272
|
+
|
273
|
+
if mixin_node.type == :mixin_call
|
274
|
+
blocks = mixin_node.blocks
|
275
|
+
|
276
|
+
other_children = (mixin_node.children - mixin_node.blocks - mixin_node.params)
|
277
|
+
if other_children.reject { |n| n.type == :newline }.count > 0
|
278
|
+
def_block_node = AST::NodeRegistrator.create(:mixin_block, mixin_node.lineno)
|
279
|
+
def_block_node.name = DEFAULT_BLOCK_NAME
|
280
|
+
def_block_node.children = other_children
|
281
|
+
|
282
|
+
blocks << def_block_node
|
283
|
+
end
|
284
|
+
|
285
|
+
if blocks.length > 0
|
286
|
+
buff_code '__blocks = {}'
|
287
|
+
|
288
|
+
blocks.each do |block|
|
289
|
+
block_definition(block)
|
290
|
+
end
|
291
|
+
|
292
|
+
result << '__blocks.dup'
|
293
|
+
else
|
294
|
+
result << '{}'
|
295
|
+
end
|
296
|
+
elsif mixin_node.type == :mixin_decl
|
297
|
+
result << '__blocks'
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
# normal params
|
302
|
+
result += params.select { |n| n.type == :mixin_param }.map(&:value)
|
303
|
+
result += params.select { |n| n.type == :mixin_key_param }.map { |param| "#{param.name}: #{param.value}" }
|
304
|
+
|
305
|
+
result.join(', ')
|
306
|
+
end
|
307
|
+
|
308
|
+
# Generates code for definition of block
|
309
|
+
#
|
310
|
+
# @param [MixinCallBlockNode] block_node
|
311
|
+
#
|
312
|
+
# @return [nil]
|
313
|
+
#
|
314
|
+
def block_definition(block_node)
|
315
|
+
buff_code "__blocks['#{block_node.name}'] = __create_block('#{block_node.name}') do"
|
316
|
+
|
317
|
+
code_indent do
|
318
|
+
buff_code '__buffs_push()'
|
319
|
+
|
320
|
+
visit_node_children(block_node)
|
321
|
+
|
322
|
+
buff_code '__buffs_pop()'
|
323
|
+
end
|
324
|
+
|
325
|
+
buff_code 'end'
|
326
|
+
end
|
327
|
+
|
328
|
+
# Generates code for block variables declaration in mixin definition
|
329
|
+
#
|
330
|
+
# @param [String] block_name
|
331
|
+
#
|
332
|
+
# @return [nil]
|
333
|
+
#
|
334
|
+
def block_name_declaration(block_name)
|
335
|
+
buff_code "#{block_name} = __blocks.delete('#{block_name}') { __create_block('#{block_name}') }"
|
336
|
+
end
|
337
|
+
|
338
|
+
# @param [MixinDeclarationNode] mixin_node
|
339
|
+
#
|
340
|
+
# @return [nil]
|
341
|
+
#
|
342
|
+
def blocks_name_declaration(mixin_node)
|
343
|
+
block_name_declaration(DEFAULT_BLOCK_NAME)
|
344
|
+
|
345
|
+
mixin_node.params.select { |n| n.type == :mixin_block_param }.each do |param|
|
346
|
+
block_name_declaration(param.value)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
|
352
|
+
# @param [String] str
|
353
|
+
#
|
354
|
+
# @return [Void]
|
355
|
+
#
|
356
|
+
def escape_double_quotes!(str)
|
357
|
+
str.gsub!(/"/, '\"')
|
14
358
|
end
|
15
359
|
end
|
360
|
+
|
361
|
+
# backward compatibility
|
362
|
+
RubyGenerator = Generator
|
16
363
|
end
|
data/lib/bade/parser.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ast/node'
|
4
|
+
require_relative 'ast/document'
|
5
|
+
require_relative 'ast/node_registrator'
|
1
6
|
|
2
|
-
require_relative 'node'
|
3
|
-
require_relative 'document'
|
4
7
|
require_relative 'ruby_extensions/string'
|
8
|
+
require_relative 'ruby_extensions/array'
|
5
9
|
|
6
10
|
module Bade
|
7
11
|
class Parser
|
@@ -29,35 +33,36 @@ module Bade
|
|
29
33
|
|
30
34
|
class ParserInternalError < StandardError; end
|
31
35
|
|
36
|
+
# @type @stacks [Array<Bade::Node>]
|
37
|
+
|
32
38
|
# @return [Array<String>]
|
33
39
|
#
|
34
40
|
attr_reader :dependency_paths
|
35
41
|
|
36
|
-
#
|
42
|
+
# @return [String]
|
37
43
|
#
|
38
|
-
|
39
|
-
|
40
|
-
#
|
44
|
+
attr_reader :file_path
|
45
|
+
|
46
|
+
# @param [Fixnum] tabsize
|
47
|
+
# @param [String] file_path
|
41
48
|
#
|
42
|
-
def initialize(
|
49
|
+
def initialize(tabsize: 4, file_path: nil)
|
43
50
|
@line = ''
|
44
51
|
|
45
|
-
tabsize = options.delete(:tabsize) { 4 }
|
46
52
|
@tabsize = tabsize
|
53
|
+
@file_path = file_path
|
47
54
|
|
48
55
|
@tab_re = /\G((?: {#{tabsize}})*) {0,#{tabsize-1}}\t/
|
49
56
|
@tab = '\1' + ' ' * tabsize
|
50
57
|
|
51
|
-
@options = options
|
52
|
-
|
53
58
|
reset
|
54
59
|
end
|
55
60
|
|
56
61
|
# @param [String, Array<String>] str
|
57
|
-
# @return [Bade::Document] root node
|
62
|
+
# @return [Bade::AST::Document] root node
|
58
63
|
#
|
59
64
|
def parse(str)
|
60
|
-
@document = Document.new(file_path:
|
65
|
+
@document = AST::Document.new(file_path: file_path)
|
61
66
|
@root = @document.root
|
62
67
|
|
63
68
|
@dependency_paths = []
|
@@ -65,7 +70,7 @@ module Bade
|
|
65
70
|
if str.kind_of? Array
|
66
71
|
reset(str, [[@root]])
|
67
72
|
else
|
68
|
-
reset(str.split(/\r?\n
|
73
|
+
reset(str.split(/\r?\n/, -1), [[@root]]) # -1 is for not suppressing empty lines
|
69
74
|
end
|
70
75
|
|
71
76
|
parse_line while next_line
|
@@ -81,7 +86,7 @@ module Bade
|
|
81
86
|
NAME_RE_STRING = "(#{WORD_RE}(?:#{WORD_RE}|:|-|_)*)"
|
82
87
|
|
83
88
|
ATTR_NAME_RE_STRING = "\\A\\s*#{NAME_RE_STRING}"
|
84
|
-
CODE_ATTR_RE = /#{ATTR_NAME_RE_STRING}\s
|
89
|
+
CODE_ATTR_RE = /#{ATTR_NAME_RE_STRING}\s*&?:\s*/
|
85
90
|
|
86
91
|
TAG_RE = /\A#{NAME_RE_STRING}/
|
87
92
|
CLASS_TAG_RE = /\A\.#{NAME_RE_STRING}/
|
@@ -119,6 +124,11 @@ module Bade
|
|
119
124
|
def next_line
|
120
125
|
if @lines.empty?
|
121
126
|
@orig_line = @line = nil
|
127
|
+
|
128
|
+
last_newlines = remove_last_newlines
|
129
|
+
@root.children += last_newlines
|
130
|
+
|
131
|
+
nil
|
122
132
|
else
|
123
133
|
@orig_line = @lines.shift
|
124
134
|
@lineno += 1
|
@@ -140,16 +150,16 @@ module Bade
|
|
140
150
|
#
|
141
151
|
# @param [Symbol] type
|
142
152
|
#
|
143
|
-
def append_node(type, indent: @indents.length, add: false,
|
153
|
+
def append_node(type, indent: @indents.length, add: false, value: nil)
|
144
154
|
while indent >= @stacks.length
|
145
155
|
@stacks << @stacks.last.dup
|
146
156
|
end
|
147
157
|
|
148
158
|
parent = @stacks[indent].last
|
149
|
-
node =
|
150
|
-
|
159
|
+
node = AST::NodeRegistrator.create(type, @lineno)
|
160
|
+
parent.children << node
|
151
161
|
|
152
|
-
node.
|
162
|
+
node.value = value unless value.nil?
|
153
163
|
|
154
164
|
if add
|
155
165
|
@stacks[indent] << node
|
@@ -158,19 +168,24 @@ module Bade
|
|
158
168
|
node
|
159
169
|
end
|
160
170
|
|
161
|
-
|
162
|
-
|
171
|
+
# @return [Array<AST::Node>]
|
172
|
+
#
|
173
|
+
def remove_last_newlines
|
174
|
+
last_node = @stacks.last.last
|
175
|
+
last_newlines_count = last_node.children.rcount_matching { |n| n.type == :newline }
|
176
|
+
last_node.children.pop(last_newlines_count)
|
177
|
+
end
|
163
178
|
|
164
|
-
|
165
|
-
|
179
|
+
def parse_line
|
180
|
+
if @line.strip.length == 0
|
181
|
+
append_node(:newline) unless @lines.empty?
|
166
182
|
return
|
167
183
|
end
|
168
184
|
|
169
|
-
indent = get_indent(line)
|
185
|
+
indent = get_indent(@line)
|
170
186
|
|
171
187
|
# left strip
|
172
|
-
line.remove_indent!(indent, @tabsize)
|
173
|
-
@line = line
|
188
|
+
@line.remove_indent!(indent, @tabsize)
|
174
189
|
|
175
190
|
# If there's more stacks than indents, it means that the previous
|
176
191
|
# line is expecting this line to be indented.
|
@@ -181,19 +196,36 @@ module Bade
|
|
181
196
|
else
|
182
197
|
# This line was *not* indented more than the line before,
|
183
198
|
# so we'll just forget about the stack that the previous line pushed.
|
184
|
-
|
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
|
185
207
|
|
186
208
|
# This line was deindented.
|
187
209
|
# Now we're have to go through the all the indents and figure out
|
188
210
|
# how many levels we've deindented.
|
189
211
|
while indent < @indents.last
|
212
|
+
last_newlines = remove_last_newlines
|
213
|
+
|
190
214
|
@indents.pop
|
191
215
|
@stacks.pop
|
216
|
+
|
217
|
+
new_node = @stacks.last.last
|
218
|
+
new_node.children += last_newlines
|
192
219
|
end
|
193
220
|
|
194
221
|
# Remove old stacks we don't need
|
195
222
|
while not @stacks[indent].nil? and indent < @stacks[indent].length - 1
|
223
|
+
last_newlines = remove_last_newlines
|
224
|
+
|
196
225
|
@stacks[indent].pop
|
226
|
+
|
227
|
+
new_node = @stacks.last.last
|
228
|
+
new_node.children += last_newlines
|
197
229
|
end
|
198
230
|
|
199
231
|
# This line's indentation happens lie "between" two other line's
|
@@ -208,83 +240,98 @@ module Bade
|
|
208
240
|
parse_line_indicators
|
209
241
|
end
|
210
242
|
|
211
|
-
|
212
|
-
|
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
|
213
263
|
|
264
|
+
def parse_line_indicators(add_newline: true)
|
214
265
|
case @line
|
215
|
-
when
|
266
|
+
when LineIndicatorRegexps::IMPORT
|
216
267
|
@line = $'
|
217
268
|
parse_import
|
218
269
|
|
219
|
-
when
|
270
|
+
when LineIndicatorRegexps::MIXIN_DECL
|
220
271
|
# Mixin declaration
|
221
272
|
@line = $'
|
222
273
|
parse_mixin_declaration($1)
|
223
274
|
|
224
|
-
when
|
275
|
+
when LineIndicatorRegexps::MIXIN_CALL
|
225
276
|
# Mixin call
|
226
277
|
@line = $'
|
227
278
|
parse_mixin_call($1)
|
228
279
|
|
229
|
-
when
|
280
|
+
when LineIndicatorRegexps::BLOCK_DECLARATION
|
230
281
|
@line = $'
|
231
282
|
if @stacks.last.last.type == :mixin_call
|
232
|
-
append_node
|
283
|
+
node = append_node(:mixin_block, add: true)
|
284
|
+
node.name = $1
|
233
285
|
else
|
234
286
|
# keyword block used outside of mixin call
|
235
287
|
parse_tag($&)
|
236
288
|
end
|
237
289
|
|
238
|
-
when
|
290
|
+
when LineIndicatorRegexps::HTML_COMMENT
|
239
291
|
# HTML comment
|
240
|
-
append_node
|
292
|
+
append_node(:html_comment, add: true)
|
241
293
|
parse_text_block $', @indents.last + @tabsize
|
242
294
|
|
243
|
-
when
|
295
|
+
when LineIndicatorRegexps::NORMAL_COMMENT
|
244
296
|
# Comment
|
245
|
-
append_node
|
297
|
+
append_node(:comment, add: true)
|
246
298
|
parse_text_block $', @indents.last + @tabsize
|
247
299
|
|
248
|
-
when
|
300
|
+
when LineIndicatorRegexps::TEXT_BLOCK_START
|
249
301
|
# Found a text block.
|
250
302
|
parse_text_block $', @indents.last + @tabsize
|
251
303
|
|
252
|
-
when
|
304
|
+
when LineIndicatorRegexps::INLINE_HTML
|
253
305
|
# Inline html
|
254
|
-
append_node
|
306
|
+
append_node(:text, value: @line)
|
255
307
|
|
256
|
-
when
|
308
|
+
when LineIndicatorRegexps::CODE_BLOCK
|
257
309
|
# Found a code block.
|
258
|
-
|
259
|
-
code_node.data = $1
|
260
|
-
add_new_line = false
|
310
|
+
append_node(:code, value: $'.strip)
|
261
311
|
|
262
|
-
when
|
312
|
+
when LineIndicatorRegexps::OUTPUT_BLOCK
|
263
313
|
# Found an output block.
|
264
314
|
# We expect the line to be broken or the next line to be indented.
|
265
315
|
@line = $'
|
266
|
-
output_node = append_node
|
267
|
-
output_node.
|
268
|
-
output_node.
|
269
|
-
|
270
|
-
when /\A(\w+):\s*\Z/
|
271
|
-
# Embedded template detected. It is treated as block.
|
272
|
-
@stacks.last << [:slim, :embedded, $1, parse_text_block]
|
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)
|
273
320
|
|
274
|
-
when
|
321
|
+
when LineIndicatorRegexps::DOCTYPE
|
275
322
|
# Found doctype declaration
|
276
|
-
append_node
|
323
|
+
append_node(:doctype, value: $'.strip)
|
277
324
|
|
278
325
|
when TAG_RE
|
279
326
|
# Found a HTML tag.
|
280
327
|
@line = $' if $1
|
281
328
|
parse_tag($&)
|
282
329
|
|
283
|
-
when
|
330
|
+
when LineIndicatorRegexps::TAG_CLASS_START_BLOCK
|
284
331
|
# Found class name -> implicit div
|
285
332
|
parse_tag 'div'
|
286
333
|
|
287
|
-
when
|
334
|
+
when LineIndicatorRegexps::TAG_ID_START_BLOCK
|
288
335
|
# Found id name -> implicit div
|
289
336
|
parse_tag 'div'
|
290
337
|
|
@@ -292,38 +339,54 @@ module Bade
|
|
292
339
|
syntax_error 'Unknown line indicator'
|
293
340
|
end
|
294
341
|
|
295
|
-
append_node
|
342
|
+
append_node(:newline) if add_newline && !@lines.empty?
|
296
343
|
end
|
297
344
|
|
298
345
|
def parse_import
|
299
346
|
path = eval(@line)
|
300
|
-
|
301
|
-
import_node.data = path
|
347
|
+
append_node(:import, value: path)
|
302
348
|
|
303
349
|
@dependency_paths << path unless @dependency_paths.include?(path)
|
304
350
|
end
|
305
351
|
|
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
|
+
|
306
367
|
def parse_mixin_call(mixin_name)
|
307
|
-
|
308
|
-
|
368
|
+
mixin_name = fixed_trailing_colon(mixin_name)
|
369
|
+
|
370
|
+
mixin_node = append_node(:mixin_call, add: true)
|
371
|
+
mixin_node.name = mixin_name
|
309
372
|
|
310
373
|
parse_mixin_call_params
|
311
374
|
|
312
375
|
case @line
|
313
|
-
when
|
376
|
+
when MixinRegexps::TEXT_START
|
314
377
|
@line = $'
|
315
378
|
parse_text
|
316
379
|
|
317
|
-
when
|
380
|
+
when MixinRegexps::BLOCK_EXPANSION
|
318
381
|
# Block expansion
|
319
382
|
@line = $'
|
320
|
-
parse_line_indicators
|
383
|
+
parse_line_indicators(add_newline: false)
|
321
384
|
|
322
|
-
when
|
385
|
+
when MixinRegexps::OUTPUT_CODE
|
323
386
|
# Handle output code
|
324
|
-
parse_line_indicators
|
387
|
+
parse_line_indicators(add_newline: false)
|
325
388
|
|
326
|
-
when
|
389
|
+
when ''
|
327
390
|
# nothing
|
328
391
|
|
329
392
|
else
|
@@ -334,47 +397,45 @@ module Bade
|
|
334
397
|
def parse_mixin_call_params
|
335
398
|
# between tag name and attribute must not be space
|
336
399
|
# and skip when is nothing other
|
337
|
-
if @line
|
338
|
-
@line
|
400
|
+
if @line.start_with?('(')
|
401
|
+
@line.remove_first!
|
339
402
|
else
|
340
403
|
return
|
341
404
|
end
|
342
405
|
|
343
|
-
end_re = /\A\s*\)/
|
344
|
-
|
345
406
|
while true
|
346
407
|
case @line
|
347
|
-
when
|
408
|
+
when MixinRegexps::PARAMS_KEY_PARAM_NAME
|
348
409
|
@line = $'
|
349
|
-
attr_node = append_node
|
350
|
-
attr_node.name = $1
|
351
|
-
attr_node.value = parse_ruby_code(
|
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)
|
352
413
|
|
353
|
-
when
|
414
|
+
when MixinRegexps::PARAMS_ARGS_DELIMITER
|
354
415
|
# args delimiter
|
355
416
|
@line = $'
|
356
417
|
next
|
357
418
|
|
358
|
-
when
|
419
|
+
when MixinRegexps::PARAMS_END_SPACES
|
359
420
|
# spaces and/or end of line
|
360
421
|
next_line
|
361
422
|
next
|
362
423
|
|
363
|
-
when
|
424
|
+
when MixinRegexps::PARAMS_END
|
364
425
|
# Find ending delimiter
|
365
426
|
@line = $'
|
366
427
|
break
|
367
428
|
|
368
429
|
else
|
369
|
-
attr_node = append_node
|
370
|
-
attr_node.
|
430
|
+
attr_node = append_node(:mixin_param)
|
431
|
+
attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
|
371
432
|
end
|
372
433
|
end
|
373
434
|
end
|
374
435
|
|
375
436
|
def parse_mixin_declaration(mixin_name)
|
376
|
-
mixin_node = append_node
|
377
|
-
mixin_node.
|
437
|
+
mixin_node = append_node(:mixin_decl, add: true)
|
438
|
+
mixin_node.name = mixin_name
|
378
439
|
|
379
440
|
parse_mixin_declaration_params
|
380
441
|
end
|
@@ -382,37 +443,35 @@ module Bade
|
|
382
443
|
def parse_mixin_declaration_params
|
383
444
|
# between tag name and attribute must not be space
|
384
445
|
# and skip when is nothing other
|
385
|
-
if @line
|
386
|
-
@line
|
446
|
+
if @line.start_with?('(')
|
447
|
+
@line.remove_first!
|
387
448
|
else
|
388
449
|
return
|
389
450
|
end
|
390
451
|
|
391
|
-
end_re = /\A\s*\)/
|
392
|
-
|
393
452
|
while true
|
394
453
|
case @line
|
395
|
-
when
|
454
|
+
when MixinRegexps::PARAMS_KEY_PARAM_NAME
|
396
455
|
# Value ruby code
|
397
456
|
@line = $'
|
398
|
-
attr_node = append_node
|
399
|
-
attr_node.name = $1
|
400
|
-
attr_node.value = parse_ruby_code(
|
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)
|
401
460
|
|
402
|
-
when
|
461
|
+
when MixinRegexps::PARAMS_PARAM_NAME
|
403
462
|
@line = $'
|
404
|
-
append_node
|
463
|
+
append_node(:mixin_param, value: $1)
|
405
464
|
|
406
|
-
when
|
465
|
+
when MixinRegexps::PARAMS_BLOCK_NAME
|
407
466
|
@line = $'
|
408
|
-
append_node
|
467
|
+
append_node(:mixin_block_param, value: $1)
|
409
468
|
|
410
|
-
when
|
469
|
+
when MixinRegexps::PARAMS_ARGS_DELIMITER
|
411
470
|
# args delimiter
|
412
471
|
@line = $'
|
413
472
|
next
|
414
473
|
|
415
|
-
when
|
474
|
+
when MixinRegexps::PARAMS_END
|
416
475
|
# Find ending delimiter
|
417
476
|
@line = $'
|
418
477
|
break
|
@@ -425,17 +484,26 @@ module Bade
|
|
425
484
|
|
426
485
|
def parse_text
|
427
486
|
text = @line
|
428
|
-
text = text.gsub(/&\{/, '#{
|
429
|
-
append_node
|
487
|
+
text = text.gsub(/&\{/, '#{ __html_escaped ')
|
488
|
+
append_node(:text, value: text)
|
430
489
|
end
|
431
490
|
|
432
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
|
+
|
433
501
|
# @param value [String]
|
434
502
|
#
|
435
503
|
def fixed_trailing_colon(value)
|
436
|
-
if value
|
437
|
-
value = value.
|
438
|
-
@line.prepend
|
504
|
+
if String === value && value.end_with?(':')
|
505
|
+
value = value.remove_last
|
506
|
+
@line.prepend(':')
|
439
507
|
end
|
440
508
|
|
441
509
|
value
|
@@ -447,30 +515,30 @@ module Bade
|
|
447
515
|
def parse_tag(tag)
|
448
516
|
tag = fixed_trailing_colon(tag)
|
449
517
|
|
450
|
-
if tag.is_a?
|
518
|
+
if tag.is_a?(AST::Node)
|
451
519
|
tag_node = tag
|
452
520
|
else
|
453
|
-
tag_node = append_node
|
521
|
+
tag_node = append_node(:tag, add: true)
|
454
522
|
tag_node.name = tag
|
455
523
|
end
|
456
524
|
|
457
525
|
parse_tag_attributes
|
458
526
|
|
459
527
|
case @line
|
460
|
-
when
|
528
|
+
when TagRegexps::BLOCK_EXPANSION
|
461
529
|
# Block expansion
|
462
530
|
@line = $'
|
463
|
-
parse_line_indicators
|
531
|
+
parse_line_indicators(add_newline: false)
|
464
532
|
|
465
|
-
when
|
533
|
+
when TagRegexps::OUTPUT_CODE
|
466
534
|
# Handle output code
|
467
|
-
parse_line_indicators
|
535
|
+
parse_line_indicators(add_newline: false)
|
468
536
|
|
469
537
|
when CLASS_TAG_RE
|
470
538
|
# Class name
|
471
539
|
@line = $'
|
472
540
|
|
473
|
-
attr_node = append_node
|
541
|
+
attr_node = append_node(:tag_attr)
|
474
542
|
attr_node.name = 'class'
|
475
543
|
attr_node.value = fixed_trailing_colon($1).single_quote
|
476
544
|
|
@@ -480,18 +548,18 @@ module Bade
|
|
480
548
|
# Id name
|
481
549
|
@line = $'
|
482
550
|
|
483
|
-
attr_node = append_node
|
551
|
+
attr_node = append_node(:tag_attr)
|
484
552
|
attr_node.name = 'id'
|
485
553
|
attr_node.value = fixed_trailing_colon($1).single_quote
|
486
554
|
|
487
555
|
parse_tag tag_node
|
488
556
|
|
489
|
-
when
|
557
|
+
when TagRegexps::TEXT_START
|
490
558
|
# Text content
|
491
559
|
@line = $'
|
492
560
|
parse_text
|
493
561
|
|
494
|
-
when
|
562
|
+
when ''
|
495
563
|
# nothing
|
496
564
|
|
497
565
|
else
|
@@ -504,29 +572,27 @@ module Bade
|
|
504
572
|
|
505
573
|
# between tag name and attribute must not be space
|
506
574
|
# and skip when is nothing other
|
507
|
-
if @line
|
508
|
-
@line
|
575
|
+
if @line.start_with?('(')
|
576
|
+
@line.remove_first!
|
509
577
|
else
|
510
578
|
return
|
511
579
|
end
|
512
580
|
|
513
|
-
end_re = /\A\s*\)/
|
514
|
-
|
515
581
|
while true
|
516
582
|
case @line
|
517
583
|
when CODE_ATTR_RE
|
518
584
|
# Value ruby code
|
519
585
|
@line = $'
|
520
|
-
attr_node = append_node
|
586
|
+
attr_node = append_node(:tag_attr)
|
521
587
|
attr_node.name = $1
|
522
|
-
attr_node.value = parse_ruby_code(
|
588
|
+
attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
|
523
589
|
|
524
|
-
when
|
590
|
+
when TagRegexps::PARAMS_ARGS_DELIMITER
|
525
591
|
# args delimiter
|
526
592
|
@line = $'
|
527
593
|
next
|
528
594
|
|
529
|
-
when
|
595
|
+
when TagRegexps::PARAMS_END
|
530
596
|
# Find ending delimiter
|
531
597
|
@line = $'
|
532
598
|
break
|
@@ -537,8 +603,8 @@ module Bade
|
|
537
603
|
syntax_error('Expected attribute') unless @line.empty?
|
538
604
|
|
539
605
|
# Attributes span multiple lines
|
540
|
-
|
541
|
-
syntax_error(
|
606
|
+
append_node(:newline)
|
607
|
+
syntax_error('Expected closing tag attributes delimiter `)`') if @lines.empty?
|
542
608
|
next_line
|
543
609
|
end
|
544
610
|
end
|
@@ -553,9 +619,9 @@ module Bade
|
|
553
619
|
end
|
554
620
|
|
555
621
|
until @lines.empty?
|
556
|
-
if @lines.first
|
622
|
+
if @lines.first.blank?
|
557
623
|
next_line
|
558
|
-
append_node
|
624
|
+
append_node(:newline)
|
559
625
|
else
|
560
626
|
indent = get_indent(@lines.first)
|
561
627
|
break if indent <= @indents.last
|
@@ -575,13 +641,17 @@ module Bade
|
|
575
641
|
|
576
642
|
# Parse ruby code, ended with outer delimiters
|
577
643
|
#
|
578
|
-
# @param [String] outer_delimiters
|
644
|
+
# @param [String, Regexp] outer_delimiters
|
579
645
|
#
|
580
646
|
# @return [Void] parsed ruby code
|
581
647
|
#
|
582
648
|
def parse_ruby_code(outer_delimiters)
|
583
|
-
code =
|
584
|
-
end_re =
|
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
|
585
655
|
delimiters = []
|
586
656
|
|
587
657
|
until @line.empty? or (delimiters.count == 0 and @line =~ end_re)
|
@@ -634,8 +704,8 @@ module Bade
|
|
634
704
|
RUBY_END_DELIMITERS = (%w(\) ] }) + RUBY_NOT_NESTABLE_DELIMITERS).freeze
|
635
705
|
RUBY_ALL_DELIMITERS = (RUBY_START_DELIMITERS + RUBY_END_DELIMITERS).uniq.freeze
|
636
706
|
|
637
|
-
RUBY_START_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_START_DELIMITERS.join
|
638
|
-
RUBY_END_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_END_DELIMITERS.join
|
707
|
+
RUBY_START_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_START_DELIMITERS.join}]/
|
708
|
+
RUBY_END_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_END_DELIMITERS.join}]/
|
639
709
|
|
640
710
|
|
641
711
|
# ----------- Errors ---------------
|
@@ -645,7 +715,7 @@ module Bade
|
|
645
715
|
# @param [String] message
|
646
716
|
#
|
647
717
|
def syntax_error(message)
|
648
|
-
raise SyntaxError.new(message,
|
718
|
+
raise SyntaxError.new(message, file_path, @orig_line, @lineno,
|
649
719
|
@orig_line && @line ? @orig_line.size - @line.size : 0)
|
650
720
|
end
|
651
721
|
end
|