bade 0.1.4 → 0.2.0
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 +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
|