super-pro-kit 0.0.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 +7 -0
- data/slim-5.2.1/CHANGES +496 -0
- data/slim-5.2.1/Gemfile +43 -0
- data/slim-5.2.1/LICENSE +21 -0
- data/slim-5.2.1/README.jp.md +1276 -0
- data/slim-5.2.1/README.md +1313 -0
- data/slim-5.2.1/Rakefile +80 -0
- data/slim-5.2.1/bin/slimrb +6 -0
- data/slim-5.2.1/doc/include.md +20 -0
- data/slim-5.2.1/doc/jp/include.md +20 -0
- data/slim-5.2.1/doc/jp/logic_less.md +137 -0
- data/slim-5.2.1/doc/jp/smart.md +102 -0
- data/slim-5.2.1/doc/jp/translator.md +28 -0
- data/slim-5.2.1/doc/logic_less.md +137 -0
- data/slim-5.2.1/doc/smart.md +120 -0
- data/slim-5.2.1/doc/translator.md +28 -0
- data/slim-5.2.1/lib/slim/code_attributes.rb +68 -0
- data/slim-5.2.1/lib/slim/command.rb +122 -0
- data/slim-5.2.1/lib/slim/controls.rb +61 -0
- data/slim-5.2.1/lib/slim/do_inserter.rb +34 -0
- data/slim-5.2.1/lib/slim/embedded.rb +248 -0
- data/slim-5.2.1/lib/slim/end_inserter.rb +63 -0
- data/slim-5.2.1/lib/slim/engine.rb +42 -0
- data/slim-5.2.1/lib/slim/erb_converter.rb +15 -0
- data/slim-5.2.1/lib/slim/filter.rb +31 -0
- data/slim-5.2.1/lib/slim/grammar.rb +24 -0
- data/slim-5.2.1/lib/slim/include.rb +58 -0
- data/slim-5.2.1/lib/slim/interpolation.rb +36 -0
- data/slim-5.2.1/lib/slim/logic_less/context.rb +126 -0
- data/slim-5.2.1/lib/slim/logic_less/filter.rb +81 -0
- data/slim-5.2.1/lib/slim/logic_less.rb +6 -0
- data/slim-5.2.1/lib/slim/parser.rb +535 -0
- data/slim-5.2.1/lib/slim/railtie.rb +19 -0
- data/slim-5.2.1/lib/slim/smart/escaper.rb +42 -0
- data/slim-5.2.1/lib/slim/smart/filter.rb +97 -0
- data/slim-5.2.1/lib/slim/smart/parser.rb +35 -0
- data/slim-5.2.1/lib/slim/smart.rb +9 -0
- data/slim-5.2.1/lib/slim/splat/builder.rb +115 -0
- data/slim-5.2.1/lib/slim/splat/filter.rb +93 -0
- data/slim-5.2.1/lib/slim/template.rb +6 -0
- data/slim-5.2.1/lib/slim/translator.rb +119 -0
- data/slim-5.2.1/lib/slim/version.rb +6 -0
- data/slim-5.2.1/lib/slim.rb +16 -0
- data/slim-5.2.1/slim.gemspec +33 -0
- data/slim-5.2.1/test/core/helper.rb +201 -0
- data/slim-5.2.1/test/core/test_code_blocks.rb +194 -0
- data/slim-5.2.1/test/core/test_code_escaping.rb +162 -0
- data/slim-5.2.1/test/core/test_code_evaluation.rb +181 -0
- data/slim-5.2.1/test/core/test_code_output.rb +168 -0
- data/slim-5.2.1/test/core/test_code_structure.rb +203 -0
- data/slim-5.2.1/test/core/test_commands.rb +219 -0
- data/slim-5.2.1/test/core/test_embedded_engines.rb +247 -0
- data/slim-5.2.1/test/core/test_encoding.rb +28 -0
- data/slim-5.2.1/test/core/test_erb_converter.rb +65 -0
- data/slim-5.2.1/test/core/test_html_attributes.rb +276 -0
- data/slim-5.2.1/test/core/test_html_escaping.rb +65 -0
- data/slim-5.2.1/test/core/test_html_structure.rb +599 -0
- data/slim-5.2.1/test/core/test_parser_errors.rb +148 -0
- data/slim-5.2.1/test/core/test_pretty.rb +163 -0
- data/slim-5.2.1/test/core/test_ruby_errors.rb +200 -0
- data/slim-5.2.1/test/core/test_slim_template.rb +78 -0
- data/slim-5.2.1/test/core/test_splat_prefix_option.rb +155 -0
- data/slim-5.2.1/test/core/test_tabs.rb +169 -0
- data/slim-5.2.1/test/core/test_text_interpolation.rb +79 -0
- data/slim-5.2.1/test/core/test_thread_options.rb +18 -0
- data/slim-5.2.1/test/core/test_unicode.rb +15 -0
- data/slim-5.2.1/test/include/files/recursive.slim +1 -0
- data/slim-5.2.1/test/include/files/slimfile.slim +3 -0
- data/slim-5.2.1/test/include/files/subdir/test.slim +1 -0
- data/slim-5.2.1/test/include/files/textfile +1 -0
- data/slim-5.2.1/test/include/test_include.rb +24 -0
- data/slim-5.2.1/test/literate/TESTS.md +1323 -0
- data/slim-5.2.1/test/literate/helper.rb +15 -0
- data/slim-5.2.1/test/literate/run.rb +92 -0
- data/slim-5.2.1/test/logic_less/test_logic_less.rb +330 -0
- data/slim-5.2.1/test/rails/Rakefile +7 -0
- data/slim-5.2.1/test/rails/app/assets/config/manifest.js +1 -0
- data/slim-5.2.1/test/rails/app/controllers/application_controller.rb +2 -0
- data/slim-5.2.1/test/rails/app/controllers/entries_controller.rb +5 -0
- data/slim-5.2.1/test/rails/app/controllers/slim_controller.rb +46 -0
- data/slim-5.2.1/test/rails/app/helpers/application_helper.rb +5 -0
- data/slim-5.2.1/test/rails/app/models/entry.rb +16 -0
- data/slim-5.2.1/test/rails/app/views/entries/edit.html.slim +3 -0
- data/slim-5.2.1/test/rails/app/views/layouts/application.html+testvariant.slim +10 -0
- data/slim-5.2.1/test/rails/app/views/layouts/application.html.slim +10 -0
- data/slim-5.2.1/test/rails/app/views/slim/_partial.html.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/attributes.html.slim +3 -0
- data/slim-5.2.1/test/rails/app/views/slim/content_for.html.slim +7 -0
- data/slim-5.2.1/test/rails/app/views/slim/erb.html.erb +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/form_for.html.slim +2 -0
- data/slim-5.2.1/test/rails/app/views/slim/helper.html.slim +4 -0
- data/slim-5.2.1/test/rails/app/views/slim/integers.html.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/no_layout.html.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/normal.html.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/partial.html.slim +2 -0
- data/slim-5.2.1/test/rails/app/views/slim/splat.html.slim +2 -0
- data/slim-5.2.1/test/rails/app/views/slim/splat_with_delimiter.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/thread_options.html.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/variables.html.slim +1 -0
- data/slim-5.2.1/test/rails/app/views/slim/xml.slim +1 -0
- data/slim-5.2.1/test/rails/config/application.rb +44 -0
- data/slim-5.2.1/test/rails/config/boot.rb +10 -0
- data/slim-5.2.1/test/rails/config/environment.rb +5 -0
- data/slim-5.2.1/test/rails/config/environments/test.rb +34 -0
- data/slim-5.2.1/test/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/slim-5.2.1/test/rails/config/initializers/inflections.rb +10 -0
- data/slim-5.2.1/test/rails/config/initializers/mime_types.rb +5 -0
- data/slim-5.2.1/test/rails/config/initializers/session_store.rb +8 -0
- data/slim-5.2.1/test/rails/config/locales/en.yml +5 -0
- data/slim-5.2.1/test/rails/config/routes.rb +60 -0
- data/slim-5.2.1/test/rails/config.ru +4 -0
- data/slim-5.2.1/test/rails/script/rails +6 -0
- data/slim-5.2.1/test/rails/test/helper.rb +34 -0
- data/slim-5.2.1/test/rails/test/test_slim.rb +102 -0
- data/slim-5.2.1/test/sinatra/contest.rb +91 -0
- data/slim-5.2.1/test/sinatra/helper.rb +143 -0
- data/slim-5.2.1/test/sinatra/test_core.rb +91 -0
- data/slim-5.2.1/test/sinatra/test_include.rb +16 -0
- data/slim-5.2.1/test/sinatra/views/embed_include_js.slim +8 -0
- data/slim-5.2.1/test/sinatra/views/embed_js.slim +11 -0
- data/slim-5.2.1/test/sinatra/views/footer.slim +1 -0
- data/slim-5.2.1/test/sinatra/views/hello.slim +1 -0
- data/slim-5.2.1/test/sinatra/views/layout2.slim +3 -0
- data/slim-5.2.1/test/smart/test_smart_text.rb +294 -0
- data/slim-5.2.1/test/translator/test_translator.rb +76 -0
- data/super-pro-kit.gemspec +11 -0
- metadata +165 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Slim
|
|
3
|
+
# Parses Slim code and transforms it to a Temple expression
|
|
4
|
+
# @api private
|
|
5
|
+
class Parser < Temple::Parser
|
|
6
|
+
define_options :file,
|
|
7
|
+
:default_tag,
|
|
8
|
+
tabsize: 4,
|
|
9
|
+
code_attr_delims: {
|
|
10
|
+
'(' => ')',
|
|
11
|
+
'[' => ']',
|
|
12
|
+
'{' => '}',
|
|
13
|
+
},
|
|
14
|
+
attr_list_delims: {
|
|
15
|
+
'(' => ')',
|
|
16
|
+
'[' => ']',
|
|
17
|
+
'{' => '}',
|
|
18
|
+
},
|
|
19
|
+
shortcut: {
|
|
20
|
+
'#' => { attr: 'id' },
|
|
21
|
+
'.' => { attr: 'class' }
|
|
22
|
+
},
|
|
23
|
+
splat_prefix: '*'
|
|
24
|
+
|
|
25
|
+
class SyntaxError < StandardError
|
|
26
|
+
attr_reader :error, :file, :line, :lineno, :column
|
|
27
|
+
|
|
28
|
+
def initialize(error, file, line, lineno, column)
|
|
29
|
+
@error = error
|
|
30
|
+
@file = file || '(__TEMPLATE__)'
|
|
31
|
+
@line = line.to_s
|
|
32
|
+
@lineno = lineno
|
|
33
|
+
@column = column
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
line = @line.lstrip
|
|
38
|
+
column = @column + line.size - @line.size
|
|
39
|
+
%{#{error}
|
|
40
|
+
#{file}, Line #{lineno}, Column #{@column}
|
|
41
|
+
#{line}
|
|
42
|
+
#{' ' * column}^
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(opts = {})
|
|
48
|
+
super
|
|
49
|
+
@attr_list_delims = options[:attr_list_delims]
|
|
50
|
+
@code_attr_delims = options[:code_attr_delims]
|
|
51
|
+
tabsize = options[:tabsize]
|
|
52
|
+
if tabsize > 1
|
|
53
|
+
@tab_re = /\G((?: {#{tabsize}})*) {0,#{tabsize - 1}}\t/
|
|
54
|
+
@tab = '\1' + ' ' * tabsize
|
|
55
|
+
else
|
|
56
|
+
@tab_re = "\t"
|
|
57
|
+
@tab = ' '
|
|
58
|
+
end
|
|
59
|
+
@tag_shortcut, @attr_shortcut, @additional_attrs = {}, {}, {}
|
|
60
|
+
options[:shortcut].each do |k,v|
|
|
61
|
+
raise ArgumentError, 'Shortcut requires :tag and/or :attr' unless (v[:attr] || v[:tag]) && (v.keys - [:attr, :tag, :additional_attrs]).empty?
|
|
62
|
+
@tag_shortcut[k] = v[:tag] || options[:default_tag]
|
|
63
|
+
if v.include?(:attr) || v.include?(:additional_attrs)
|
|
64
|
+
raise ArgumentError, 'You can only use special characters for attribute shortcuts' if k =~ /(\p{Word}|-)/
|
|
65
|
+
end
|
|
66
|
+
if v.include?(:attr)
|
|
67
|
+
@attr_shortcut[k] = v[:attr].is_a?(Proc) ? v[:attr] : [v[:attr]].flatten
|
|
68
|
+
end
|
|
69
|
+
if v.include?(:additional_attrs)
|
|
70
|
+
@additional_attrs[k] = v[:additional_attrs]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
keys = Regexp.union @attr_shortcut.keys.sort_by { |k| -k.size }
|
|
74
|
+
@attr_shortcut_re = /\A(#{keys}+)((?:\p{Word}|-|\/\d+|:(\w|-)+)*)/
|
|
75
|
+
keys = Regexp.union @tag_shortcut.keys.sort_by { |k| -k.size }
|
|
76
|
+
@tag_re = /\A(?:#{keys}|\*(?=[^\s]+)|(\p{Word}(?:\p{Word}|:|-)*\p{Word}|\p{Word}+))/
|
|
77
|
+
keys = Regexp.escape @code_attr_delims.keys.join
|
|
78
|
+
@code_attr_delims_re = /\A[#{keys}]/
|
|
79
|
+
keys = Regexp.escape @attr_list_delims.keys.join
|
|
80
|
+
@attr_list_delims_re = /\A\s*([#{keys}])/
|
|
81
|
+
@embedded_re = /\A(#{Regexp.union(Embedded.engines.keys.map(&:to_s))})(?:\s*(?:(.*)))?:(\s*)/
|
|
82
|
+
keys = Regexp.escape ('"\'></='.split(//) + @attr_list_delims.flatten + @code_attr_delims.flatten).uniq.join
|
|
83
|
+
@attr_name = "\\A\\s*([^\\0\\s#{keys}]+)"
|
|
84
|
+
@quoted_attr_re = /#{@attr_name}\s*=(=?)\s*("|')/
|
|
85
|
+
@code_attr_re = /#{@attr_name}\s*=(=?)\s*/
|
|
86
|
+
|
|
87
|
+
splat_prefix = Regexp.escape(options[:splat_prefix])
|
|
88
|
+
splat_regexp_source = '\A\s*' + splat_prefix + '(?=[^\s]+)'
|
|
89
|
+
@splat_attrs_regexp = Regexp.new(splat_regexp_source)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Compile string to Temple expression
|
|
93
|
+
#
|
|
94
|
+
# @param [String] str Slim code
|
|
95
|
+
# @return [Array] Temple expression representing the code
|
|
96
|
+
def call(str)
|
|
97
|
+
result = [:multi]
|
|
98
|
+
reset(str.split(/\r?\n/), [result])
|
|
99
|
+
|
|
100
|
+
parse_line while next_line
|
|
101
|
+
|
|
102
|
+
reset
|
|
103
|
+
result
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
protected
|
|
107
|
+
|
|
108
|
+
def reset(lines = nil, stacks = nil)
|
|
109
|
+
# Since you can indent however you like in Slim, we need to keep a list
|
|
110
|
+
# of how deeply indented you are. For instance, in a template like this:
|
|
111
|
+
#
|
|
112
|
+
# doctype # 0 spaces
|
|
113
|
+
# html # 0 spaces
|
|
114
|
+
# head # 1 space
|
|
115
|
+
# title # 4 spaces
|
|
116
|
+
#
|
|
117
|
+
# indents will then contain [0, 1, 4] (when it's processing the last line.)
|
|
118
|
+
#
|
|
119
|
+
# We uses this information to figure out how many steps we must "jump"
|
|
120
|
+
# out when we see an de-indented line.
|
|
121
|
+
@indents = []
|
|
122
|
+
|
|
123
|
+
# Whenever we want to output something, we'll *always* output it to the
|
|
124
|
+
# last stack in this array. So when there's a line that expects
|
|
125
|
+
# indentation, we simply push a new stack onto this array. When it
|
|
126
|
+
# processes the next line, the content will then be outputted into that
|
|
127
|
+
# stack.
|
|
128
|
+
@stacks = stacks
|
|
129
|
+
|
|
130
|
+
@lineno = 0
|
|
131
|
+
@lines = lines
|
|
132
|
+
@line = @orig_line = nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def next_line
|
|
136
|
+
if @lines.empty?
|
|
137
|
+
@orig_line = @line = nil
|
|
138
|
+
else
|
|
139
|
+
@orig_line = @lines.shift
|
|
140
|
+
@lineno += 1
|
|
141
|
+
@line = @orig_line.dup
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def get_indent(line)
|
|
146
|
+
# Figure out the indentation. Kinda ugly/slow way to support tabs,
|
|
147
|
+
# but remember that this is only done at parsing time.
|
|
148
|
+
line[/\A[ \t]*/].gsub(@tab_re, @tab).size
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def parse_line
|
|
152
|
+
if @line =~ /\A\s*\Z/
|
|
153
|
+
@stacks.last << [:newline]
|
|
154
|
+
return
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
indent = get_indent(@line)
|
|
158
|
+
|
|
159
|
+
# Choose first indentation yourself
|
|
160
|
+
@indents << indent if @indents.empty?
|
|
161
|
+
|
|
162
|
+
# Remove the indentation
|
|
163
|
+
@line.lstrip!
|
|
164
|
+
|
|
165
|
+
# If there's more stacks than indents, it means that the previous
|
|
166
|
+
# line is expecting this line to be indented.
|
|
167
|
+
expecting_indentation = @stacks.size > @indents.size
|
|
168
|
+
|
|
169
|
+
if indent > @indents.last
|
|
170
|
+
# This line was actually indented, so we'll have to check if it was
|
|
171
|
+
# supposed to be indented or not.
|
|
172
|
+
syntax_error!('Unexpected indentation') unless expecting_indentation
|
|
173
|
+
|
|
174
|
+
@indents << indent
|
|
175
|
+
else
|
|
176
|
+
# This line was *not* indented more than the line before,
|
|
177
|
+
# so we'll just forget about the stack that the previous line pushed.
|
|
178
|
+
@stacks.pop if expecting_indentation
|
|
179
|
+
|
|
180
|
+
# This line was deindented.
|
|
181
|
+
# Now we're have to go through the all the indents and figure out
|
|
182
|
+
# how many levels we've deindented.
|
|
183
|
+
while indent < @indents.last && @indents.size > 1
|
|
184
|
+
@indents.pop
|
|
185
|
+
@stacks.pop
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# This line's indentation happens to lie "between" two other line's
|
|
189
|
+
# indentation:
|
|
190
|
+
#
|
|
191
|
+
# hello
|
|
192
|
+
# world
|
|
193
|
+
# this # <- This should not be possible!
|
|
194
|
+
syntax_error!('Malformed indentation') if indent != @indents.last
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
parse_line_indicators
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def parse_line_indicators
|
|
201
|
+
case @line
|
|
202
|
+
when /\A\/!( ?)/
|
|
203
|
+
# HTML comment
|
|
204
|
+
@stacks.last << [:html, :comment, [:slim, :text, :verbatim, parse_text_block($', @indents.last + $1.size + 2)]]
|
|
205
|
+
when /\A\/\[\s*(.*?)\s*\]\s*\Z/
|
|
206
|
+
# HTML conditional comment
|
|
207
|
+
block = [:multi]
|
|
208
|
+
@stacks.last << [:html, :condcomment, $1, block]
|
|
209
|
+
@stacks << block
|
|
210
|
+
when /\A\//
|
|
211
|
+
# Slim comment
|
|
212
|
+
parse_comment_block
|
|
213
|
+
when /\A([\|'])([<>]{1,2}(?: |\z)| ?)/
|
|
214
|
+
# Found verbatim text block.
|
|
215
|
+
leading_ws = $2.include?('<'.freeze)
|
|
216
|
+
trailing_ws = ($1 == "'") || $2.include?('>'.freeze)
|
|
217
|
+
@stacks.last << [:static, ' '] if leading_ws
|
|
218
|
+
@stacks.last << [:slim, :text, :verbatim, parse_text_block($', @indents.last + $2.count(' ') + 1)]
|
|
219
|
+
@stacks.last << [:static, ' '] if trailing_ws
|
|
220
|
+
when /\A</
|
|
221
|
+
# Inline html
|
|
222
|
+
block = [:multi]
|
|
223
|
+
@stacks.last << [:multi, [:slim, :interpolate, @line], block]
|
|
224
|
+
@stacks << block
|
|
225
|
+
when /\A-/
|
|
226
|
+
# Found a code block.
|
|
227
|
+
# We expect the line to be broken or the next line to be indented.
|
|
228
|
+
@line.slice!(0)
|
|
229
|
+
block = [:multi]
|
|
230
|
+
@stacks.last << [:slim, :control, parse_broken_line, block]
|
|
231
|
+
@stacks << block
|
|
232
|
+
when /\A=(=?)([<>]*)/
|
|
233
|
+
# Found an output block.
|
|
234
|
+
# We expect the line to be broken or the next line to be indented.
|
|
235
|
+
@line = $'
|
|
236
|
+
leading_ws = $2.include?('<'.freeze)
|
|
237
|
+
trailing_ws = $2.include?('>'.freeze)
|
|
238
|
+
block = [:multi]
|
|
239
|
+
@stacks.last << [:static, ' '] if leading_ws
|
|
240
|
+
@stacks.last << [:slim, :output, $1.empty?, parse_broken_line, block]
|
|
241
|
+
@stacks.last << [:static, ' '] if trailing_ws
|
|
242
|
+
@stacks << block
|
|
243
|
+
when @embedded_re
|
|
244
|
+
# Embedded template detected. It is treated as block.
|
|
245
|
+
@line = $2
|
|
246
|
+
attrs = parse_attributes
|
|
247
|
+
@stacks.last << [:slim, :embedded, $1, parse_text_block($', @orig_line.size - $'.size + $2.size), attrs]
|
|
248
|
+
when /\Adoctype\b/
|
|
249
|
+
# Found doctype declaration
|
|
250
|
+
@stacks.last << [:html, :doctype, $'.strip]
|
|
251
|
+
when @tag_re
|
|
252
|
+
# Found a HTML tag.
|
|
253
|
+
@line = $' if $1
|
|
254
|
+
parse_tag($&)
|
|
255
|
+
else
|
|
256
|
+
unknown_line_indicator
|
|
257
|
+
end
|
|
258
|
+
@stacks.last << [:newline]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Unknown line indicator found. Overwrite this method if
|
|
262
|
+
# you want to add line indicators to the Slim parser.
|
|
263
|
+
# The default implementation throws a syntax error.
|
|
264
|
+
def unknown_line_indicator
|
|
265
|
+
syntax_error! 'Unknown line indicator'
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def parse_comment_block
|
|
269
|
+
while !@lines.empty? && (@lines.first =~ /\A\s*\Z/ || get_indent(@lines.first) > @indents.last)
|
|
270
|
+
next_line
|
|
271
|
+
@stacks.last << [:newline]
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def parse_text_block(first_line = nil, text_indent = nil)
|
|
276
|
+
result = [:multi]
|
|
277
|
+
if !first_line || first_line.empty?
|
|
278
|
+
text_indent = nil
|
|
279
|
+
else
|
|
280
|
+
result << [:slim, :interpolate, first_line]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
empty_lines = 0
|
|
284
|
+
until @lines.empty?
|
|
285
|
+
if @lines.first =~ /\A\s*\Z/
|
|
286
|
+
next_line
|
|
287
|
+
result << [:newline]
|
|
288
|
+
empty_lines += 1 if text_indent
|
|
289
|
+
else
|
|
290
|
+
indent = get_indent(@lines.first)
|
|
291
|
+
break if indent <= @indents.last
|
|
292
|
+
|
|
293
|
+
if empty_lines > 0
|
|
294
|
+
result << [:slim, :interpolate, "\n" * empty_lines]
|
|
295
|
+
empty_lines = 0
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
next_line
|
|
299
|
+
@line.lstrip!
|
|
300
|
+
|
|
301
|
+
# The text block lines must be at least indented
|
|
302
|
+
# as deep as the first line.
|
|
303
|
+
offset = text_indent ? indent - text_indent : 0
|
|
304
|
+
if offset < 0
|
|
305
|
+
text_indent += offset
|
|
306
|
+
offset = 0
|
|
307
|
+
end
|
|
308
|
+
result << [:newline] << [:slim, :interpolate, (text_indent ? "\n" : '') + (' ' * offset) + @line]
|
|
309
|
+
|
|
310
|
+
# The indentation of first line of the text block
|
|
311
|
+
# determines the text base indentation.
|
|
312
|
+
text_indent ||= indent
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
result
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def parse_broken_line
|
|
319
|
+
broken_line = @line.strip
|
|
320
|
+
while broken_line =~ /[,\\]\Z/
|
|
321
|
+
expect_next_line
|
|
322
|
+
broken_line << "\n" << @line
|
|
323
|
+
end
|
|
324
|
+
broken_line
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def parse_tag(tag)
|
|
328
|
+
if @tag_shortcut[tag]
|
|
329
|
+
@line.slice!(0, tag.size) unless @attr_shortcut[tag]
|
|
330
|
+
tag = @tag_shortcut[tag]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Find any shortcut attributes
|
|
334
|
+
attributes = [:html, :attrs]
|
|
335
|
+
while @line =~ @attr_shortcut_re
|
|
336
|
+
# The class/id attribute is :static instead of :slim :interpolate,
|
|
337
|
+
# because we don't want text interpolation in .class or #id shortcut
|
|
338
|
+
syntax_error!('Illegal shortcut') unless shortcut = @attr_shortcut[$1]
|
|
339
|
+
|
|
340
|
+
if shortcut.is_a?(Proc)
|
|
341
|
+
shortcut.call($2).each { |a, v| attributes << [:html, :attr, a, [:static, v]] }
|
|
342
|
+
else
|
|
343
|
+
shortcut.each {|a| attributes << [:html, :attr, a, [:static, $2]] }
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
if additional_attr_pairs = @additional_attrs[$1]
|
|
347
|
+
additional_attr_pairs.each do |k,v|
|
|
348
|
+
attributes << [:html, :attr, k.to_s, [:static, v]]
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
@line = $'
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
@line =~ /\A[<>']*/
|
|
355
|
+
@line = $'
|
|
356
|
+
trailing_ws = $&.include?('>'.freeze)
|
|
357
|
+
leading_ws = $&.include?('<'.freeze)
|
|
358
|
+
|
|
359
|
+
parse_attributes(attributes)
|
|
360
|
+
|
|
361
|
+
tag = [:html, :tag, tag, attributes]
|
|
362
|
+
|
|
363
|
+
@stacks.last << [:static, ' '] if leading_ws
|
|
364
|
+
@stacks.last << tag
|
|
365
|
+
@stacks.last << [:static, ' '] if trailing_ws
|
|
366
|
+
|
|
367
|
+
case @line
|
|
368
|
+
when /\A\s*:\s*/
|
|
369
|
+
# Block expansion
|
|
370
|
+
@line = $'
|
|
371
|
+
if @line =~ @embedded_re
|
|
372
|
+
|
|
373
|
+
# Parse attributes
|
|
374
|
+
@line = $2
|
|
375
|
+
attrs = parse_attributes
|
|
376
|
+
tag << [:slim, :embedded, $1, parse_text_block($', @orig_line.size - $'.size + $2.size), attrs]
|
|
377
|
+
else
|
|
378
|
+
(@line =~ @tag_re) || syntax_error!('Expected tag')
|
|
379
|
+
@line = $' if $1
|
|
380
|
+
content = [:multi]
|
|
381
|
+
tag << content
|
|
382
|
+
i = @stacks.size
|
|
383
|
+
@stacks << content
|
|
384
|
+
parse_tag($&)
|
|
385
|
+
@stacks.delete_at(i)
|
|
386
|
+
end
|
|
387
|
+
when /\A\s*=(=?)(['<>]*)/
|
|
388
|
+
# Handle output code
|
|
389
|
+
@line = $'
|
|
390
|
+
trailing_ws2 = $2.include?('>'.freeze)
|
|
391
|
+
block = [:multi]
|
|
392
|
+
@stacks.last.insert(-2, [:static, ' ']) if !leading_ws && $2.include?('<'.freeze)
|
|
393
|
+
tag << [:slim, :output, $1 != '=', parse_broken_line, block]
|
|
394
|
+
@stacks.last << [:static, ' '] if !trailing_ws && trailing_ws2
|
|
395
|
+
@stacks << block
|
|
396
|
+
when /\A\s*\/\s*/
|
|
397
|
+
# Closed tag. Do nothing
|
|
398
|
+
@line = $'
|
|
399
|
+
syntax_error!('Unexpected text after closed tag') unless @line.empty?
|
|
400
|
+
when /\A\s*\Z/
|
|
401
|
+
# Empty content
|
|
402
|
+
content = [:multi]
|
|
403
|
+
tag << content
|
|
404
|
+
@stacks << content
|
|
405
|
+
when /\A ?/
|
|
406
|
+
# Text content
|
|
407
|
+
tag << [:slim, :text, :inline, parse_text_block($', @orig_line.size - $'.size)]
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def parse_attributes(attributes = [:html, :attrs])
|
|
412
|
+
# Check to see if there is a delimiter right after the tag name
|
|
413
|
+
delimiter = nil
|
|
414
|
+
if @line =~ @attr_list_delims_re
|
|
415
|
+
delimiter = @attr_list_delims[$1]
|
|
416
|
+
@line = $'
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
if delimiter
|
|
420
|
+
boolean_attr_re = /#{@attr_name}(?=(\s|#{Regexp.escape delimiter}|\Z))/
|
|
421
|
+
end_re = /\A\s*#{Regexp.escape delimiter}/
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
while true
|
|
425
|
+
case @line
|
|
426
|
+
when @splat_attrs_regexp
|
|
427
|
+
# Splat attribute
|
|
428
|
+
@line = $'
|
|
429
|
+
attributes << [:slim, :splat, parse_ruby_code(delimiter)]
|
|
430
|
+
when @quoted_attr_re
|
|
431
|
+
# Value is quoted (static)
|
|
432
|
+
@line = $'
|
|
433
|
+
attributes << [:html, :attr, $1,
|
|
434
|
+
[:escape, $2.empty?, [:slim, :interpolate, parse_quoted_attribute($3)]]]
|
|
435
|
+
when @code_attr_re
|
|
436
|
+
# Value is ruby code
|
|
437
|
+
@line = $'
|
|
438
|
+
name = $1
|
|
439
|
+
escape = $2.empty?
|
|
440
|
+
value = parse_ruby_code(delimiter)
|
|
441
|
+
syntax_error!('Invalid empty attribute') if value.empty?
|
|
442
|
+
attributes << [:html, :attr, name, [:slim, :attrvalue, escape, value]]
|
|
443
|
+
else
|
|
444
|
+
break unless delimiter
|
|
445
|
+
|
|
446
|
+
case @line
|
|
447
|
+
when boolean_attr_re
|
|
448
|
+
# Boolean attribute
|
|
449
|
+
@line = $'
|
|
450
|
+
attributes << [:html, :attr, $1, [:multi]]
|
|
451
|
+
when end_re
|
|
452
|
+
# Find ending delimiter
|
|
453
|
+
@line = $'
|
|
454
|
+
break
|
|
455
|
+
else
|
|
456
|
+
# Found something where an attribute should be
|
|
457
|
+
@line.lstrip!
|
|
458
|
+
syntax_error!('Expected attribute') unless @line.empty?
|
|
459
|
+
|
|
460
|
+
# Attributes span multiple lines
|
|
461
|
+
@stacks.last << [:newline]
|
|
462
|
+
syntax_error!("Expected closing delimiter #{delimiter}") if @lines.empty?
|
|
463
|
+
next_line
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
attributes || [:html, :attrs]
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def parse_ruby_code(outer_delimiter)
|
|
471
|
+
code, count, delimiter, close_delimiter = ''.dup, 0, nil, nil
|
|
472
|
+
|
|
473
|
+
# Attribute ends with space or attribute delimiter
|
|
474
|
+
end_re = /\A[\s#{Regexp.escape outer_delimiter.to_s}]/
|
|
475
|
+
|
|
476
|
+
until @line.empty? || (count == 0 && @line =~ end_re)
|
|
477
|
+
if @line =~ /\A[,\\]\Z/
|
|
478
|
+
code << @line << "\n"
|
|
479
|
+
expect_next_line
|
|
480
|
+
else
|
|
481
|
+
if count > 0
|
|
482
|
+
if @line[0] == delimiter[0]
|
|
483
|
+
count += 1
|
|
484
|
+
elsif @line[0] == close_delimiter[0]
|
|
485
|
+
count -= 1
|
|
486
|
+
end
|
|
487
|
+
elsif @line =~ @code_attr_delims_re
|
|
488
|
+
count = 1
|
|
489
|
+
delimiter, close_delimiter = $&, @code_attr_delims[$&]
|
|
490
|
+
end
|
|
491
|
+
code << @line.slice!(0)
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
syntax_error!("Expected closing delimiter #{close_delimiter}") if count != 0
|
|
495
|
+
code
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def parse_quoted_attribute(quote)
|
|
499
|
+
value, count = ''.dup, 0
|
|
500
|
+
|
|
501
|
+
until count == 0 && @line[0] == quote[0]
|
|
502
|
+
if @line =~ /\A(\\)?\Z/
|
|
503
|
+
value << ($1 ? ' ' : "\n")
|
|
504
|
+
expect_next_line
|
|
505
|
+
else
|
|
506
|
+
if @line[0] == '{'
|
|
507
|
+
count += 1
|
|
508
|
+
elsif @line[0] == '}'
|
|
509
|
+
count -= 1
|
|
510
|
+
end
|
|
511
|
+
value << @line.slice!(0)
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
@line.slice!(0)
|
|
516
|
+
value
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Helper for raising exceptions
|
|
520
|
+
def syntax_error!(message)
|
|
521
|
+
raise SyntaxError.new(message, options[:file], @orig_line, @lineno,
|
|
522
|
+
@orig_line && @line ? @orig_line.size - @line.size : 0)
|
|
523
|
+
rescue SyntaxError => ex
|
|
524
|
+
# HACK: Manipulate stacktrace for Rails and other frameworks
|
|
525
|
+
# to find the right file.
|
|
526
|
+
ex.backtrace.unshift "#{options[:file]}:#{@lineno}"
|
|
527
|
+
raise
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def expect_next_line
|
|
531
|
+
next_line || syntax_error!('Unexpected end of file')
|
|
532
|
+
@line.strip!
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slim
|
|
4
|
+
class Railtie < ::Rails::Railtie
|
|
5
|
+
initializer 'initialize slim template handler' do
|
|
6
|
+
ActiveSupport.on_load(:action_view) do
|
|
7
|
+
Slim::RailsTemplate = Temple::Templates::Rails(Slim::Engine,
|
|
8
|
+
register_as: :slim,
|
|
9
|
+
# Use rails-specific generator. This is necessary
|
|
10
|
+
# to support block capturing and streaming.
|
|
11
|
+
generator: Temple::Generators::RailsOutputBuffer,
|
|
12
|
+
# Disable the internal slim capturing.
|
|
13
|
+
# Rails takes care of the capturing by itself.
|
|
14
|
+
disable_capture: true,
|
|
15
|
+
streaming: true)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Slim
|
|
3
|
+
module Smart
|
|
4
|
+
# Perform smart entity escaping in the
|
|
5
|
+
# expressions `[:slim, :text, type, Expression]`.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class Escaper < ::Slim::Filter
|
|
9
|
+
define_options smart_text_escaping: true
|
|
10
|
+
|
|
11
|
+
def call(exp)
|
|
12
|
+
if options[:smart_text_escaping]
|
|
13
|
+
super
|
|
14
|
+
else
|
|
15
|
+
exp
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def on_slim_text(type, content)
|
|
20
|
+
[:escape, type != :verbatim, [:slim, :text, type, compile(content)]]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def on_static(string)
|
|
24
|
+
# Prevent obvious &foo; and Ӓ and ÿ entities from escaping.
|
|
25
|
+
block = [:multi]
|
|
26
|
+
until string.empty?
|
|
27
|
+
case string
|
|
28
|
+
when /\A&([a-z][a-z0-9]*|#x[0-9a-f]+|#\d+);/i
|
|
29
|
+
# Entity.
|
|
30
|
+
block << [:escape, false, [:static, $&]]
|
|
31
|
+
string = $'
|
|
32
|
+
when /\A&?[^&]*/
|
|
33
|
+
# Other text.
|
|
34
|
+
block << [:static, $&]
|
|
35
|
+
string = $'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
block
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Slim
|
|
3
|
+
module Smart
|
|
4
|
+
# Perform newline processing in the
|
|
5
|
+
# expressions `[:slim, :text, type, Expression]`.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class Filter < ::Slim::Filter
|
|
9
|
+
define_options smart_text: true,
|
|
10
|
+
smart_text_end_chars: '([{',
|
|
11
|
+
smart_text_begin_chars: ',.;:!?)]}'
|
|
12
|
+
|
|
13
|
+
def initialize(opts = {})
|
|
14
|
+
super
|
|
15
|
+
@active = @prepend = @append = false
|
|
16
|
+
@prepend_re = /\A#{chars_re(options[:smart_text_begin_chars])}/
|
|
17
|
+
@append_re = /#{chars_re(options[:smart_text_end_chars])}\Z/
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(exp)
|
|
21
|
+
if options[:smart_text]
|
|
22
|
+
super
|
|
23
|
+
else
|
|
24
|
+
exp
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_multi(*exps)
|
|
29
|
+
# The [:multi] blocks serve two purposes.
|
|
30
|
+
# On outer level, they collect the building blocks like
|
|
31
|
+
# tags, verbatim text, and implicit/explicit text.
|
|
32
|
+
# Within a text block, they collect the individual
|
|
33
|
+
# lines in [:slim, :interpolate, string] blocks.
|
|
34
|
+
#
|
|
35
|
+
# Our goal here is to decide when we want to prepend and
|
|
36
|
+
# append newlines to those individual interpolated lines.
|
|
37
|
+
# We basically want the text to come out as it was originally entered,
|
|
38
|
+
# while removing newlines next to the enclosing tags.
|
|
39
|
+
#
|
|
40
|
+
# On outer level, we choose to prepend every time, except
|
|
41
|
+
# right after the opening tag or after other text block.
|
|
42
|
+
# We also use the append flag to recognize the last expression
|
|
43
|
+
# before the closing tag, as we don't want to append newline there.
|
|
44
|
+
#
|
|
45
|
+
# Within text block, we prepend only before the first line unless
|
|
46
|
+
# the outer level tells us not to, and we append only after the last line,
|
|
47
|
+
# unless the outer level tells us it is the last line before the closing tag.
|
|
48
|
+
# Of course, this is later subject to the special begin/end characters
|
|
49
|
+
# which may further suppress the newline at the corresponding line boundary.
|
|
50
|
+
# Also note that the lines themselves are already correctly separated by newlines,
|
|
51
|
+
# so we don't have to worry about that at all.
|
|
52
|
+
block = [:multi]
|
|
53
|
+
prev = nil
|
|
54
|
+
last_exp = exps.reject { |exp| exp.first == :newline }.last unless @active && @append
|
|
55
|
+
exps.each do |exp|
|
|
56
|
+
@append = exp.equal?(last_exp)
|
|
57
|
+
if @active
|
|
58
|
+
@prepend = false if prev
|
|
59
|
+
else
|
|
60
|
+
@prepend = prev && (prev.first != :slim || prev[1] != :text)
|
|
61
|
+
end
|
|
62
|
+
block << compile(exp)
|
|
63
|
+
prev = exp unless exp.first == :newline
|
|
64
|
+
end
|
|
65
|
+
block
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def on_slim_text(type, content)
|
|
69
|
+
@active = type != :verbatim
|
|
70
|
+
[:slim, :text, type, compile(content)]
|
|
71
|
+
ensure
|
|
72
|
+
@active = false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def on_slim_text_inline(content)
|
|
76
|
+
# Inline text is not wrapped in multi block, so set it up as if it was.
|
|
77
|
+
@prepend = false
|
|
78
|
+
@append = true
|
|
79
|
+
on_slim_text(:inline, content)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def on_slim_interpolate(string)
|
|
83
|
+
if @active
|
|
84
|
+
string = "\n" + string if @prepend && string !~ @prepend_re
|
|
85
|
+
string += "\n" if @append && string !~ @append_re
|
|
86
|
+
end
|
|
87
|
+
[:slim, :interpolate, string]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def chars_re(string)
|
|
93
|
+
Regexp.union(string.split(//))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|