bade 0.2.5 → 0.3.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 +5 -4
- data/Gemfile +6 -2
- data/README.md +3 -3
- data/lib/bade/ast/document.rb +1 -1
- data/lib/bade/ast/node/value_node.rb +10 -1
- data/lib/bade/ast/node.rb +12 -2
- data/lib/bade/ast/node_registrator.rb +3 -2
- data/lib/bade/ast/string_serializer.rb +3 -5
- data/lib/bade/generator.rb +73 -19
- data/lib/bade/parser/parser_lines.rb +8 -6
- data/lib/bade/parser/parser_mixin.rb +7 -1
- data/lib/bade/parser.rb +4 -2
- data/lib/bade/precompiled.rb +2 -2
- data/lib/bade/renderer.rb +66 -20
- data/lib/bade/ruby2_keywords.rb +1 -0
- data/lib/bade/ruby_extensions/string.rb +4 -3
- data/lib/bade/runtime/block.rb +47 -9
- data/lib/bade/runtime/globals_tracker.rb +59 -0
- data/lib/bade/runtime/mixin.rb +18 -6
- data/lib/bade/runtime/render_binding.rb +56 -13
- data/lib/bade/runtime.rb +79 -1
- data/lib/bade/version.rb +1 -1
- data/lib/bade.rb +1 -0
- metadata +20 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 202b283306b0a05d870766ff8d7c17f8312727843632958adb2bb3f2300a673f
|
|
4
|
+
data.tar.gz: '0820f4f77eb7936b77748257faa1fd4ee51b206d4a7929c656f717127c7b2248'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ecd2dbf7aa570c8051a2e1b59a1a537583cd2cd5005bb06328795de099fab4213a9cfd616a788d66e333a862253d6aecedd928632e3edb923f021e86658f6780
|
|
7
|
+
data.tar.gz: 8633aba2120eaf8f60f96a357cde8eda8c32d862e92dca3c060f66c014bf1031e5bc2916077ee1d1a7de6494cc62c426ded062065c664949acaf849fce71de66
|
data/Bade.gemspec
CHANGED
|
@@ -14,16 +14,17 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
spec.summary = 'Minimalistic template engine for Ruby.'
|
|
15
15
|
spec.homepage = 'https://github.com/epuber-io/bade'
|
|
16
16
|
spec.license = 'MIT'
|
|
17
|
-
spec.
|
|
17
|
+
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
|
18
|
+
spec.required_ruby_version = '>= 2.5'
|
|
18
19
|
|
|
19
20
|
spec.files = Dir['bin/**/*'] + Dir['lib/**/*'] + %w[Bade.gemspec Gemfile README.md]
|
|
20
21
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
21
22
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
22
23
|
spec.require_paths = ['lib']
|
|
23
24
|
|
|
24
|
-
spec.add_dependency 'psych', '>= 2.2', '<
|
|
25
|
+
spec.add_dependency 'psych', '>= 2.2', '< 5.0'
|
|
25
26
|
|
|
26
|
-
spec.add_development_dependency '
|
|
27
|
+
spec.add_development_dependency 'fakefs', '~> 1.3'
|
|
27
28
|
spec.add_development_dependency 'rspec', '~> 3.2'
|
|
28
|
-
spec.add_development_dependency 'rubocop', '~>
|
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 1.14'
|
|
29
30
|
end
|
data/Gemfile
CHANGED
|
@@ -5,9 +5,13 @@ source 'https://rubygems.org'
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
7
|
gem 'benchmark-ips', require: false
|
|
8
|
-
gem 'coveralls', require: false
|
|
9
8
|
|
|
10
|
-
group :
|
|
9
|
+
group :dev, optional: true do
|
|
10
|
+
gem 'debase', require: false
|
|
11
|
+
gem 'ruby-debug-ide', require: false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
group :benchmarks, optional: true do
|
|
11
15
|
gem 'flamegraph'
|
|
12
16
|
gem 'ruby-prof'
|
|
13
17
|
end
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
# Bade
|
|
3
3
|
|
|
4
|
-
[](http://badge.fury.io/rb/bade) [](http://badge.fury.io/rb/bade) [](https://github.com/epuber-io/bade/actions) [](https://coveralls.io/github/epuber-io/bade?branch=master) [](https://inch-ci.org/github/epuber-io/bade)
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Minimalistic template engine written in Ruby for Ruby. Developed mainly to help with creating e-books. Highly influenced by [Jade](http://jade-lang.com) and [Slim](http://slim-lang.com).
|
|
@@ -30,7 +30,7 @@ Or install it yourself as:
|
|
|
30
30
|
|
|
31
31
|
## Development
|
|
32
32
|
|
|
33
|
-
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
|
|
33
|
+
After checking out the repo, run `bundle install --with dev` to install dependencies. Then, run `rake spec` to run the tests.
|
|
34
34
|
|
|
35
35
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
36
36
|
|
|
@@ -40,7 +40,7 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
|
40
40
|
Bug reports and pull requests are welcome on GitHub at https://github.com/epuber-io/bade. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## TODO
|
|
44
44
|
|
|
45
45
|
- [ ] create documentation about syntax
|
|
46
46
|
- [ ] create several examples
|
data/lib/bade/ast/document.rb
CHANGED
|
@@ -17,17 +17,26 @@ module Bade
|
|
|
17
17
|
#
|
|
18
18
|
attr_accessor :conditional
|
|
19
19
|
|
|
20
|
+
# @return [String, nil]
|
|
21
|
+
#
|
|
22
|
+
attr_accessor :default_value
|
|
23
|
+
|
|
20
24
|
ruby2_keywords def initialize(*args)
|
|
21
25
|
super(*args)
|
|
22
26
|
|
|
23
27
|
@escaped = false
|
|
24
28
|
@conditional = false
|
|
29
|
+
@default_value = nil
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
# @param [ValueNode] other
|
|
28
33
|
#
|
|
29
34
|
def ==(other)
|
|
30
|
-
super &&
|
|
35
|
+
super &&
|
|
36
|
+
value == other.value &&
|
|
37
|
+
escaped == other.escaped &&
|
|
38
|
+
conditional == other.conditional &&
|
|
39
|
+
default_value == other.default_value
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
42
|
end
|
data/lib/bade/ast/node.rb
CHANGED
|
@@ -10,7 +10,11 @@ module Bade
|
|
|
10
10
|
#
|
|
11
11
|
attr_reader :type
|
|
12
12
|
|
|
13
|
-
# @return [
|
|
13
|
+
# @return [Bade::AST::Node, nil]
|
|
14
|
+
#
|
|
15
|
+
attr_accessor :parent
|
|
16
|
+
|
|
17
|
+
# @return [Array<Bade::AST::Node>]
|
|
14
18
|
#
|
|
15
19
|
attr_accessor :children
|
|
16
20
|
|
|
@@ -20,10 +24,16 @@ module Bade
|
|
|
20
24
|
#
|
|
21
25
|
attr_reader :lineno
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
# @return [String] filename
|
|
28
|
+
#
|
|
29
|
+
attr_reader :filename
|
|
30
|
+
|
|
31
|
+
def initialize(type, parent = nil, lineno: nil, filename: nil)
|
|
24
32
|
@type = type
|
|
33
|
+
@parent = parent
|
|
25
34
|
@children = []
|
|
26
35
|
@lineno = lineno
|
|
36
|
+
@filename = filename
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
def to_s
|
|
@@ -38,12 +38,12 @@ module Bade
|
|
|
38
38
|
#
|
|
39
39
|
# @return [Bade::AST::Node]
|
|
40
40
|
#
|
|
41
|
-
def create(type, lineno)
|
|
41
|
+
def create(type, parent, lineno: nil, filename: nil)
|
|
42
42
|
klass = registered_types[type]
|
|
43
43
|
|
|
44
44
|
raise ::KeyError, "Undefined node type #{type.inspect}" if klass.nil?
|
|
45
45
|
|
|
46
|
-
klass.new(type, lineno: lineno)
|
|
46
|
+
klass.new(type, parent, lineno: lineno, filename: filename)
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -55,6 +55,7 @@ module Bade
|
|
|
55
55
|
register_type DoctypeNode, :doctype
|
|
56
56
|
|
|
57
57
|
register_type ValueNode, :import
|
|
58
|
+
register_type ValueNode, :yield
|
|
58
59
|
|
|
59
60
|
# --- Comments
|
|
60
61
|
|
|
@@ -37,13 +37,13 @@ module Bade
|
|
|
37
37
|
|
|
38
38
|
children_s = ''
|
|
39
39
|
if node.children.count > 0
|
|
40
|
-
children_s = "\n
|
|
40
|
+
children_s = "\n#{node.children.map { |n| node_to_s(n, level + 1) }.join("\n")}\n#{indent}"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
other = ''
|
|
44
44
|
|
|
45
45
|
case node
|
|
46
|
-
when TagNode
|
|
46
|
+
when TagNode, MixinCommonNode
|
|
47
47
|
other = node.name
|
|
48
48
|
when KeyValueNode
|
|
49
49
|
other = "#{node.name}:#{node.value}"
|
|
@@ -56,15 +56,13 @@ module Bade
|
|
|
56
56
|
''
|
|
57
57
|
end
|
|
58
58
|
other = "#{escaped_sign}#{node.value}"
|
|
59
|
-
when MixinCommonNode
|
|
60
|
-
other = node.name
|
|
61
59
|
when Node
|
|
62
60
|
# nothing
|
|
63
61
|
else
|
|
64
62
|
raise "Unknown node class #{node.class} of type #{node.type} for serializing"
|
|
65
63
|
end
|
|
66
64
|
|
|
67
|
-
other =
|
|
65
|
+
other = " #{other}" if other && !other.empty?
|
|
68
66
|
|
|
69
67
|
"#{indent}(#{type_s}#{other}#{children_s})"
|
|
70
68
|
end
|
data/lib/bade/generator.rb
CHANGED
|
@@ -75,7 +75,7 @@ module Bade
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def buff_code(text)
|
|
78
|
-
@buff << ' ' * @code_indent
|
|
78
|
+
@buff << "#{' ' * @code_indent}#{text}"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# @param document [Bade::Document]
|
|
@@ -86,6 +86,7 @@ module Bade
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
buff_code("# ----- start file #{document.file_path}") unless document.file_path.nil?
|
|
89
|
+
buff_code "__buffs_push(#{location(filename: document.file_path, lineno: 0, label: '<top>')})"
|
|
89
90
|
|
|
90
91
|
new_root = if @optimize
|
|
91
92
|
Optimizer.new(document.root).optimize
|
|
@@ -115,6 +116,8 @@ module Bade
|
|
|
115
116
|
# @param current_node [Node]
|
|
116
117
|
#
|
|
117
118
|
def visit_node(current_node)
|
|
119
|
+
update_location_node(current_node)
|
|
120
|
+
|
|
118
121
|
case current_node.type
|
|
119
122
|
when :root
|
|
120
123
|
visit_node_children(current_node)
|
|
@@ -134,7 +137,7 @@ module Bade
|
|
|
134
137
|
buff_print_text ' -->'
|
|
135
138
|
|
|
136
139
|
when :comment
|
|
137
|
-
comment_text =
|
|
140
|
+
comment_text = "##{current_node.children.map(&:value).join("\n#")}"
|
|
138
141
|
buff_code(comment_text)
|
|
139
142
|
|
|
140
143
|
when :doctype
|
|
@@ -166,8 +169,11 @@ module Bade
|
|
|
166
169
|
"#{base_path}.rb"
|
|
167
170
|
end
|
|
168
171
|
|
|
169
|
-
buff_code "
|
|
170
|
-
|
|
172
|
+
buff_code "__load('#{load_path}')" unless load_path.nil?
|
|
173
|
+
when :yield
|
|
174
|
+
block_name = DEFAULT_BLOCK_NAME
|
|
175
|
+
method = current_node.conditional ? 'call' : 'call!'
|
|
176
|
+
buff_code "#{block_name}.#{method}"
|
|
171
177
|
else
|
|
172
178
|
raise "Unknown type #{current_node.type}"
|
|
173
179
|
end
|
|
@@ -267,19 +273,22 @@ module Bade
|
|
|
267
273
|
params = mixin_node.params
|
|
268
274
|
result = []
|
|
269
275
|
|
|
270
|
-
|
|
276
|
+
case mixin_node.type
|
|
277
|
+
when :mixin_call
|
|
271
278
|
blocks = mixin_node.blocks
|
|
272
279
|
|
|
273
280
|
other_children = (mixin_node.children - mixin_node.blocks - mixin_node.params)
|
|
274
281
|
if other_children.count { |n| n.type != :newline } > 0
|
|
275
|
-
def_block_node = AST::NodeRegistrator.create(:mixin_block, mixin_node.lineno)
|
|
282
|
+
def_block_node = AST::NodeRegistrator.create(:mixin_block, mixin_node, lineno: mixin_node.lineno)
|
|
276
283
|
def_block_node.name = DEFAULT_BLOCK_NAME
|
|
277
284
|
def_block_node.children = other_children
|
|
278
285
|
|
|
279
286
|
blocks << def_block_node
|
|
280
287
|
end
|
|
281
288
|
|
|
282
|
-
if
|
|
289
|
+
if blocks.empty?
|
|
290
|
+
result << '{}'
|
|
291
|
+
else
|
|
283
292
|
buff_code '__blocks = {}'
|
|
284
293
|
|
|
285
294
|
blocks.each do |block|
|
|
@@ -287,17 +296,18 @@ module Bade
|
|
|
287
296
|
end
|
|
288
297
|
|
|
289
298
|
result << '__blocks.dup'
|
|
290
|
-
else
|
|
291
|
-
result << '{}'
|
|
292
299
|
end
|
|
293
|
-
|
|
300
|
+
when :mixin_decl
|
|
294
301
|
result << '__blocks'
|
|
295
302
|
end
|
|
296
303
|
|
|
304
|
+
# positional params
|
|
305
|
+
result += params.select { |n| n.type == :mixin_param }
|
|
306
|
+
.map { |param| param.default_value ? "#{param.value} = #{param.default_value}" : param.value }
|
|
297
307
|
|
|
298
|
-
#
|
|
299
|
-
result += params.select { |n| n.type == :
|
|
300
|
-
|
|
308
|
+
# key-value params
|
|
309
|
+
result += params.select { |n| n.type == :mixin_key_param }
|
|
310
|
+
.map { |param| "#{param.name}: #{param.value}" }
|
|
301
311
|
|
|
302
312
|
result.join(', ')
|
|
303
313
|
end
|
|
@@ -309,14 +319,10 @@ module Bade
|
|
|
309
319
|
# @return [nil]
|
|
310
320
|
#
|
|
311
321
|
def block_definition(block_node)
|
|
312
|
-
buff_code "__blocks['#{block_node.name}'] = __create_block('#{block_node.name}') do"
|
|
322
|
+
buff_code "__blocks['#{block_node.name}'] = __create_block('#{block_node.name}', #{location_node(block_node)}) do"
|
|
313
323
|
|
|
314
324
|
code_indent do
|
|
315
|
-
buff_code '__buffs_push()'
|
|
316
|
-
|
|
317
325
|
visit_node_children(block_node)
|
|
318
|
-
|
|
319
|
-
buff_code '__buffs_pop()'
|
|
320
326
|
end
|
|
321
327
|
|
|
322
328
|
buff_code 'end'
|
|
@@ -350,7 +356,7 @@ module Bade
|
|
|
350
356
|
#
|
|
351
357
|
def visit_block_decl(current_node)
|
|
352
358
|
params = formatted_mixin_params(current_node)
|
|
353
|
-
buff_code "#{MIXINS_NAME}['#{current_node.name}'] = __create_mixin('#{current_node.name}', &lambda { |#{params}|"
|
|
359
|
+
buff_code "#{MIXINS_NAME}['#{current_node.name}'] = __create_mixin('#{current_node.name}', #{location_node(current_node)}, &lambda { |#{params}|"
|
|
354
360
|
|
|
355
361
|
code_indent do
|
|
356
362
|
blocks_name_declaration(current_node)
|
|
@@ -367,6 +373,54 @@ module Bade
|
|
|
367
373
|
def escape_double_quotes!(str)
|
|
368
374
|
str.gsub!(/"/, '\"')
|
|
369
375
|
end
|
|
376
|
+
|
|
377
|
+
# @param [Bade::AST::Node]
|
|
378
|
+
# @return [Void]
|
|
379
|
+
def update_location_node(node)
|
|
380
|
+
should_skip = case node.type
|
|
381
|
+
when :code
|
|
382
|
+
value = node.value.strip
|
|
383
|
+
|
|
384
|
+
%w[end else }].include?(value) || value.match(/^when /)
|
|
385
|
+
when :newline
|
|
386
|
+
true
|
|
387
|
+
else
|
|
388
|
+
false
|
|
389
|
+
end
|
|
390
|
+
return if should_skip
|
|
391
|
+
return if node.lineno.nil?
|
|
392
|
+
|
|
393
|
+
buff_code "__update_lineno(#{node.lineno})"
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# @param [String] filename
|
|
397
|
+
# @param [Fixnum] lineno
|
|
398
|
+
# @param [String] label
|
|
399
|
+
# @return [String]
|
|
400
|
+
def location(filename:, lineno:, label:)
|
|
401
|
+
args = [
|
|
402
|
+
filename ? "path: '#{filename}'" : nil,
|
|
403
|
+
"lineno: #{lineno}",
|
|
404
|
+
"label: '#{label}'",
|
|
405
|
+
].compact
|
|
406
|
+
|
|
407
|
+
"Location.new(#{args.join(',')})"
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# @param [Node] node
|
|
411
|
+
# @return [String]
|
|
412
|
+
def location_node(node)
|
|
413
|
+
label = case node.type
|
|
414
|
+
when :mixin_decl
|
|
415
|
+
"+#{node.name}"
|
|
416
|
+
when :mixin_block
|
|
417
|
+
"#{node.name} in +#{node.parent.name}"
|
|
418
|
+
else
|
|
419
|
+
node.name
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
location(filename: node.filename, lineno: node.lineno, label: label)
|
|
423
|
+
end
|
|
370
424
|
end
|
|
371
425
|
|
|
372
426
|
# backward compatibility
|
|
@@ -7,6 +7,7 @@ module Bade
|
|
|
7
7
|
class Parser
|
|
8
8
|
module LineIndicatorRegexps
|
|
9
9
|
IMPORT = /\Aimport /.freeze
|
|
10
|
+
YIELD = /\Ayield(!?)/.freeze
|
|
10
11
|
MIXIN_DECL = /\Amixin #{NAME_RE_STRING}/.freeze
|
|
11
12
|
MIXIN_CALL = /\A\+#{NAME_RE_STRING}/.freeze
|
|
12
13
|
BLOCK_DECLARATION = /\Ablock #{NAME_RE_STRING}/.freeze
|
|
@@ -135,6 +136,11 @@ module Bade
|
|
|
135
136
|
@line = $'
|
|
136
137
|
parse_import
|
|
137
138
|
|
|
139
|
+
when LineIndicatorRegexps::YIELD
|
|
140
|
+
@line = $'
|
|
141
|
+
node = append_node(:yield, add: true)
|
|
142
|
+
node.conditional = $1.nil?
|
|
143
|
+
|
|
138
144
|
when LineIndicatorRegexps::MIXIN_DECL
|
|
139
145
|
# Mixin declaration
|
|
140
146
|
@line = $'
|
|
@@ -195,12 +201,8 @@ module Bade
|
|
|
195
201
|
@line = $' if $1
|
|
196
202
|
parse_tag($&)
|
|
197
203
|
|
|
198
|
-
when LineIndicatorRegexps::TAG_CLASS_START_BLOCK
|
|
199
|
-
# Found class
|
|
200
|
-
parse_tag 'div'
|
|
201
|
-
|
|
202
|
-
when LineIndicatorRegexps::TAG_ID_START_BLOCK
|
|
203
|
-
# Found id name -> implicit div
|
|
204
|
+
when LineIndicatorRegexps::TAG_CLASS_START_BLOCK, LineIndicatorRegexps::TAG_ID_START_BLOCK
|
|
205
|
+
# Found a class or id selector.
|
|
204
206
|
parse_tag 'div'
|
|
205
207
|
|
|
206
208
|
else
|
|
@@ -18,6 +18,7 @@ module Bade
|
|
|
18
18
|
PARAMS_PARAM_NAME = /\A\s*#{NAME_RE_STRING}/.freeze
|
|
19
19
|
PARAMS_BLOCK_NAME = /\A\s*&#{NAME_RE_STRING}/.freeze
|
|
20
20
|
PARAMS_KEY_PARAM_NAME = CODE_ATTR_RE
|
|
21
|
+
PARAMS_PARAM_DEFAULT_START = /\A\s*=/.freeze
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def parse_mixin_call(mixin_name)
|
|
@@ -114,7 +115,12 @@ module Bade
|
|
|
114
115
|
|
|
115
116
|
when MixinRegexps::PARAMS_PARAM_NAME
|
|
116
117
|
@line = $'
|
|
117
|
-
append_node(:mixin_param, value: $1)
|
|
118
|
+
attr_node = append_node(:mixin_param, value: $1)
|
|
119
|
+
|
|
120
|
+
if @line =~ MixinRegexps::PARAMS_PARAM_DEFAULT_START
|
|
121
|
+
@line = $'
|
|
122
|
+
attr_node.default_value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
|
|
123
|
+
end
|
|
118
124
|
|
|
119
125
|
when MixinRegexps::PARAMS_BLOCK_NAME
|
|
120
126
|
@line = $'
|
data/lib/bade/parser.rb
CHANGED
|
@@ -17,6 +17,8 @@ module Bade
|
|
|
17
17
|
attr_reader :error, :file, :line, :lineno, :column
|
|
18
18
|
|
|
19
19
|
def initialize(error, file, line, lineno, column)
|
|
20
|
+
super(error)
|
|
21
|
+
|
|
20
22
|
@error = error
|
|
21
23
|
@file = file || '(__TEMPLATE__)'
|
|
22
24
|
@line = line.to_s
|
|
@@ -60,7 +62,7 @@ module Bade
|
|
|
60
62
|
@file_path = file_path
|
|
61
63
|
|
|
62
64
|
@tab_re = /\G((?: {#{tabsize}})*) {0,#{tabsize - 1}}\t/
|
|
63
|
-
@tab =
|
|
65
|
+
@tab = "\1#{' ' * tabsize}"
|
|
64
66
|
|
|
65
67
|
reset
|
|
66
68
|
end
|
|
@@ -106,7 +108,7 @@ module Bade
|
|
|
106
108
|
@stacks << @stacks.last.dup while indent >= @stacks.length
|
|
107
109
|
|
|
108
110
|
parent = @stacks[indent].last
|
|
109
|
-
node = AST::NodeRegistrator.create(type, @lineno)
|
|
111
|
+
node = AST::NodeRegistrator.create(type, parent, lineno: @lineno, filename: @file_path)
|
|
110
112
|
parent.children << node
|
|
111
113
|
|
|
112
114
|
node.value = value unless value.nil?
|
data/lib/bade/precompiled.rb
CHANGED
|
@@ -26,9 +26,9 @@ module Bade
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
hash = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.0')
|
|
29
|
-
|
|
29
|
+
Psych.safe_load(file, filename: file.path, permitted_classes: [Symbol])
|
|
30
30
|
else
|
|
31
|
-
|
|
31
|
+
Psych.safe_load(file, [Symbol])
|
|
32
32
|
end
|
|
33
33
|
raise LoadError, 'YAML file is not in valid format' unless hash.is_a?(Hash)
|
|
34
34
|
|
data/lib/bade/renderer.rb
CHANGED
|
@@ -9,7 +9,7 @@ require_relative 'precompiled'
|
|
|
9
9
|
|
|
10
10
|
module Bade
|
|
11
11
|
class Renderer
|
|
12
|
-
class LoadError < ::RuntimeError
|
|
12
|
+
class LoadError < Bade::Runtime::RuntimeError
|
|
13
13
|
# @return [String]
|
|
14
14
|
#
|
|
15
15
|
attr_reader :loading_path
|
|
@@ -22,18 +22,29 @@ module Bade
|
|
|
22
22
|
# @param [String] reference_path reference file from which is load performed
|
|
23
23
|
# @param [String] msg standard message
|
|
24
24
|
#
|
|
25
|
-
def initialize(loading_path, reference_path, msg =
|
|
26
|
-
super(msg)
|
|
25
|
+
def initialize(loading_path, reference_path, msg, template_backtrace = [])
|
|
26
|
+
super(msg, template_backtrace)
|
|
27
27
|
@loading_path = loading_path
|
|
28
28
|
@reference_path = reference_path
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
class << self
|
|
33
|
+
def _globals_tracker
|
|
34
|
+
@globals_tracker ||= Bade::Runtime::GlobalsTracker.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# When set to true it will remove all constants that template created. Off by default.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
attr_accessor :clear_constants
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
# @param clear_constants [Boolean] When set to true it will remove all constants that template created. Off by default.
|
|
44
|
+
def initialize(clear_constants: Bade::Renderer.clear_constants)
|
|
45
|
+
@optimize = false
|
|
46
|
+
@clear_constants = clear_constants
|
|
47
|
+
end
|
|
37
48
|
|
|
38
49
|
# @return [String]
|
|
39
50
|
#
|
|
@@ -59,6 +70,10 @@ module Bade
|
|
|
59
70
|
#
|
|
60
71
|
attr_accessor :optimize
|
|
61
72
|
|
|
73
|
+
# @return [Boolean] When set to true it will remove all constants that template created. Off by default.
|
|
74
|
+
#
|
|
75
|
+
attr_accessor :clear_constants
|
|
76
|
+
|
|
62
77
|
|
|
63
78
|
# ----------------------------------------------------------------------------- #
|
|
64
79
|
# Internal attributes
|
|
@@ -77,8 +92,8 @@ module Bade
|
|
|
77
92
|
#
|
|
78
93
|
# @return [Renderer] preconfigured instance of this class
|
|
79
94
|
#
|
|
80
|
-
def self.from_source(source, file_path = nil)
|
|
81
|
-
inst = new
|
|
95
|
+
def self.from_source(source, file_path = nil, clear_constants: Bade::Renderer.clear_constants)
|
|
96
|
+
inst = new(clear_constants: clear_constants)
|
|
82
97
|
inst.source_text = source
|
|
83
98
|
inst.file_path = file_path
|
|
84
99
|
inst
|
|
@@ -88,14 +103,14 @@ module Bade
|
|
|
88
103
|
#
|
|
89
104
|
# @return [Renderer] preconfigured instance of this class
|
|
90
105
|
#
|
|
91
|
-
def self.from_file(file)
|
|
106
|
+
def self.from_file(file, clear_constants: Bade::Renderer.clear_constants)
|
|
92
107
|
path = if file.is_a?(File)
|
|
93
108
|
file.path
|
|
94
109
|
else
|
|
95
110
|
file
|
|
96
111
|
end
|
|
97
112
|
|
|
98
|
-
from_source(nil, path)
|
|
113
|
+
from_source(nil, path, clear_constants: clear_constants)
|
|
99
114
|
end
|
|
100
115
|
|
|
101
116
|
# Method to create Renderer from Precompiled object, for example when you want to reuse precompiled object from disk
|
|
@@ -104,8 +119,8 @@ module Bade
|
|
|
104
119
|
#
|
|
105
120
|
# @return [Renderer] preconfigured instance of this class
|
|
106
121
|
#
|
|
107
|
-
def self.from_precompiled(precompiled)
|
|
108
|
-
inst = new
|
|
122
|
+
def self.from_precompiled(precompiled, clear_constants: Bade::Renderer.clear_constants)
|
|
123
|
+
inst = new(clear_constants: clear_constants)
|
|
109
124
|
inst.precompiled = precompiled
|
|
110
125
|
inst
|
|
111
126
|
end
|
|
@@ -125,11 +140,20 @@ module Bade
|
|
|
125
140
|
self
|
|
126
141
|
end
|
|
127
142
|
|
|
143
|
+
# @return [self]
|
|
128
144
|
def with_binding(binding)
|
|
129
145
|
self.lambda_binding = binding
|
|
130
146
|
self
|
|
131
147
|
end
|
|
132
148
|
|
|
149
|
+
# @param [RenderBinding] binding
|
|
150
|
+
# @return [self]
|
|
151
|
+
def with_render_binding(binding)
|
|
152
|
+
self.lambda_binding = nil
|
|
153
|
+
self.render_binding = binding
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
133
157
|
def optimized
|
|
134
158
|
self.optimize = true
|
|
135
159
|
self
|
|
@@ -173,10 +197,12 @@ module Bade
|
|
|
173
197
|
# @return [Proc]
|
|
174
198
|
#
|
|
175
199
|
def lambda_instance
|
|
176
|
-
|
|
177
|
-
lambda_binding
|
|
178
|
-
|
|
179
|
-
|
|
200
|
+
_catching_globals do
|
|
201
|
+
if lambda_binding
|
|
202
|
+
lambda_binding.eval(lambda_string, file_path || Bade::Runtime::TEMPLATE_FILE_NAME)
|
|
203
|
+
else
|
|
204
|
+
render_binding.instance_eval(lambda_string, file_path || Bade::Runtime::TEMPLATE_FILE_NAME)
|
|
205
|
+
end
|
|
180
206
|
end
|
|
181
207
|
end
|
|
182
208
|
|
|
@@ -197,13 +223,22 @@ module Bade
|
|
|
197
223
|
Generator::NEW_LINE_NAME.to_sym => new_line,
|
|
198
224
|
Generator::BASE_INDENT_NAME.to_sym => indent,
|
|
199
225
|
}
|
|
200
|
-
run_vars.
|
|
226
|
+
run_vars.compact! # remove nil values
|
|
201
227
|
|
|
202
|
-
|
|
228
|
+
begin
|
|
229
|
+
return _catching_globals do
|
|
230
|
+
lambda_instance.call(**run_vars)
|
|
231
|
+
end
|
|
232
|
+
rescue Bade::Runtime::RuntimeError => e
|
|
233
|
+
raise e
|
|
234
|
+
rescue Exception => e
|
|
235
|
+
msg = "Exception raised during execution of template: #{e}"
|
|
236
|
+
raise Bade::Runtime::RuntimeError.wrap_existing_error(msg, e, render_binding.__location_stack)
|
|
237
|
+
ensure
|
|
238
|
+
self.class._globals_tracker.clear_constants if @clear_constants
|
|
239
|
+
end
|
|
203
240
|
end
|
|
204
241
|
|
|
205
|
-
|
|
206
|
-
|
|
207
242
|
private
|
|
208
243
|
|
|
209
244
|
# @param [String] content source code of the template
|
|
@@ -275,5 +310,16 @@ module Bade
|
|
|
275
310
|
end
|
|
276
311
|
end
|
|
277
312
|
end
|
|
313
|
+
|
|
314
|
+
def _catching_globals(&block)
|
|
315
|
+
if @clear_constants
|
|
316
|
+
self.class._globals_tracker.catch(&block)
|
|
317
|
+
else
|
|
318
|
+
block.call
|
|
319
|
+
end
|
|
320
|
+
end
|
|
278
321
|
end
|
|
279
322
|
end
|
|
323
|
+
|
|
324
|
+
# Set default value to clear_constants
|
|
325
|
+
Bade::Renderer.clear_constants = false
|
data/lib/bade/ruby2_keywords.rb
CHANGED
|
@@ -83,9 +83,10 @@ class String
|
|
|
83
83
|
count = 0
|
|
84
84
|
|
|
85
85
|
each_char do |char|
|
|
86
|
-
|
|
86
|
+
case char
|
|
87
|
+
when SPACE_CHAR
|
|
87
88
|
count += 1
|
|
88
|
-
|
|
89
|
+
when TAB_CHAR
|
|
89
90
|
count += tabsize
|
|
90
91
|
else
|
|
91
92
|
break
|
|
@@ -100,7 +101,7 @@ class String
|
|
|
100
101
|
#
|
|
101
102
|
def strip_heredoc
|
|
102
103
|
min_val = scan(/^[ \t]*(?=\S)/).min
|
|
103
|
-
indent =
|
|
104
|
+
indent = min_val&.size || 0
|
|
104
105
|
gsub(/^[ \t]{#{indent}}/, '')
|
|
105
106
|
end
|
|
106
107
|
end
|
data/lib/bade/runtime/block.rb
CHANGED
|
@@ -4,8 +4,6 @@ require_relative '../ruby2_keywords'
|
|
|
4
4
|
|
|
5
5
|
module Bade
|
|
6
6
|
module Runtime
|
|
7
|
-
class RuntimeError < ::StandardError; end
|
|
8
|
-
|
|
9
7
|
class Block
|
|
10
8
|
class MissingBlockDefinitionError < RuntimeError
|
|
11
9
|
# @return [String]
|
|
@@ -16,8 +14,8 @@ module Bade
|
|
|
16
14
|
#
|
|
17
15
|
attr_accessor :context
|
|
18
16
|
|
|
19
|
-
def initialize(name, context, msg
|
|
20
|
-
super()
|
|
17
|
+
def initialize(name, context, msg, template_backtrace)
|
|
18
|
+
super(msg, template_backtrace)
|
|
21
19
|
|
|
22
20
|
self.name = name
|
|
23
21
|
self.context = context
|
|
@@ -38,34 +36,51 @@ module Bade
|
|
|
38
36
|
#
|
|
39
37
|
attr_reader :name
|
|
40
38
|
|
|
39
|
+
# @return [RenderBinding::Location, nil]
|
|
40
|
+
#
|
|
41
|
+
attr_reader :location
|
|
42
|
+
|
|
41
43
|
# @return [RenderBinding]
|
|
42
44
|
#
|
|
43
45
|
attr_reader :render_binding
|
|
44
46
|
|
|
45
47
|
# @param [String] name name of the block
|
|
48
|
+
# @param [RenderBinding::Location, nil] location
|
|
46
49
|
# @param [RenderBinding] render_binding reference to current binding instance
|
|
47
50
|
# @param [Proc] block reference to lambda
|
|
48
51
|
#
|
|
49
|
-
def initialize(name, render_binding, &block)
|
|
52
|
+
def initialize(name, location, render_binding, &block)
|
|
50
53
|
@name = name
|
|
54
|
+
@location = location
|
|
51
55
|
@render_binding = render_binding
|
|
52
56
|
@block = block
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
# --- Calling methods
|
|
56
60
|
|
|
61
|
+
# Calls the block and adds rendered content into current buffer stack.
|
|
62
|
+
#
|
|
63
|
+
# @return [Void]
|
|
57
64
|
ruby2_keywords def call(*args)
|
|
58
65
|
call!(*args) unless @block.nil?
|
|
59
66
|
end
|
|
60
67
|
|
|
68
|
+
# Calls the block and adds rendered content into current buffer stack.
|
|
69
|
+
#
|
|
70
|
+
# @return [Void]
|
|
61
71
|
ruby2_keywords def call!(*args)
|
|
62
|
-
raise MissingBlockDefinitionError.new(name, :call) if @block.nil?
|
|
72
|
+
raise MissingBlockDefinitionError.new(name, :call, nil, render_binding.__location_stack) if @block.nil?
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
__call(*args)
|
|
65
75
|
end
|
|
66
76
|
|
|
67
77
|
# --- Rendering methods
|
|
68
78
|
|
|
79
|
+
# Calls the block and returns rendered content in string.
|
|
80
|
+
#
|
|
81
|
+
# Returns empty string when there is no block.
|
|
82
|
+
#
|
|
83
|
+
# @return [String]
|
|
69
84
|
def render(*args)
|
|
70
85
|
if @block.nil?
|
|
71
86
|
''
|
|
@@ -74,10 +89,33 @@ module Bade
|
|
|
74
89
|
end
|
|
75
90
|
end
|
|
76
91
|
|
|
92
|
+
# Calls the block and returns rendered content in string.
|
|
93
|
+
#
|
|
94
|
+
# Throws error when there is no block.
|
|
95
|
+
#
|
|
96
|
+
# @return [String]
|
|
77
97
|
def render!(*args)
|
|
78
|
-
raise MissingBlockDefinitionError.new(name, :render) if @block.nil?
|
|
98
|
+
raise MissingBlockDefinitionError.new(name, :render, nil, render_binding.__location_stack) if @block.nil?
|
|
99
|
+
|
|
100
|
+
loc = location.dup
|
|
101
|
+
render_binding.__buffs_push(loc)
|
|
102
|
+
|
|
103
|
+
@block.call(*args)
|
|
104
|
+
|
|
105
|
+
render_binding.__buffs_pop&.join || ''
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Calls the block and adds rendered content into current buffer stack.
|
|
109
|
+
#
|
|
110
|
+
# @return [Void]
|
|
111
|
+
ruby2_keywords def __call(*args)
|
|
112
|
+
loc = location.dup
|
|
113
|
+
render_binding.__buffs_push(loc)
|
|
114
|
+
|
|
115
|
+
@block.call(*args)
|
|
79
116
|
|
|
80
|
-
|
|
117
|
+
res = render_binding.__buffs_pop
|
|
118
|
+
render_binding.__buff&.concat(res) if !res.nil? && !res.empty?
|
|
81
119
|
end
|
|
82
120
|
end
|
|
83
121
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bade
|
|
4
|
+
module Runtime
|
|
5
|
+
# Tracks created global variables and constants in block.
|
|
6
|
+
class GlobalsTracker
|
|
7
|
+
# @return [Array<Symbol>]
|
|
8
|
+
attr_accessor :caught_variables
|
|
9
|
+
|
|
10
|
+
# @return [Array<[Object, :Symbol]>]
|
|
11
|
+
attr_accessor :caught_constants
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@caught_variables = []
|
|
15
|
+
@caught_constants = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @yieldreturn [T]
|
|
19
|
+
# @return [T]
|
|
20
|
+
def catch
|
|
21
|
+
before_variables = global_variables
|
|
22
|
+
before_global_constants = Object.constants
|
|
23
|
+
before_binding_constants = Bade::Runtime::RenderBinding.constants(false)
|
|
24
|
+
|
|
25
|
+
res = nil
|
|
26
|
+
begin
|
|
27
|
+
res = yield
|
|
28
|
+
ensure
|
|
29
|
+
@caught_variables += global_variables - before_variables
|
|
30
|
+
|
|
31
|
+
@caught_constants += (Object.constants - before_global_constants)
|
|
32
|
+
.map { |name| [Object, name] }
|
|
33
|
+
@caught_constants += (Bade::Runtime::RenderBinding.constants(false) - before_binding_constants)
|
|
34
|
+
.map { |name| [Bade::Runtime::RenderBinding, name] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
res
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clear_all
|
|
41
|
+
clear_global_variables
|
|
42
|
+
clear_constants
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def clear_constants
|
|
46
|
+
@caught_constants.each do |(obj, name)|
|
|
47
|
+
obj.send(:remove_const, name) if obj.const_defined?(name)
|
|
48
|
+
end
|
|
49
|
+
@caught_constants = []
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def clear_global_variables
|
|
53
|
+
@caught_variables.each do |name|
|
|
54
|
+
eval("#{name} = nil", binding, __FILE__, __LINE__)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/bade/runtime/mixin.rb
CHANGED
|
@@ -9,8 +9,8 @@ module Bade
|
|
|
9
9
|
class Mixin < Block
|
|
10
10
|
ruby2_keywords def call!(blocks, *args)
|
|
11
11
|
begin
|
|
12
|
-
|
|
13
|
-
rescue ArgumentError => e
|
|
12
|
+
__call(blocks, *args)
|
|
13
|
+
rescue ::ArgumentError => e
|
|
14
14
|
case e.message
|
|
15
15
|
when /\Awrong number of arguments \(given ([0-9]+), expected ([0-9]+)\)\Z/,
|
|
16
16
|
/\Awrong number of arguments \(([0-9]+) for ([0-9]+)\)\Z/
|
|
@@ -19,12 +19,19 @@ module Bade
|
|
|
19
19
|
# minus one, because first argument is always hash of blocks
|
|
20
20
|
given = $1.to_i - 1
|
|
21
21
|
expected = $2.to_i - 1
|
|
22
|
-
|
|
22
|
+
msg = "wrong number of arguments (given #{given}, expected #{expected}) for mixin `#{name}`"
|
|
23
|
+
raise Bade::Runtime::ArgumentError.new(msg, render_binding.__location_stack)
|
|
23
24
|
|
|
24
25
|
when /\Aunknown keyword: (.*)\Z/
|
|
25
26
|
# handle unknown key-value parameter
|
|
26
27
|
key_name = $1
|
|
27
|
-
|
|
28
|
+
msg = "unknown key-value argument `#{key_name}` for mixin `#{name}`"
|
|
29
|
+
raise Bade::Runtime::ArgumentError.new(msg, render_binding.__location_stack)
|
|
30
|
+
|
|
31
|
+
when /\Amissing keyword: :?(.*)\Z/
|
|
32
|
+
key_name = $1
|
|
33
|
+
msg = "missing value for required key-value argument `#{key_name}` for mixin `#{name}`"
|
|
34
|
+
raise Bade::Runtime::ArgumentError.new(msg, render_binding.__location_stack)
|
|
28
35
|
|
|
29
36
|
else
|
|
30
37
|
raise
|
|
@@ -36,10 +43,15 @@ module Bade
|
|
|
36
43
|
when :render
|
|
37
44
|
"Mixin `#{name}` requires block to get rendered content of block `#{e.name}`"
|
|
38
45
|
else
|
|
39
|
-
raise ::ArgumentError
|
|
46
|
+
raise Bade::Runtime::ArgumentError.new("Unknown context #{e.context} of error #{e}!",
|
|
47
|
+
render_binding.__location_stack)
|
|
40
48
|
end
|
|
41
49
|
|
|
42
|
-
raise Block::MissingBlockDefinitionError.new(e.name, e.context, msg)
|
|
50
|
+
raise Block::MissingBlockDefinitionError.new(e.name, e.context, msg, render_binding.__location_stack)
|
|
51
|
+
|
|
52
|
+
rescue Exception => e
|
|
53
|
+
msg = "Exception raised during execution of mixin `#{name}`: #{e}"
|
|
54
|
+
raise Bade::Runtime::RuntimeError.wrap_existing_error(msg, e, render_binding.__location_stack)
|
|
43
55
|
end
|
|
44
56
|
end
|
|
45
57
|
end
|
|
@@ -5,12 +5,15 @@ require_relative 'block'
|
|
|
5
5
|
module Bade
|
|
6
6
|
module Runtime
|
|
7
7
|
class RenderBinding
|
|
8
|
-
|
|
8
|
+
Location = Bade::Runtime::Location
|
|
9
9
|
|
|
10
10
|
# @return [Array<Array<String>>]
|
|
11
11
|
#
|
|
12
12
|
attr_accessor :__buffs_stack
|
|
13
13
|
|
|
14
|
+
# @return [Array<Location>]
|
|
15
|
+
attr_accessor :__location_stack
|
|
16
|
+
|
|
14
17
|
# @return [Hash<String, Mixin>]
|
|
15
18
|
#
|
|
16
19
|
attr_accessor :__mixins
|
|
@@ -34,13 +37,14 @@ module Bade
|
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
# Resets this binding to default state, this method should be
|
|
40
|
+
# Resets this binding to default state, this method should be evoked after running the template lambda
|
|
38
41
|
#
|
|
39
42
|
# @return [nil]
|
|
40
43
|
#
|
|
41
44
|
def __reset
|
|
42
|
-
@__buffs_stack = [
|
|
43
|
-
@
|
|
45
|
+
@__buffs_stack = []
|
|
46
|
+
@__location_stack = []
|
|
47
|
+
@__mixins = Hash.new { |_hash, key| raise Bade::Runtime::KeyError.new("Undefined mixin '#{key}'", __location_stack) }
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
# @return [Binding]
|
|
@@ -51,26 +55,56 @@ module Bade
|
|
|
51
55
|
|
|
52
56
|
# Shortcut for creating blocks
|
|
53
57
|
#
|
|
54
|
-
def __create_block(name, &block)
|
|
55
|
-
Bade::Runtime::Block.new(name, self, &block)
|
|
58
|
+
def __create_block(name, location = nil, &block)
|
|
59
|
+
Bade::Runtime::Block.new(name, location, self, &block)
|
|
56
60
|
end
|
|
57
61
|
|
|
58
|
-
def __create_mixin(name, &block)
|
|
59
|
-
Bade::Runtime::Mixin.new(name, self, &block)
|
|
62
|
+
def __create_mixin(name, location, &block)
|
|
63
|
+
Bade::Runtime::Mixin.new(name, location, self, &block)
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
# --- Methods for dealing with pushing and
|
|
66
|
+
# --- Methods for dealing with pushing and popping buffers in stack
|
|
63
67
|
|
|
64
68
|
def __buff
|
|
65
|
-
__buffs_stack.
|
|
69
|
+
__buffs_stack.first
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
# @param [RenderBinding::Location, nil] location
|
|
73
|
+
def __buffs_push(location)
|
|
74
|
+
__buffs_stack.unshift([])
|
|
75
|
+
__location_stack.unshift(location) unless location.nil?
|
|
70
76
|
end
|
|
71
77
|
|
|
78
|
+
# @return [Array<String>, nil]
|
|
72
79
|
def __buffs_pop
|
|
73
|
-
|
|
80
|
+
__location_stack.shift
|
|
81
|
+
__buffs_stack.shift
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# --- Other internal methods
|
|
85
|
+
|
|
86
|
+
# @param [String] filename
|
|
87
|
+
def __load(filename)
|
|
88
|
+
# FakeFS does not fake `load` method
|
|
89
|
+
if defined?(:FakeFS) && FakeFS.activated?
|
|
90
|
+
# rubocop:disable Security/Eval
|
|
91
|
+
eval(File.read(filename), __get_binding, filename)
|
|
92
|
+
# rubocop:enable Security/Eval
|
|
93
|
+
else
|
|
94
|
+
load(filename)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param [String] filename
|
|
99
|
+
def require_relative(filename)
|
|
100
|
+
# FakeFS does not fake `require_relative` method
|
|
101
|
+
if defined?(:FakeFS) && FakeFS.activated?
|
|
102
|
+
# rubocop:disable Security/Eval
|
|
103
|
+
eval(File.read(filename), __get_binding, filename)
|
|
104
|
+
# rubocop:enable Security/Eval
|
|
105
|
+
else
|
|
106
|
+
Kernel.require_relative(filename)
|
|
107
|
+
end
|
|
74
108
|
end
|
|
75
109
|
|
|
76
110
|
# Escape input text with html escapes
|
|
@@ -94,6 +128,15 @@ module Bade
|
|
|
94
128
|
|
|
95
129
|
%( #{name}="#{values.join(' ')}")
|
|
96
130
|
end
|
|
131
|
+
|
|
132
|
+
def __update_lineno(number)
|
|
133
|
+
__location_stack.first&.lineno = number
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @return [Location, nil]
|
|
137
|
+
def __current_location
|
|
138
|
+
__location_stack.first
|
|
139
|
+
end
|
|
97
140
|
end
|
|
98
141
|
end
|
|
99
142
|
end
|
data/lib/bade/runtime.rb
CHANGED
|
@@ -1,9 +1,87 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Bade
|
|
4
4
|
module Runtime
|
|
5
|
+
Location = Struct.new(:path, :lineno, :label, keyword_init: true) do
|
|
6
|
+
def to_s
|
|
7
|
+
"#{path || TEMPLATE_FILE_NAME}:#{lineno}:in `#{label}'"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class RuntimeError < ::StandardError
|
|
12
|
+
# @return [Array<Location>]
|
|
13
|
+
#
|
|
14
|
+
attr_reader :template_backtrace
|
|
15
|
+
|
|
16
|
+
# @param [String] msg
|
|
17
|
+
# @param [Array<Location>] template_backtrace
|
|
18
|
+
# @param [Exception, nil] original
|
|
19
|
+
def initialize(msg, template_backtrace = [], original: nil)
|
|
20
|
+
super(msg)
|
|
21
|
+
@template_backtrace = template_backtrace
|
|
22
|
+
@original = original
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def message
|
|
26
|
+
if @template_backtrace.empty?
|
|
27
|
+
super
|
|
28
|
+
else
|
|
29
|
+
<<~MSG.rstrip
|
|
30
|
+
#{super}
|
|
31
|
+
template backtrace:
|
|
32
|
+
#{__formatted_backtrace.join("\n")}
|
|
33
|
+
MSG
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def cause
|
|
38
|
+
@original
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Array<String>]
|
|
42
|
+
def __formatted_backtrace
|
|
43
|
+
bt = @template_backtrace
|
|
44
|
+
|
|
45
|
+
# delete first location if is same as second (can happen when arguments are incorrect)
|
|
46
|
+
last = bt.first
|
|
47
|
+
bt.delete_at(0) if last && bt.length > 1 && last == bt[1]
|
|
48
|
+
|
|
49
|
+
bt.map { |loc| " #{loc}" }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param [Array<Thread::Backtrace::Location>, nil] locations
|
|
53
|
+
def self.process_locations(locations)
|
|
54
|
+
return [] if locations.nil?
|
|
55
|
+
|
|
56
|
+
index = locations&.find_index { |loc| loc.path == TEMPLATE_FILE_NAME || loc.path&.include?('.bade') }
|
|
57
|
+
return [] if index.nil?
|
|
58
|
+
|
|
59
|
+
new_locations = locations[0...index] || []
|
|
60
|
+
|
|
61
|
+
new_locations.map do |loc|
|
|
62
|
+
Location.new(path: loc.path, lineno: loc.lineno, label: loc.label)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param [String] msg
|
|
67
|
+
# @param [Exception] error
|
|
68
|
+
# @param [Array<Location>] template_backtrace
|
|
69
|
+
# @return [RuntimeError]
|
|
70
|
+
def self.wrap_existing_error(msg, error, template_backtrace)
|
|
71
|
+
locs = Bade::Runtime::RuntimeError.process_locations(error.backtrace_locations) + template_backtrace
|
|
72
|
+
Bade::Runtime::RuntimeError.new(msg, locs, original: error)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class KeyError < RuntimeError; end
|
|
77
|
+
|
|
78
|
+
class ArgumentError < RuntimeError; end
|
|
79
|
+
|
|
80
|
+
TEMPLATE_FILE_NAME = '(__template__)'.freeze
|
|
81
|
+
|
|
5
82
|
require_relative 'runtime/block'
|
|
6
83
|
require_relative 'runtime/mixin'
|
|
7
84
|
require_relative 'runtime/render_binding'
|
|
85
|
+
require_relative 'runtime/globals_tracker'
|
|
8
86
|
end
|
|
9
87
|
end
|
data/lib/bade/version.rb
CHANGED
data/lib/bade.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bade
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Kříž
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: psych
|
|
@@ -19,7 +19,7 @@ dependencies:
|
|
|
19
19
|
version: '2.2'
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '
|
|
22
|
+
version: '5.0'
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -29,21 +29,21 @@ dependencies:
|
|
|
29
29
|
version: '2.2'
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '5.0'
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
|
34
|
-
name:
|
|
34
|
+
name: fakefs
|
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '1.3'
|
|
40
40
|
type: :development
|
|
41
41
|
prerelease: false
|
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '1.3'
|
|
47
47
|
- !ruby/object:Gem::Dependency
|
|
48
48
|
name: rspec
|
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -64,15 +64,15 @@ dependencies:
|
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
67
|
+
version: '1.14'
|
|
68
68
|
type: :development
|
|
69
69
|
prerelease: false
|
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version:
|
|
75
|
-
description:
|
|
74
|
+
version: '1.14'
|
|
75
|
+
description:
|
|
76
76
|
email:
|
|
77
77
|
- samnung@gmail.com
|
|
78
78
|
executables: []
|
|
@@ -109,14 +109,16 @@ files:
|
|
|
109
109
|
- lib/bade/ruby_extensions/string.rb
|
|
110
110
|
- lib/bade/runtime.rb
|
|
111
111
|
- lib/bade/runtime/block.rb
|
|
112
|
+
- lib/bade/runtime/globals_tracker.rb
|
|
112
113
|
- lib/bade/runtime/mixin.rb
|
|
113
114
|
- lib/bade/runtime/render_binding.rb
|
|
114
115
|
- lib/bade/version.rb
|
|
115
116
|
homepage: https://github.com/epuber-io/bade
|
|
116
117
|
licenses:
|
|
117
118
|
- MIT
|
|
118
|
-
metadata:
|
|
119
|
-
|
|
119
|
+
metadata:
|
|
120
|
+
rubygems_mfa_required: 'true'
|
|
121
|
+
post_install_message:
|
|
120
122
|
rdoc_options: []
|
|
121
123
|
require_paths:
|
|
122
124
|
- lib
|
|
@@ -124,15 +126,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
124
126
|
requirements:
|
|
125
127
|
- - ">="
|
|
126
128
|
- !ruby/object:Gem::Version
|
|
127
|
-
version: '2.
|
|
129
|
+
version: '2.5'
|
|
128
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
131
|
requirements:
|
|
130
132
|
- - ">="
|
|
131
133
|
- !ruby/object:Gem::Version
|
|
132
134
|
version: '0'
|
|
133
135
|
requirements: []
|
|
134
|
-
rubygems_version: 3.
|
|
135
|
-
signing_key:
|
|
136
|
+
rubygems_version: 3.3.8
|
|
137
|
+
signing_key:
|
|
136
138
|
specification_version: 4
|
|
137
139
|
summary: Minimalistic template engine for Ruby.
|
|
138
140
|
test_files: []
|