mega-quick-lib 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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/mega-quick-lib.gemspec +12 -0
  3. data/slim-5.2.1/CHANGES +496 -0
  4. data/slim-5.2.1/Gemfile +43 -0
  5. data/slim-5.2.1/LICENSE +21 -0
  6. data/slim-5.2.1/README.jp.md +1276 -0
  7. data/slim-5.2.1/README.md +1313 -0
  8. data/slim-5.2.1/Rakefile +80 -0
  9. data/slim-5.2.1/bin/slimrb +6 -0
  10. data/slim-5.2.1/doc/include.md +20 -0
  11. data/slim-5.2.1/doc/jp/include.md +20 -0
  12. data/slim-5.2.1/doc/jp/logic_less.md +137 -0
  13. data/slim-5.2.1/doc/jp/smart.md +102 -0
  14. data/slim-5.2.1/doc/jp/translator.md +28 -0
  15. data/slim-5.2.1/doc/logic_less.md +137 -0
  16. data/slim-5.2.1/doc/smart.md +120 -0
  17. data/slim-5.2.1/doc/translator.md +28 -0
  18. data/slim-5.2.1/lib/slim/code_attributes.rb +68 -0
  19. data/slim-5.2.1/lib/slim/command.rb +122 -0
  20. data/slim-5.2.1/lib/slim/controls.rb +61 -0
  21. data/slim-5.2.1/lib/slim/do_inserter.rb +34 -0
  22. data/slim-5.2.1/lib/slim/embedded.rb +248 -0
  23. data/slim-5.2.1/lib/slim/end_inserter.rb +63 -0
  24. data/slim-5.2.1/lib/slim/engine.rb +42 -0
  25. data/slim-5.2.1/lib/slim/erb_converter.rb +15 -0
  26. data/slim-5.2.1/lib/slim/filter.rb +31 -0
  27. data/slim-5.2.1/lib/slim/grammar.rb +24 -0
  28. data/slim-5.2.1/lib/slim/include.rb +58 -0
  29. data/slim-5.2.1/lib/slim/interpolation.rb +36 -0
  30. data/slim-5.2.1/lib/slim/logic_less/context.rb +126 -0
  31. data/slim-5.2.1/lib/slim/logic_less/filter.rb +81 -0
  32. data/slim-5.2.1/lib/slim/logic_less.rb +6 -0
  33. data/slim-5.2.1/lib/slim/parser.rb +535 -0
  34. data/slim-5.2.1/lib/slim/railtie.rb +19 -0
  35. data/slim-5.2.1/lib/slim/smart/escaper.rb +42 -0
  36. data/slim-5.2.1/lib/slim/smart/filter.rb +97 -0
  37. data/slim-5.2.1/lib/slim/smart/parser.rb +35 -0
  38. data/slim-5.2.1/lib/slim/smart.rb +9 -0
  39. data/slim-5.2.1/lib/slim/splat/builder.rb +115 -0
  40. data/slim-5.2.1/lib/slim/splat/filter.rb +93 -0
  41. data/slim-5.2.1/lib/slim/template.rb +6 -0
  42. data/slim-5.2.1/lib/slim/translator.rb +119 -0
  43. data/slim-5.2.1/lib/slim/version.rb +6 -0
  44. data/slim-5.2.1/lib/slim.rb +16 -0
  45. data/slim-5.2.1/slim.gemspec +33 -0
  46. data/slim-5.2.1/test/core/helper.rb +201 -0
  47. data/slim-5.2.1/test/core/test_code_blocks.rb +194 -0
  48. data/slim-5.2.1/test/core/test_code_escaping.rb +162 -0
  49. data/slim-5.2.1/test/core/test_code_evaluation.rb +181 -0
  50. data/slim-5.2.1/test/core/test_code_output.rb +168 -0
  51. data/slim-5.2.1/test/core/test_code_structure.rb +203 -0
  52. data/slim-5.2.1/test/core/test_commands.rb +219 -0
  53. data/slim-5.2.1/test/core/test_embedded_engines.rb +247 -0
  54. data/slim-5.2.1/test/core/test_encoding.rb +28 -0
  55. data/slim-5.2.1/test/core/test_erb_converter.rb +65 -0
  56. data/slim-5.2.1/test/core/test_html_attributes.rb +276 -0
  57. data/slim-5.2.1/test/core/test_html_escaping.rb +65 -0
  58. data/slim-5.2.1/test/core/test_html_structure.rb +599 -0
  59. data/slim-5.2.1/test/core/test_parser_errors.rb +148 -0
  60. data/slim-5.2.1/test/core/test_pretty.rb +163 -0
  61. data/slim-5.2.1/test/core/test_ruby_errors.rb +200 -0
  62. data/slim-5.2.1/test/core/test_slim_template.rb +78 -0
  63. data/slim-5.2.1/test/core/test_splat_prefix_option.rb +155 -0
  64. data/slim-5.2.1/test/core/test_tabs.rb +169 -0
  65. data/slim-5.2.1/test/core/test_text_interpolation.rb +79 -0
  66. data/slim-5.2.1/test/core/test_thread_options.rb +18 -0
  67. data/slim-5.2.1/test/core/test_unicode.rb +15 -0
  68. data/slim-5.2.1/test/include/files/recursive.slim +1 -0
  69. data/slim-5.2.1/test/include/files/slimfile.slim +3 -0
  70. data/slim-5.2.1/test/include/files/subdir/test.slim +1 -0
  71. data/slim-5.2.1/test/include/files/textfile +1 -0
  72. data/slim-5.2.1/test/include/test_include.rb +24 -0
  73. data/slim-5.2.1/test/literate/TESTS.md +1323 -0
  74. data/slim-5.2.1/test/literate/helper.rb +15 -0
  75. data/slim-5.2.1/test/literate/run.rb +92 -0
  76. data/slim-5.2.1/test/logic_less/test_logic_less.rb +330 -0
  77. data/slim-5.2.1/test/rails/Rakefile +7 -0
  78. data/slim-5.2.1/test/rails/app/assets/config/manifest.js +1 -0
  79. data/slim-5.2.1/test/rails/app/controllers/application_controller.rb +2 -0
  80. data/slim-5.2.1/test/rails/app/controllers/entries_controller.rb +5 -0
  81. data/slim-5.2.1/test/rails/app/controllers/slim_controller.rb +46 -0
  82. data/slim-5.2.1/test/rails/app/helpers/application_helper.rb +5 -0
  83. data/slim-5.2.1/test/rails/app/models/entry.rb +16 -0
  84. data/slim-5.2.1/test/rails/app/views/entries/edit.html.slim +3 -0
  85. data/slim-5.2.1/test/rails/app/views/layouts/application.html+testvariant.slim +10 -0
  86. data/slim-5.2.1/test/rails/app/views/layouts/application.html.slim +10 -0
  87. data/slim-5.2.1/test/rails/app/views/slim/_partial.html.slim +1 -0
  88. data/slim-5.2.1/test/rails/app/views/slim/attributes.html.slim +3 -0
  89. data/slim-5.2.1/test/rails/app/views/slim/content_for.html.slim +7 -0
  90. data/slim-5.2.1/test/rails/app/views/slim/erb.html.erb +1 -0
  91. data/slim-5.2.1/test/rails/app/views/slim/form_for.html.slim +2 -0
  92. data/slim-5.2.1/test/rails/app/views/slim/helper.html.slim +4 -0
  93. data/slim-5.2.1/test/rails/app/views/slim/integers.html.slim +1 -0
  94. data/slim-5.2.1/test/rails/app/views/slim/no_layout.html.slim +1 -0
  95. data/slim-5.2.1/test/rails/app/views/slim/normal.html.slim +1 -0
  96. data/slim-5.2.1/test/rails/app/views/slim/partial.html.slim +2 -0
  97. data/slim-5.2.1/test/rails/app/views/slim/splat.html.slim +2 -0
  98. data/slim-5.2.1/test/rails/app/views/slim/splat_with_delimiter.slim +1 -0
  99. data/slim-5.2.1/test/rails/app/views/slim/thread_options.html.slim +1 -0
  100. data/slim-5.2.1/test/rails/app/views/slim/variables.html.slim +1 -0
  101. data/slim-5.2.1/test/rails/app/views/slim/xml.slim +1 -0
  102. data/slim-5.2.1/test/rails/config/application.rb +44 -0
  103. data/slim-5.2.1/test/rails/config/boot.rb +10 -0
  104. data/slim-5.2.1/test/rails/config/environment.rb +5 -0
  105. data/slim-5.2.1/test/rails/config/environments/test.rb +34 -0
  106. data/slim-5.2.1/test/rails/config/initializers/backtrace_silencers.rb +7 -0
  107. data/slim-5.2.1/test/rails/config/initializers/inflections.rb +10 -0
  108. data/slim-5.2.1/test/rails/config/initializers/mime_types.rb +5 -0
  109. data/slim-5.2.1/test/rails/config/initializers/session_store.rb +8 -0
  110. data/slim-5.2.1/test/rails/config/locales/en.yml +5 -0
  111. data/slim-5.2.1/test/rails/config/routes.rb +60 -0
  112. data/slim-5.2.1/test/rails/config.ru +4 -0
  113. data/slim-5.2.1/test/rails/script/rails +6 -0
  114. data/slim-5.2.1/test/rails/test/helper.rb +34 -0
  115. data/slim-5.2.1/test/rails/test/test_slim.rb +102 -0
  116. data/slim-5.2.1/test/sinatra/contest.rb +91 -0
  117. data/slim-5.2.1/test/sinatra/helper.rb +143 -0
  118. data/slim-5.2.1/test/sinatra/test_core.rb +91 -0
  119. data/slim-5.2.1/test/sinatra/test_include.rb +16 -0
  120. data/slim-5.2.1/test/sinatra/views/embed_include_js.slim +8 -0
  121. data/slim-5.2.1/test/sinatra/views/embed_js.slim +11 -0
  122. data/slim-5.2.1/test/sinatra/views/footer.slim +1 -0
  123. data/slim-5.2.1/test/sinatra/views/hello.slim +1 -0
  124. data/slim-5.2.1/test/sinatra/views/layout2.slim +3 -0
  125. data/slim-5.2.1/test/smart/test_smart_text.rb +294 -0
  126. data/slim-5.2.1/test/translator/test_translator.rb +76 -0
  127. metadata +166 -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 &#1234; and &#x00ff; 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