bade 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Bade.gemspec +16 -15
- data/lib/bade/ast/document.rb +1 -1
- data/lib/bade/ast/node/doctype_node.rb +6 -6
- data/lib/bade/ast/node/static_text_node.rb +27 -0
- data/lib/bade/ast/node_registrator.rb +3 -4
- data/lib/bade/ast/string_serializer.rb +4 -3
- data/lib/bade/generator.rb +94 -88
- data/lib/bade/parser.rb +28 -587
- data/lib/bade/parser/parser_constants.rb +18 -0
- data/lib/bade/parser/parser_lines.rb +213 -0
- data/lib/bade/parser/parser_mixin.rb +139 -0
- data/lib/bade/parser/parser_ruby_code.rb +77 -0
- data/lib/bade/parser/parser_tag.rb +116 -0
- data/lib/bade/parser/parser_text.rb +78 -0
- data/lib/bade/precompiled.rb +2 -2
- data/lib/bade/renderer.rb +17 -12
- data/lib/bade/ruby_extensions/array.rb +9 -13
- data/lib/bade/ruby_extensions/string.rb +15 -8
- data/lib/bade/runtime/block.rb +6 -10
- data/lib/bade/runtime/mixin.rb +2 -1
- data/lib/bade/runtime/render_binding.rb +5 -5
- data/lib/bade/version.rb +1 -1
- metadata +25 -4
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
module Bade
|
5
|
+
require_relative '../parser'
|
6
|
+
|
7
|
+
class Parser
|
8
|
+
module TagRegexps
|
9
|
+
BLOCK_EXPANSION = /\A:\s+/
|
10
|
+
OUTPUT_CODE = LineIndicatorRegexps::OUTPUT_BLOCK
|
11
|
+
TEXT_START = /\A /
|
12
|
+
|
13
|
+
PARAMS_ARGS_DELIMITER = /\A\s*,/
|
14
|
+
PARAMS_END = /\A\s*\)/
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [String] tag tag name
|
18
|
+
#
|
19
|
+
def parse_tag(tag)
|
20
|
+
tag = fixed_trailing_colon(tag)
|
21
|
+
|
22
|
+
if tag.is_a?(AST::Node)
|
23
|
+
tag_node = tag
|
24
|
+
else
|
25
|
+
tag_node = append_node(:tag, add: true)
|
26
|
+
tag_node.name = tag
|
27
|
+
end
|
28
|
+
|
29
|
+
parse_tag_attributes
|
30
|
+
|
31
|
+
case @line
|
32
|
+
when TagRegexps::BLOCK_EXPANSION
|
33
|
+
# Block expansion
|
34
|
+
@line = $'
|
35
|
+
parse_line_indicators(add_newline: false)
|
36
|
+
|
37
|
+
when TagRegexps::OUTPUT_CODE
|
38
|
+
# Handle output code
|
39
|
+
parse_line_indicators(add_newline: false)
|
40
|
+
|
41
|
+
when CLASS_TAG_RE
|
42
|
+
# Class name
|
43
|
+
@line = $'
|
44
|
+
|
45
|
+
attr_node = append_node(:tag_attr)
|
46
|
+
attr_node.name = 'class'
|
47
|
+
attr_node.value = fixed_trailing_colon($1).single_quote
|
48
|
+
|
49
|
+
parse_tag tag_node
|
50
|
+
|
51
|
+
when ID_TAG_RE
|
52
|
+
# Id name
|
53
|
+
@line = $'
|
54
|
+
|
55
|
+
attr_node = append_node(:tag_attr)
|
56
|
+
attr_node.name = 'id'
|
57
|
+
attr_node.value = fixed_trailing_colon($1).single_quote
|
58
|
+
|
59
|
+
parse_tag tag_node
|
60
|
+
|
61
|
+
when TagRegexps::TEXT_START
|
62
|
+
# Text content
|
63
|
+
@line = $'
|
64
|
+
parse_text
|
65
|
+
|
66
|
+
when ''
|
67
|
+
# nothing
|
68
|
+
|
69
|
+
else
|
70
|
+
syntax_error "Unknown symbol after tag definition #{@line}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_tag_attributes
|
75
|
+
# Check to see if there is a delimiter right after the tag name
|
76
|
+
|
77
|
+
# between tag name and attribute must not be space
|
78
|
+
# and skip when is nothing other
|
79
|
+
return unless @line.start_with?('(')
|
80
|
+
|
81
|
+
# remove starting bracket
|
82
|
+
@line.remove_first!
|
83
|
+
|
84
|
+
loop do
|
85
|
+
case @line
|
86
|
+
when CODE_ATTR_RE
|
87
|
+
# Value ruby code
|
88
|
+
@line = $'
|
89
|
+
attr_node = append_node(:tag_attr)
|
90
|
+
attr_node.name = $1
|
91
|
+
attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
|
92
|
+
|
93
|
+
when TagRegexps::PARAMS_ARGS_DELIMITER
|
94
|
+
# args delimiter
|
95
|
+
@line = $'
|
96
|
+
next
|
97
|
+
|
98
|
+
when TagRegexps::PARAMS_END
|
99
|
+
# Find ending delimiter
|
100
|
+
@line = $'
|
101
|
+
break
|
102
|
+
|
103
|
+
else
|
104
|
+
# Found something where an attribute should be
|
105
|
+
@line.lstrip!
|
106
|
+
syntax_error('Expected attribute') unless @line.empty?
|
107
|
+
|
108
|
+
# Attributes span multiple lines
|
109
|
+
append_node(:newline)
|
110
|
+
syntax_error('Expected closing tag attributes delimiter `)`') if @lines.empty?
|
111
|
+
next_line
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
module Bade
|
5
|
+
require_relative '../parser'
|
6
|
+
|
7
|
+
class Parser
|
8
|
+
module TextRegexps
|
9
|
+
INTERPOLATION_START = /(\\)?(&|#)\{/
|
10
|
+
INTERPOLATION_END = /\A\}/
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_text
|
14
|
+
new_index = @line.index(TextRegexps::INTERPOLATION_START)
|
15
|
+
|
16
|
+
# the interpolation sequence is not in text, mark whole text as static
|
17
|
+
if new_index.nil?
|
18
|
+
append_node(:static_text, value: @line)
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
unparsed_part = String.new
|
23
|
+
|
24
|
+
while (new_index = @line.index(TextRegexps::INTERPOLATION_START))
|
25
|
+
if $1.nil?
|
26
|
+
static_part = unparsed_part + @line.remove_first!(new_index)
|
27
|
+
append_node(:static_text, value: static_part)
|
28
|
+
|
29
|
+
@line.remove_first!(2) # #{ or &{
|
30
|
+
|
31
|
+
dynamic_part = parse_ruby_code(TextRegexps::INTERPOLATION_END)
|
32
|
+
node = append_node(:output, value: dynamic_part)
|
33
|
+
node.escaped = $2 == '&'
|
34
|
+
|
35
|
+
@line.remove_first! # ending }
|
36
|
+
|
37
|
+
unparsed_part = String.new
|
38
|
+
else
|
39
|
+
unparsed_part << @line.remove_first!(new_index)
|
40
|
+
@line.remove_first! # symbol \
|
41
|
+
unparsed_part << @line.remove_first!(2) # #{ or &{
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# add the rest of line
|
46
|
+
append_node(:static_text, value: unparsed_part + @line) unless @line.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_text_block(first_line, text_indent = nil)
|
50
|
+
if !first_line || first_line.empty?
|
51
|
+
text_indent = nil
|
52
|
+
else
|
53
|
+
@line = first_line
|
54
|
+
parse_text
|
55
|
+
end
|
56
|
+
|
57
|
+
until @lines.empty?
|
58
|
+
if @lines.first.blank?
|
59
|
+
next_line
|
60
|
+
append_node(:newline)
|
61
|
+
else
|
62
|
+
indent = get_indent(@lines.first)
|
63
|
+
break if indent <= @indents.last
|
64
|
+
|
65
|
+
next_line
|
66
|
+
|
67
|
+
@line.remove_indent!(text_indent ? text_indent : indent, @tabsize)
|
68
|
+
|
69
|
+
parse_text
|
70
|
+
|
71
|
+
# The indentation of first line of the text block
|
72
|
+
# determines the text base indentation.
|
73
|
+
text_indent ||= indent
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/bade/precompiled.rb
CHANGED
data/lib/bade/renderer.rb
CHANGED
@@ -22,14 +22,14 @@ 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=nil)
|
25
|
+
def initialize(loading_path, reference_path, msg = nil)
|
26
26
|
super(msg)
|
27
27
|
@loading_path = loading_path
|
28
28
|
@reference_path = reference_path
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
TEMPLATE_FILE_NAME = '(__template__)'
|
32
|
+
TEMPLATE_FILE_NAME = '(__template__)'.freeze
|
33
33
|
|
34
34
|
# @return [String]
|
35
35
|
#
|
@@ -167,7 +167,8 @@ module Bade
|
|
167
167
|
# ----------------------------------------------------------------------------- #
|
168
168
|
# Render
|
169
169
|
|
170
|
-
# @param [Binding] binding custom binding for evaluating the template, but it is not recommended to use,
|
170
|
+
# @param [Binding] binding custom binding for evaluating the template, but it is not recommended to use,
|
171
|
+
# use :locals and #with_locals instead
|
171
172
|
# @param [String] new_line newline string, default is \n
|
172
173
|
# @param [String] indent indent string, default is two spaces
|
173
174
|
#
|
@@ -177,8 +178,8 @@ module Bade
|
|
177
178
|
self.lambda_binding = binding unless binding.nil? # backward compatibility
|
178
179
|
|
179
180
|
run_vars = {
|
180
|
-
|
181
|
-
|
181
|
+
Generator::NEW_LINE_NAME.to_sym => new_line,
|
182
|
+
Generator::BASE_INDENT_NAME.to_sym => indent,
|
182
183
|
}
|
183
184
|
run_vars.reject! { |_key, value| value.nil? } # remove nil values
|
184
185
|
|
@@ -219,7 +220,8 @@ module Bade
|
|
219
220
|
document
|
220
221
|
end
|
221
222
|
|
222
|
-
# Tries to find file with name, if no file could be found or there are multiple files matching the name error is
|
223
|
+
# Tries to find file with name, if no file could be found or there are multiple files matching the name error is
|
224
|
+
# raised
|
223
225
|
#
|
224
226
|
# @param [String] name name of the file that should be found
|
225
227
|
# @param [String] reference_path path to file from which is loading/finding
|
@@ -229,25 +231,28 @@ module Bade
|
|
229
231
|
def _find_file!(name, reference_path)
|
230
232
|
sub_path = File.expand_path(name, File.dirname(reference_path))
|
231
233
|
|
232
|
-
if File.
|
234
|
+
if File.exist?(sub_path)
|
233
235
|
return if sub_path.end_with?('.rb') # handled in Generator
|
234
236
|
sub_path
|
235
237
|
else
|
236
238
|
bade_path = "#{sub_path}.bade"
|
237
239
|
rb_path = "#{sub_path}.rb"
|
238
240
|
|
239
|
-
bade_exist = File.
|
240
|
-
rb_exist = File.
|
241
|
-
relative = Pathname.new(reference_path).relative_path_from(Pathname.new(File.dirname(
|
241
|
+
bade_exist = File.exist?(bade_path)
|
242
|
+
rb_exist = File.exist?(rb_path)
|
243
|
+
relative = Pathname.new(reference_path).relative_path_from(Pathname.new(File.dirname(file_path))).to_s
|
242
244
|
|
243
245
|
if bade_exist && rb_exist
|
244
|
-
|
246
|
+
message = "Found both .bade and .rb files for `#{name}` in file #{relative}, "\
|
247
|
+
'change the import path so it references uniq file.'
|
248
|
+
raise LoadError.new(name, reference_path, message)
|
245
249
|
elsif bade_exist
|
246
250
|
return bade_path
|
247
251
|
elsif rb_exist
|
248
252
|
return # handled in Generator
|
249
253
|
else
|
250
|
-
|
254
|
+
message = "Can't find file matching name `#{name}` referenced from file #{relative}"
|
255
|
+
raise LoadError.new(name, reference_path, message)
|
251
256
|
end
|
252
257
|
end
|
253
258
|
end
|
@@ -7,19 +7,17 @@ class Array
|
|
7
7
|
#
|
8
8
|
# @return [Fixnum]
|
9
9
|
#
|
10
|
-
def rindex_last_matching
|
10
|
+
def rindex_last_matching
|
11
11
|
return nil if empty?
|
12
12
|
|
13
13
|
index = nil
|
14
14
|
|
15
15
|
current_index = count - 1
|
16
16
|
reverse_each do |item|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
break
|
22
|
-
end
|
17
|
+
break unless yield item
|
18
|
+
|
19
|
+
index = current_index
|
20
|
+
current_index -= 1
|
23
21
|
end
|
24
22
|
|
25
23
|
index
|
@@ -29,15 +27,13 @@ class Array
|
|
29
27
|
#
|
30
28
|
# @return [Fixnum] count of items
|
31
29
|
#
|
32
|
-
def rcount_matching
|
30
|
+
def rcount_matching
|
33
31
|
count = 0
|
34
32
|
|
35
33
|
reverse_each do |item|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
break
|
40
|
-
end
|
34
|
+
break unless yield item
|
35
|
+
|
36
|
+
count += 1
|
41
37
|
end
|
42
38
|
|
43
39
|
count
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :nodoc:
|
3
4
|
class String
|
4
|
-
SPACE_CHAR = ' '
|
5
|
-
TAB_CHAR = "\t"
|
5
|
+
SPACE_CHAR = ' '.freeze
|
6
|
+
TAB_CHAR = "\t".freeze
|
6
7
|
|
7
8
|
# Creates new string surrounded by single quotes
|
8
9
|
#
|
@@ -14,7 +15,7 @@ class String
|
|
14
15
|
|
15
16
|
|
16
17
|
def blank?
|
17
|
-
strip.
|
18
|
+
strip.empty?
|
18
19
|
end
|
19
20
|
|
20
21
|
|
@@ -37,16 +38,14 @@ class String
|
|
37
38
|
|
38
39
|
def __chars_count_for_indent(indent, tabsize)
|
39
40
|
count = 0
|
40
|
-
|
41
|
+
each_char do |char|
|
41
42
|
break if indent <= 0
|
42
43
|
|
43
44
|
case char
|
44
45
|
when SPACE_CHAR
|
45
46
|
indent -= 1
|
46
47
|
when TAB_CHAR
|
47
|
-
if indent - tabsize < 0
|
48
|
-
raise StandardError, 'malformed tabs'
|
49
|
-
end
|
48
|
+
raise StandardError, 'malformed tabs' if indent - tabsize < 0
|
50
49
|
|
51
50
|
indent -= tabsize
|
52
51
|
else
|
@@ -88,7 +87,7 @@ class String
|
|
88
87
|
def get_indent(tabsize)
|
89
88
|
count = 0
|
90
89
|
|
91
|
-
|
90
|
+
each_char do |char|
|
92
91
|
if char == SPACE_CHAR
|
93
92
|
count += 1
|
94
93
|
elsif char == TAB_CHAR
|
@@ -101,4 +100,12 @@ class String
|
|
101
100
|
count
|
102
101
|
end
|
103
102
|
|
103
|
+
# source: http://apidock.com/rails/String/strip_heredoc
|
104
|
+
# @return [String]
|
105
|
+
#
|
106
|
+
def strip_heredoc
|
107
|
+
min_val = scan(/^[ \t]*(?=\S)/).min
|
108
|
+
indent = (min_val && min_val.size) || 0
|
109
|
+
gsub(/^[ \t]{#{indent}}/, '')
|
110
|
+
end
|
104
111
|
end
|
data/lib/bade/runtime/block.rb
CHANGED
@@ -57,11 +57,9 @@ module Bade
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def call!(*args)
|
60
|
-
if @block.nil?
|
61
|
-
|
62
|
-
|
63
|
-
render_binding.__buff.concat(@block.call(*args))
|
64
|
-
end
|
60
|
+
raise MissingBlockDefinitionError.new(name, :call) if @block.nil?
|
61
|
+
|
62
|
+
render_binding.__buff.concat(@block.call(*args))
|
65
63
|
end
|
66
64
|
|
67
65
|
# --- Rendering methods
|
@@ -75,11 +73,9 @@ module Bade
|
|
75
73
|
end
|
76
74
|
|
77
75
|
def render!(*args)
|
78
|
-
if @block.nil?
|
79
|
-
|
80
|
-
|
81
|
-
@block.call(*args).join
|
82
|
-
end
|
76
|
+
raise MissingBlockDefinitionError.new(name, :render) if @block.nil?
|
77
|
+
|
78
|
+
@block.call(*args).join
|
83
79
|
end
|
84
80
|
end
|
85
81
|
end
|
data/lib/bade/runtime/mixin.rb
CHANGED
@@ -9,7 +9,8 @@ module Bade
|
|
9
9
|
block.call(blocks, *args)
|
10
10
|
rescue ArgumentError => e
|
11
11
|
case e.message
|
12
|
-
when /\Awrong number of arguments \(given ([0-9]+), expected ([0-9]+)\)\Z/,
|
12
|
+
when /\Awrong number of arguments \(given ([0-9]+), expected ([0-9]+)\)\Z/,
|
13
|
+
/\Awrong number of arguments \(([0-9]+) for ([0-9]+)\)\Z/
|
13
14
|
# handle incorrect parameters count
|
14
15
|
|
15
16
|
# minus one, because first argument is always hash of blocks
|
@@ -80,17 +80,17 @@ module Bade
|
|
80
80
|
# @return [String]
|
81
81
|
#
|
82
82
|
def __html_escaped(text)
|
83
|
-
text.
|
84
|
-
.
|
85
|
-
.
|
86
|
-
.
|
83
|
+
text.gsub('&', '&')
|
84
|
+
.gsub('<', '<')
|
85
|
+
.gsub('>', '>')
|
86
|
+
.gsub('"', '"')
|
87
87
|
end
|
88
88
|
|
89
89
|
def __tag_render_attribute(name, *values)
|
90
90
|
values = values.compact
|
91
91
|
return if values.empty?
|
92
92
|
|
93
|
-
%
|
93
|
+
%( #{name}="#{values.join(' ')}")
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|