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.
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module Bade
5
+ require_relative '../parser'
6
+
7
+ class Parser
8
+ WORD_RE = ''.respond_to?(:encoding) ? '\p{Word}' : '\w'
9
+ NAME_RE_STRING = "(#{WORD_RE}(?:#{WORD_RE}|:|-|_)*)".freeze
10
+
11
+ ATTR_NAME_RE_STRING = "\\A\\s*#{NAME_RE_STRING}".freeze
12
+ CODE_ATTR_RE = /#{ATTR_NAME_RE_STRING}\s*&?:\s*/
13
+
14
+ TAG_RE = /\A#{NAME_RE_STRING}/
15
+ CLASS_TAG_RE = /\A\.#{NAME_RE_STRING}/
16
+ ID_TAG_RE = /\A##{NAME_RE_STRING}/
17
+ end
18
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module Bade
5
+ require_relative '../parser'
6
+
7
+ class Parser
8
+ module LineIndicatorRegexps
9
+ IMPORT = /\Aimport /
10
+ MIXIN_DECL = /\Amixin #{NAME_RE_STRING}/
11
+ MIXIN_CALL = /\A\+#{NAME_RE_STRING}/
12
+ BLOCK_DECLARATION = /\Ablock #{NAME_RE_STRING}/
13
+ HTML_COMMENT = %r{\A//! }
14
+ NORMAL_COMMENT = %r{\A//}
15
+ TEXT_BLOCK_START = /\A\|( ?)/
16
+ INLINE_HTML = /\A</
17
+ CODE_BLOCK = /\A-/
18
+ OUTPUT_BLOCK = /\A(\??)(&?)=/
19
+ DOCTYPE = /\Adoctype\s/i
20
+ TAG_CLASS_START_BLOCK = /\A\./
21
+ TAG_ID_START_BLOCK = /\A#/
22
+ end
23
+
24
+ def reset(lines = nil, stacks = nil)
25
+ # Since you can indent however you like in Slim, we need to keep a list
26
+ # of how deeply indented you are. For instance, in a template like this:
27
+ #
28
+ # doctype # 0 spaces
29
+ # html # 0 spaces
30
+ # head # 1 space
31
+ # title # 4 spaces
32
+ #
33
+ # indents will then contain [0, 1, 4] (when it's processing the last line.)
34
+ #
35
+ # We uses this information to figure out how many steps we must "jump"
36
+ # out when we see an de-indented line.
37
+ @indents = [0]
38
+
39
+ # Whenever we want to output something, we'll *always* output it to the
40
+ # last stack in this array. So when there's a line that expects
41
+ # indentation, we simply push a new stack onto this array. When it
42
+ # processes the next line, the content will then be outputted into that
43
+ # stack.
44
+ @stacks = stacks
45
+
46
+ @lineno = 0
47
+ @lines = lines
48
+
49
+ # @return [String]
50
+ @line = @orig_line = nil
51
+ end
52
+
53
+ def next_line
54
+ if @lines.empty?
55
+ @orig_line = @line = nil
56
+
57
+ last_newlines = remove_last_newlines
58
+ @root.children += last_newlines
59
+
60
+ nil
61
+ else
62
+ @orig_line = @lines.shift
63
+ @lineno += 1
64
+ @line = @orig_line.dup
65
+ end
66
+ end
67
+
68
+ def parse_line
69
+ if @line.strip.empty?
70
+ append_node(:newline) unless @lines.empty?
71
+ return
72
+ end
73
+
74
+ indent = get_indent(@line)
75
+
76
+ # left strip
77
+ @line.remove_indent!(indent, @tabsize)
78
+
79
+ # If there's more stacks than indents, it means that the previous
80
+ # line is expecting this line to be indented.
81
+ expecting_indentation = @stacks.length > @indents.length
82
+
83
+ if indent > @indents.last
84
+ @indents << indent
85
+ else
86
+ # This line was *not* indented more than the line before,
87
+ # so we'll just forget about the stack that the previous line pushed.
88
+ if expecting_indentation
89
+ last_newlines = remove_last_newlines
90
+
91
+ @stacks.pop
92
+
93
+ new_node = @stacks.last.last
94
+ new_node.children += last_newlines
95
+ end
96
+
97
+ # This line was deindented.
98
+ # Now we're have to go through the all the indents and figure out
99
+ # how many levels we've deindented.
100
+ while indent < @indents.last
101
+ last_newlines = remove_last_newlines
102
+
103
+ @indents.pop
104
+ @stacks.pop
105
+
106
+ new_node = @stacks.last.last
107
+ new_node.children += last_newlines
108
+ end
109
+
110
+ # Remove old stacks we don't need
111
+ while !@stacks[indent].nil? && indent < @stacks[indent].length - 1
112
+ last_newlines = remove_last_newlines
113
+
114
+ @stacks[indent].pop
115
+
116
+ new_node = @stacks.last.last
117
+ new_node.children += last_newlines
118
+ end
119
+
120
+ # This line's indentation happens lie "between" two other line's
121
+ # indentation:
122
+ #
123
+ # hello
124
+ # world
125
+ # this # <- This should not be possible!
126
+ syntax_error('Malformed indentation') if indent != @indents.last
127
+ end
128
+
129
+ parse_line_indicators
130
+ end
131
+
132
+ def parse_line_indicators(add_newline: true)
133
+ case @line
134
+ when LineIndicatorRegexps::IMPORT
135
+ @line = $'
136
+ parse_import
137
+
138
+ when LineIndicatorRegexps::MIXIN_DECL
139
+ # Mixin declaration
140
+ @line = $'
141
+ parse_mixin_declaration($1)
142
+
143
+ when LineIndicatorRegexps::MIXIN_CALL
144
+ # Mixin call
145
+ @line = $'
146
+ parse_mixin_call($1)
147
+
148
+ when LineIndicatorRegexps::BLOCK_DECLARATION
149
+ @line = $'
150
+ if @stacks.last.last.type == :mixin_call
151
+ node = append_node(:mixin_block, add: true)
152
+ node.name = $1
153
+ else
154
+ # keyword block used outside of mixin call
155
+ parse_tag($&)
156
+ end
157
+
158
+ when LineIndicatorRegexps::HTML_COMMENT
159
+ # HTML comment
160
+ append_node(:html_comment, add: true)
161
+ parse_text_block $', @indents.last + @tabsize
162
+
163
+ when LineIndicatorRegexps::NORMAL_COMMENT
164
+ # Comment
165
+ append_node(:comment, add: true)
166
+ parse_text_block $', @indents.last + @tabsize
167
+
168
+ when LineIndicatorRegexps::TEXT_BLOCK_START
169
+ # Found a text block.
170
+ parse_text_block $', @indents.last + @tabsize
171
+
172
+ when LineIndicatorRegexps::INLINE_HTML
173
+ # Inline html
174
+ parse_text
175
+
176
+ when LineIndicatorRegexps::CODE_BLOCK
177
+ # Found a code block.
178
+ append_node(:code, value: $'.strip)
179
+
180
+ when LineIndicatorRegexps::OUTPUT_BLOCK
181
+ # Found an output block.
182
+ # We expect the line to be broken or the next line to be indented.
183
+ @line = $'
184
+ output_node = append_node(:output)
185
+ output_node.conditional = $1.length == 1
186
+ output_node.escaped = $2.length == 1
187
+ output_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_NEW_LINE)
188
+
189
+ when LineIndicatorRegexps::DOCTYPE
190
+ # Found doctype declaration
191
+ append_node(:doctype, value: $'.strip)
192
+
193
+ when TAG_RE
194
+ # Found a HTML tag.
195
+ @line = $' if $1
196
+ parse_tag($&)
197
+
198
+ when LineIndicatorRegexps::TAG_CLASS_START_BLOCK
199
+ # Found class name -> implicit div
200
+ parse_tag 'div'
201
+
202
+ when LineIndicatorRegexps::TAG_ID_START_BLOCK
203
+ # Found id name -> implicit div
204
+ parse_tag 'div'
205
+
206
+ else
207
+ syntax_error 'Unknown line indicator'
208
+ end
209
+
210
+ append_node(:newline) if add_newline && !@lines.empty?
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module Bade
5
+ require_relative '../parser'
6
+
7
+ class Parser
8
+ module MixinRegexps
9
+ TEXT_START = /\A /
10
+ BLOCK_EXPANSION = /\A:\s+/
11
+ OUTPUT_CODE = /\A(&?)=/
12
+
13
+ PARAMS_END = /\A\s*\)/
14
+
15
+ PARAMS_END_SPACES = /^\s*$/
16
+ PARAMS_ARGS_DELIMITER = /\A\s*,/
17
+
18
+ PARAMS_PARAM_NAME = /\A\s*#{NAME_RE_STRING}/
19
+ PARAMS_BLOCK_NAME = /\A\s*&#{NAME_RE_STRING}/
20
+ PARAMS_KEY_PARAM_NAME = CODE_ATTR_RE
21
+ end
22
+
23
+ def parse_mixin_call(mixin_name)
24
+ mixin_name = fixed_trailing_colon(mixin_name)
25
+
26
+ mixin_node = append_node(:mixin_call, add: true)
27
+ mixin_node.name = mixin_name
28
+
29
+ parse_mixin_call_params
30
+
31
+ case @line
32
+ when MixinRegexps::TEXT_START
33
+ @line = $'
34
+ parse_text
35
+
36
+ when MixinRegexps::BLOCK_EXPANSION
37
+ # Block expansion
38
+ @line = $'
39
+ parse_line_indicators(add_newline: false)
40
+
41
+ when MixinRegexps::OUTPUT_CODE
42
+ # Handle output code
43
+ parse_line_indicators(add_newline: false)
44
+
45
+ when ''
46
+ # nothing
47
+
48
+ else
49
+ syntax_error "Unknown symbol after mixin calling, line = `#{@line}'"
50
+ end
51
+ end
52
+
53
+ def parse_mixin_call_params
54
+ # between tag name and attribute must not be space
55
+ # and skip when is nothing other
56
+ return unless @line.start_with?('(')
57
+
58
+ # remove starting bracket
59
+ @line.remove_first!
60
+
61
+ loop do
62
+ case @line
63
+ when MixinRegexps::PARAMS_KEY_PARAM_NAME
64
+ @line = $'
65
+ attr_node = append_node(:mixin_key_param)
66
+ attr_node.name = fixed_trailing_colon($1)
67
+ attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
68
+
69
+ when MixinRegexps::PARAMS_ARGS_DELIMITER
70
+ # args delimiter
71
+ @line = $'
72
+ next
73
+
74
+ when MixinRegexps::PARAMS_END_SPACES
75
+ # spaces and/or end of line
76
+ next_line
77
+ next
78
+
79
+ when MixinRegexps::PARAMS_END
80
+ # Find ending delimiter
81
+ @line = $'
82
+ break
83
+
84
+ else
85
+ attr_node = append_node(:mixin_param)
86
+ attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
87
+ end
88
+ end
89
+ end
90
+
91
+ def parse_mixin_declaration(mixin_name)
92
+ mixin_node = append_node(:mixin_decl, add: true)
93
+ mixin_node.name = mixin_name
94
+
95
+ parse_mixin_declaration_params
96
+ end
97
+
98
+ def parse_mixin_declaration_params
99
+ # between tag name and attribute must not be space
100
+ # and skip when is nothing other
101
+ return unless @line.start_with?('(')
102
+
103
+ # remove starting bracket
104
+ @line.remove_first!
105
+
106
+ loop do
107
+ case @line
108
+ when MixinRegexps::PARAMS_KEY_PARAM_NAME
109
+ # Value ruby code
110
+ @line = $'
111
+ attr_node = append_node(:mixin_key_param)
112
+ attr_node.name = fixed_trailing_colon($1)
113
+ attr_node.value = parse_ruby_code(ParseRubyCodeRegexps::END_PARAMS_ARG)
114
+
115
+ when MixinRegexps::PARAMS_PARAM_NAME
116
+ @line = $'
117
+ append_node(:mixin_param, value: $1)
118
+
119
+ when MixinRegexps::PARAMS_BLOCK_NAME
120
+ @line = $'
121
+ append_node(:mixin_block_param, value: $1)
122
+
123
+ when MixinRegexps::PARAMS_ARGS_DELIMITER
124
+ # args delimiter
125
+ @line = $'
126
+ next
127
+
128
+ when MixinRegexps::PARAMS_END
129
+ # Find ending delimiter
130
+ @line = $'
131
+ break
132
+
133
+ else
134
+ syntax_error('wrong mixin attribute syntax')
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module Bade
5
+ require_relative '../parser'
6
+
7
+ class Parser
8
+ module ParseRubyCodeRegexps
9
+ END_NEW_LINE = /\A\s*\n/
10
+ END_PARAMS_ARG = /\A\s*[,)]/
11
+ end
12
+
13
+ # Parse ruby code, ended with outer delimiters
14
+ #
15
+ # @param [String, Regexp] outer_delimiters
16
+ #
17
+ # @return [Void] parsed ruby code
18
+ #
19
+ def parse_ruby_code(outer_delimiters)
20
+ code = String.new
21
+ end_re = if outer_delimiters.is_a?(Regexp)
22
+ outer_delimiters
23
+ else
24
+ /\A\s*[#{Regexp.escape outer_delimiters.to_s}]/
25
+ end
26
+ delimiters = []
27
+
28
+ until @line.empty? || (delimiters.count == 0 && @line =~ end_re)
29
+ char = @line[0]
30
+
31
+ # backslash escaped delimiter
32
+ if char == '\\' && RUBY_ALL_DELIMITERS.include?(@line[1])
33
+ code << @line.slice!(0, 2)
34
+ next
35
+ end
36
+
37
+ case char
38
+ when RUBY_START_DELIMITERS_RE
39
+ if RUBY_NOT_NESTABLE_DELIMITERS.include?(char) && delimiters.last == char
40
+ # end char of not nestable delimiter
41
+ delimiters.pop
42
+ else
43
+ # diving
44
+ delimiters << char
45
+ end
46
+
47
+ when RUBY_END_DELIMITERS_RE
48
+ # rising
49
+ delimiters.pop if char == RUBY_DELIMITERS_REVERSE[delimiters.last]
50
+ end
51
+
52
+ code << @line.slice!(0)
53
+ end
54
+
55
+ syntax_error('Unexpected end of ruby code') unless delimiters.empty?
56
+
57
+ code.strip
58
+ end
59
+
60
+ RUBY_DELIMITERS_REVERSE = {
61
+ '(' => ')',
62
+ '[' => ']',
63
+ '{' => '}',
64
+ }.freeze
65
+
66
+ RUBY_QUOTES = %w(' ").freeze
67
+
68
+ RUBY_NOT_NESTABLE_DELIMITERS = RUBY_QUOTES
69
+
70
+ RUBY_START_DELIMITERS = (%w(\( [ {) + RUBY_NOT_NESTABLE_DELIMITERS).freeze
71
+ RUBY_END_DELIMITERS = (%w(\) ] }) + RUBY_NOT_NESTABLE_DELIMITERS).freeze
72
+ RUBY_ALL_DELIMITERS = (RUBY_START_DELIMITERS + RUBY_END_DELIMITERS).uniq.freeze
73
+
74
+ RUBY_START_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_START_DELIMITERS.join}]/
75
+ RUBY_END_DELIMITERS_RE = /\A[#{Regexp.escape RUBY_END_DELIMITERS.join}]/
76
+ end
77
+ end