haml 4.0.6 → 5.2.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 +5 -5
- data/.gitignore +19 -0
- data/.gitmodules +3 -0
- data/.travis.yml +72 -0
- data/.yardopts +2 -3
- data/CHANGELOG.md +138 -4
- data/FAQ.md +4 -14
- data/Gemfile +16 -0
- data/MIT-LICENSE +2 -2
- data/README.md +79 -42
- data/REFERENCE.md +142 -67
- data/Rakefile +44 -63
- data/TODO +24 -0
- data/benchmark.rb +70 -0
- data/haml.gemspec +45 -0
- data/lib/haml.rb +2 -0
- data/lib/haml/.gitattributes +1 -0
- data/lib/haml/attribute_builder.rb +164 -0
- data/lib/haml/attribute_compiler.rb +235 -0
- data/lib/haml/attribute_parser.rb +150 -0
- data/lib/haml/buffer.rb +29 -136
- data/lib/haml/compiler.rb +110 -320
- data/lib/haml/engine.rb +34 -41
- data/lib/haml/error.rb +28 -24
- data/lib/haml/escapable.rb +77 -0
- data/lib/haml/exec.rb +38 -20
- data/lib/haml/filters.rb +22 -27
- data/lib/haml/generator.rb +42 -0
- data/lib/haml/helpers.rb +134 -89
- data/lib/haml/helpers/action_view_extensions.rb +4 -2
- data/lib/haml/helpers/action_view_mods.rb +45 -60
- data/lib/haml/helpers/action_view_xss_mods.rb +2 -0
- data/lib/haml/helpers/safe_erubi_template.rb +20 -0
- data/lib/haml/helpers/safe_erubis_template.rb +5 -1
- data/lib/haml/helpers/xss_mods.rb +23 -13
- data/lib/haml/options.rb +63 -69
- data/lib/haml/parser.rb +292 -228
- data/lib/haml/plugin.rb +37 -0
- data/lib/haml/railtie.rb +38 -12
- data/lib/haml/sass_rails_filter.rb +18 -4
- data/lib/haml/template.rb +13 -6
- data/lib/haml/template/options.rb +13 -2
- data/lib/haml/temple_engine.rb +123 -0
- data/lib/haml/temple_line_counter.rb +30 -0
- data/lib/haml/util.rb +83 -202
- data/lib/haml/version.rb +3 -1
- data/yard/default/.gitignore +1 -0
- data/yard/default/fulldoc/html/css/common.sass +15 -0
- data/yard/default/layout/html/footer.erb +12 -0
- metadata +73 -108
- data/lib/haml/template/plugin.rb +0 -41
- data/test/engine_test.rb +0 -2013
- data/test/erb/_av_partial_1.erb +0 -12
- data/test/erb/_av_partial_2.erb +0 -8
- data/test/erb/action_view.erb +0 -62
- data/test/erb/standard.erb +0 -55
- data/test/filters_test.rb +0 -254
- data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
- data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
- data/test/gemfiles/Gemfile.rails-4.0.x +0 -5
- data/test/helper_test.rb +0 -583
- data/test/markaby/standard.mab +0 -52
- data/test/mocks/article.rb +0 -6
- data/test/parser_test.rb +0 -105
- data/test/results/content_for_layout.xhtml +0 -12
- data/test/results/eval_suppressed.xhtml +0 -9
- data/test/results/helpers.xhtml +0 -70
- data/test/results/helpful.xhtml +0 -10
- data/test/results/just_stuff.xhtml +0 -70
- data/test/results/list.xhtml +0 -12
- data/test/results/nuke_inner_whitespace.xhtml +0 -40
- data/test/results/nuke_outer_whitespace.xhtml +0 -148
- data/test/results/original_engine.xhtml +0 -20
- data/test/results/partial_layout.xhtml +0 -5
- data/test/results/partial_layout_erb.xhtml +0 -5
- data/test/results/partials.xhtml +0 -21
- data/test/results/render_layout.xhtml +0 -3
- data/test/results/silent_script.xhtml +0 -74
- data/test/results/standard.xhtml +0 -162
- data/test/results/tag_parsing.xhtml +0 -23
- data/test/results/very_basic.xhtml +0 -5
- data/test/results/whitespace_handling.xhtml +0 -90
- data/test/template_test.rb +0 -354
- data/test/templates/_av_partial_1.haml +0 -9
- data/test/templates/_av_partial_1_ugly.haml +0 -9
- data/test/templates/_av_partial_2.haml +0 -5
- data/test/templates/_av_partial_2_ugly.haml +0 -5
- data/test/templates/_layout.erb +0 -3
- data/test/templates/_layout_for_partial.haml +0 -3
- data/test/templates/_partial.haml +0 -8
- data/test/templates/_text_area.haml +0 -3
- data/test/templates/_text_area_helper.html.haml +0 -4
- data/test/templates/action_view.haml +0 -47
- data/test/templates/action_view_ugly.haml +0 -47
- data/test/templates/breakage.haml +0 -8
- data/test/templates/content_for_layout.haml +0 -8
- data/test/templates/eval_suppressed.haml +0 -11
- data/test/templates/helpers.haml +0 -55
- data/test/templates/helpful.haml +0 -11
- data/test/templates/just_stuff.haml +0 -85
- data/test/templates/list.haml +0 -12
- data/test/templates/nuke_inner_whitespace.haml +0 -32
- data/test/templates/nuke_outer_whitespace.haml +0 -144
- data/test/templates/original_engine.haml +0 -17
- data/test/templates/partial_layout.haml +0 -3
- data/test/templates/partial_layout_erb.erb +0 -4
- data/test/templates/partialize.haml +0 -1
- data/test/templates/partials.haml +0 -12
- data/test/templates/render_layout.haml +0 -2
- data/test/templates/silent_script.haml +0 -45
- data/test/templates/standard.haml +0 -43
- data/test/templates/standard_ugly.haml +0 -43
- data/test/templates/tag_parsing.haml +0 -21
- data/test/templates/very_basic.haml +0 -4
- data/test/templates/whitespace_handling.haml +0 -87
- data/test/test_helper.rb +0 -81
- data/test/util_test.rb +0 -63
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'ripper'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
end
|
|
7
|
+
require 'temple/static_analyzer'
|
|
8
|
+
|
|
9
|
+
module Haml
|
|
10
|
+
# Haml::AttriubuteParser parses Hash literal to { String (key name) => String (value literal) }.
|
|
11
|
+
module AttributeParser
|
|
12
|
+
class UnexpectedTokenError < StandardError; end
|
|
13
|
+
class UnexpectedKeyError < StandardError; end
|
|
14
|
+
|
|
15
|
+
# Indices in Ripper tokens
|
|
16
|
+
TYPE = 1
|
|
17
|
+
TEXT = 2
|
|
18
|
+
|
|
19
|
+
IGNORED_TYPES = %i[on_sp on_ignored_nl].freeze
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# @return [Boolean] - return true if AttributeParser.parse can be used.
|
|
23
|
+
def available?
|
|
24
|
+
defined?(Ripper) && Temple::StaticAnalyzer.available?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param [String] exp - Old attributes literal or Hash literal generated from new attributes.
|
|
28
|
+
# @return [Hash<String, String>,nil] - Return parsed attribute Hash whose values are Ruby literals, or return nil if argument is not a single Hash literal.
|
|
29
|
+
def parse(exp)
|
|
30
|
+
return nil unless hash_literal?(exp)
|
|
31
|
+
|
|
32
|
+
hash = {}
|
|
33
|
+
each_attribute(exp) do |key, value|
|
|
34
|
+
hash[key] = value
|
|
35
|
+
end
|
|
36
|
+
hash
|
|
37
|
+
rescue UnexpectedTokenError, UnexpectedKeyError
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# @param [String] exp - Ruby expression
|
|
44
|
+
# @return [Boolean] - Return true if exp is a single Hash literal
|
|
45
|
+
def hash_literal?(exp)
|
|
46
|
+
return false if Temple::StaticAnalyzer.syntax_error?(exp)
|
|
47
|
+
sym, body = Ripper.sexp(exp)
|
|
48
|
+
sym == :program && body.is_a?(Array) && body.size == 1 && body[0] && body[0][0] == :hash
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param [Array] tokens - Ripper tokens. Scanned tokens will be destructively removed from this argument.
|
|
52
|
+
# @return [String] - attribute name in String
|
|
53
|
+
def shift_key!(tokens)
|
|
54
|
+
while !tokens.empty? && IGNORED_TYPES.include?(tokens.first[TYPE])
|
|
55
|
+
tokens.shift # ignore spaces
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
_, type, first_text = tokens.shift
|
|
59
|
+
case type
|
|
60
|
+
when :on_label # `key:`
|
|
61
|
+
first_text.tr(':', '')
|
|
62
|
+
when :on_symbeg # `:key =>`, `:'key' =>` or `:"key" =>`
|
|
63
|
+
key = tokens.shift[TEXT]
|
|
64
|
+
if first_text != ':' # `:'key'` or `:"key"`
|
|
65
|
+
expect_string_end!(tokens.shift)
|
|
66
|
+
end
|
|
67
|
+
shift_hash_rocket!(tokens)
|
|
68
|
+
key
|
|
69
|
+
when :on_tstring_beg # `"key":`, `'key':` or `"key" =>`
|
|
70
|
+
key = tokens.shift[TEXT]
|
|
71
|
+
next_token = tokens.shift
|
|
72
|
+
if next_token[TYPE] != :on_label_end # on_label_end is `":` or `':`, so `"key" =>`
|
|
73
|
+
expect_string_end!(next_token)
|
|
74
|
+
shift_hash_rocket!(tokens)
|
|
75
|
+
end
|
|
76
|
+
key
|
|
77
|
+
else
|
|
78
|
+
raise UnexpectedKeyError.new("unexpected token is given!: #{first_text} (#{type})")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param [Array] token - Ripper token
|
|
83
|
+
def expect_string_end!(token)
|
|
84
|
+
if token[TYPE] != :on_tstring_end
|
|
85
|
+
raise UnexpectedTokenError
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @param [Array] tokens - Ripper tokens
|
|
90
|
+
def shift_hash_rocket!(tokens)
|
|
91
|
+
until tokens.empty?
|
|
92
|
+
_, type, str = tokens.shift
|
|
93
|
+
break if type == :on_op && str == '=>'
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param [String] hash_literal
|
|
98
|
+
# @param [Proc] block - that takes [String, String] as arguments
|
|
99
|
+
def each_attribute(hash_literal, &block)
|
|
100
|
+
all_tokens = Ripper.lex(hash_literal.strip)
|
|
101
|
+
all_tokens = all_tokens[1...-1] || [] # strip tokens for brackets
|
|
102
|
+
|
|
103
|
+
each_balanced_tokens(all_tokens) do |tokens|
|
|
104
|
+
key = shift_key!(tokens)
|
|
105
|
+
value = tokens.map {|t| t[2] }.join.strip
|
|
106
|
+
block.call(key, value)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @param [Array] tokens - Ripper tokens
|
|
111
|
+
# @param [Proc] block - that takes balanced Ripper tokens as arguments
|
|
112
|
+
def each_balanced_tokens(tokens, &block)
|
|
113
|
+
attr_tokens = []
|
|
114
|
+
open_tokens = Hash.new { |h, k| h[k] = 0 }
|
|
115
|
+
|
|
116
|
+
tokens.each do |token|
|
|
117
|
+
case token[TYPE]
|
|
118
|
+
when :on_comma
|
|
119
|
+
if open_tokens.values.all?(&:zero?)
|
|
120
|
+
block.call(attr_tokens)
|
|
121
|
+
attr_tokens = []
|
|
122
|
+
next
|
|
123
|
+
end
|
|
124
|
+
when :on_lbracket
|
|
125
|
+
open_tokens[:array] += 1
|
|
126
|
+
when :on_rbracket
|
|
127
|
+
open_tokens[:array] -= 1
|
|
128
|
+
when :on_lbrace
|
|
129
|
+
open_tokens[:block] += 1
|
|
130
|
+
when :on_rbrace
|
|
131
|
+
open_tokens[:block] -= 1
|
|
132
|
+
when :on_lparen
|
|
133
|
+
open_tokens[:paren] += 1
|
|
134
|
+
when :on_rparen
|
|
135
|
+
open_tokens[:paren] -= 1
|
|
136
|
+
when :on_embexpr_beg
|
|
137
|
+
open_tokens[:embexpr] += 1
|
|
138
|
+
when :on_embexpr_end
|
|
139
|
+
open_tokens[:embexpr] -= 1
|
|
140
|
+
when *IGNORED_TYPES
|
|
141
|
+
next if attr_tokens.empty?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
attr_tokens << token
|
|
145
|
+
end
|
|
146
|
+
block.call(attr_tokens) unless attr_tokens.empty?
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
data/lib/haml/buffer.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Haml
|
|
2
4
|
# This class is used only internally. It holds the buffer of HTML that
|
|
3
5
|
# is eventually output as the resulting document.
|
|
@@ -90,7 +92,8 @@ module Haml
|
|
|
90
92
|
def initialize(upper = nil, options = {})
|
|
91
93
|
@active = true
|
|
92
94
|
@upper = upper
|
|
93
|
-
@options =
|
|
95
|
+
@options = Options.buffer_defaults
|
|
96
|
+
@options = @options.merge(options) unless options.empty?
|
|
94
97
|
@buffer = new_encoded_string
|
|
95
98
|
@tabulation = 0
|
|
96
99
|
|
|
@@ -115,8 +118,8 @@ module Haml
|
|
|
115
118
|
text.sub!(tabs, '') if dont_tab_up
|
|
116
119
|
end
|
|
117
120
|
|
|
118
|
-
@buffer << text
|
|
119
121
|
@real_tabs += tab_change
|
|
122
|
+
@buffer << text
|
|
120
123
|
end
|
|
121
124
|
|
|
122
125
|
# Modifies the indentation of the document.
|
|
@@ -127,82 +130,15 @@ module Haml
|
|
|
127
130
|
@real_tabs += tab_change
|
|
128
131
|
end
|
|
129
132
|
|
|
130
|
-
Haml::Util.def_static_method(self, :format_script, [:result],
|
|
131
|
-
:preserve_script, :in_tag, :preserve_tag, :escape_html,
|
|
132
|
-
:nuke_inner_whitespace, :interpolated, :ugly, <<RUBY)
|
|
133
|
-
<% # Escape HTML here so that the safety of the string is preserved in Rails
|
|
134
|
-
result_name = escape_html ? "html_escape(result.to_s)" : "result.to_s" %>
|
|
135
|
-
<% unless ugly %>
|
|
136
|
-
# If we're interpolated,
|
|
137
|
-
# then the custom tabulation is handled in #push_text.
|
|
138
|
-
# The easiest way to avoid it here is to reset @tabulation.
|
|
139
|
-
<% if interpolated %>
|
|
140
|
-
old_tabulation = @tabulation
|
|
141
|
-
@tabulation = 0
|
|
142
|
-
<% end %>
|
|
143
|
-
|
|
144
|
-
<% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
|
|
145
|
-
tabulation = @real_tabs
|
|
146
|
-
<% end %>
|
|
147
|
-
result = <%= result_name %>.<% if nuke_inner_whitespace %>strip<% else %>rstrip<% end %>
|
|
148
|
-
<% else %>
|
|
149
|
-
result = <%= result_name %><% if nuke_inner_whitespace %>.strip<% end %>
|
|
150
|
-
<% end %>
|
|
151
|
-
|
|
152
|
-
<% if preserve_tag %>
|
|
153
|
-
result = Haml::Helpers.preserve(result)
|
|
154
|
-
<% elsif preserve_script %>
|
|
155
|
-
result = Haml::Helpers.find_and_preserve(result, options[:preserve])
|
|
156
|
-
<% end %>
|
|
157
|
-
|
|
158
|
-
<% if ugly %>
|
|
159
|
-
fix_textareas!(result) if toplevel? && result.include?('<textarea')
|
|
160
|
-
return result
|
|
161
|
-
<% else %>
|
|
162
|
-
<% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
|
|
163
|
-
has_newline = result.include?("\\n")
|
|
164
|
-
<% end %>
|
|
165
|
-
|
|
166
|
-
<% if in_tag && !nuke_inner_whitespace %>
|
|
167
|
-
<% unless preserve_tag %> if !has_newline <% end %>
|
|
168
|
-
@real_tabs -= 1
|
|
169
|
-
<% if interpolated %> @tabulation = old_tabulation <% end %>
|
|
170
|
-
return result
|
|
171
|
-
<% unless preserve_tag %> end <% end %>
|
|
172
|
-
<% end %>
|
|
173
|
-
|
|
174
|
-
<% if !(in_tag && preserve_tag && !nuke_inner_whitespace) %>
|
|
175
|
-
# Precompiled tabulation may be wrong
|
|
176
|
-
<% if !interpolated && !in_tag %>
|
|
177
|
-
result = tabs + result if @tabulation > 0
|
|
178
|
-
<% end %>
|
|
179
|
-
|
|
180
|
-
if has_newline
|
|
181
|
-
result = result.gsub "\\n", "\\n" + tabs(tabulation)
|
|
182
|
-
|
|
183
|
-
# Add tabulation if it wasn't precompiled
|
|
184
|
-
<% if in_tag && !nuke_inner_whitespace %> result = tabs(tabulation) + result <% end %>
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
fix_textareas!(result) if toplevel? && result.include?('<textarea')
|
|
188
|
-
|
|
189
|
-
<% if in_tag && !nuke_inner_whitespace %>
|
|
190
|
-
result = "\\n\#{result}\\n\#{tabs(tabulation-1)}"
|
|
191
|
-
@real_tabs -= 1
|
|
192
|
-
<% end %>
|
|
193
|
-
<% if interpolated %> @tabulation = old_tabulation <% end %>
|
|
194
|
-
result
|
|
195
|
-
<% end %>
|
|
196
|
-
<% end %>
|
|
197
|
-
RUBY
|
|
198
|
-
|
|
199
133
|
def attributes(class_id, obj_ref, *attributes_hashes)
|
|
200
134
|
attributes = class_id
|
|
201
135
|
attributes_hashes.each do |old|
|
|
202
|
-
|
|
136
|
+
result = {}
|
|
137
|
+
old.each { |k, v| result[k.to_s] = v }
|
|
138
|
+
AttributeBuilder.merge_attributes!(attributes, result)
|
|
203
139
|
end
|
|
204
|
-
|
|
205
|
-
|
|
140
|
+
AttributeBuilder.merge_attributes!(attributes, parse_object_ref(obj_ref)) if obj_ref
|
|
141
|
+
AttributeBuilder.build_attributes(
|
|
206
142
|
html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
|
|
207
143
|
end
|
|
208
144
|
|
|
@@ -217,50 +153,6 @@ RUBY
|
|
|
217
153
|
buffer << buffer.slice!(capture_position..-1).rstrip
|
|
218
154
|
end
|
|
219
155
|
|
|
220
|
-
# Merges two attribute hashes.
|
|
221
|
-
# This is the same as `to.merge!(from)`,
|
|
222
|
-
# except that it merges id, class, and data attributes.
|
|
223
|
-
#
|
|
224
|
-
# ids are concatenated with `"_"`,
|
|
225
|
-
# and classes are concatenated with `" "`.
|
|
226
|
-
# data hashes are simply merged.
|
|
227
|
-
#
|
|
228
|
-
# Destructively modifies both `to` and `from`.
|
|
229
|
-
#
|
|
230
|
-
# @param to [{String => String}] The attribute hash to merge into
|
|
231
|
-
# @param from [{String => #to_s}] The attribute hash to merge from
|
|
232
|
-
# @return [{String => String}] `to`, after being merged
|
|
233
|
-
def self.merge_attrs(to, from)
|
|
234
|
-
from['id'] = Compiler.filter_and_join(from['id'], '_') if from['id']
|
|
235
|
-
if to['id'] && from['id']
|
|
236
|
-
to['id'] << '_' << from.delete('id').to_s
|
|
237
|
-
elsif to['id'] || from['id']
|
|
238
|
-
from['id'] ||= to['id']
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
from['class'] = Compiler.filter_and_join(from['class'], ' ') if from['class']
|
|
242
|
-
if to['class'] && from['class']
|
|
243
|
-
# Make sure we don't duplicate class names
|
|
244
|
-
from['class'] = (from['class'].to_s.split(' ') | to['class'].split(' ')).sort.join(' ')
|
|
245
|
-
elsif to['class'] || from['class']
|
|
246
|
-
from['class'] ||= to['class']
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
from_data = from.delete('data') || {}
|
|
250
|
-
to_data = to.delete('data') || {}
|
|
251
|
-
|
|
252
|
-
# forces to_data & from_data into a hash
|
|
253
|
-
from_data = { nil => from_data } unless from_data.is_a?(Hash)
|
|
254
|
-
to_data = { nil => to_data } unless to_data.is_a?(Hash)
|
|
255
|
-
|
|
256
|
-
merged_data = to_data.merge(from_data)
|
|
257
|
-
|
|
258
|
-
to['data'] = merged_data unless merged_data.empty?
|
|
259
|
-
to.merge!(from)
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
private
|
|
263
|
-
|
|
264
156
|
# Works like #{find_and_preserve}, but allows the first newline after a
|
|
265
157
|
# preserved opening tag to remain unencoded, and then outdents the content.
|
|
266
158
|
# This change was motivated primarily by the change in Rails 3.2.3 to emit
|
|
@@ -270,27 +162,26 @@ RUBY
|
|
|
270
162
|
# @since Haml 4.0.1
|
|
271
163
|
# @private
|
|
272
164
|
def fix_textareas!(input)
|
|
273
|
-
|
|
165
|
+
return input unless input.include?('<textarea'.freeze)
|
|
166
|
+
|
|
167
|
+
pattern = /<(textarea)([^>]*)>(\n|
)(.*?)<\/textarea>/im
|
|
274
168
|
input.gsub!(pattern) do |s|
|
|
275
169
|
match = pattern.match(s)
|
|
276
|
-
content = match[
|
|
277
|
-
if match[
|
|
170
|
+
content = match[4]
|
|
171
|
+
if match[3] == '
'
|
|
278
172
|
content.sub!(/\A /, ' ')
|
|
279
173
|
else
|
|
280
174
|
content.sub!(/\A[ ]*/, '')
|
|
281
175
|
end
|
|
282
|
-
"
|
|
176
|
+
"<#{match[1]}#{match[2]}>\n#{content}</#{match[1]}>"
|
|
283
177
|
end
|
|
178
|
+
input
|
|
284
179
|
end
|
|
285
180
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
else
|
|
291
|
-
def new_encoded_string
|
|
292
|
-
"".encode(Encoding.find(options[:encoding]))
|
|
293
|
-
end
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
def new_encoded_string
|
|
184
|
+
"".encode(options[:encoding])
|
|
294
185
|
end
|
|
295
186
|
|
|
296
187
|
@@tab_cache = {}
|
|
@@ -328,18 +219,20 @@ RUBY
|
|
|
328
219
|
id = "#{ prefix }_#{ id }"
|
|
329
220
|
end
|
|
330
221
|
|
|
331
|
-
{'id' => id, 'class' => class_name}
|
|
222
|
+
{ 'id'.freeze => id, 'class'.freeze => class_name }
|
|
332
223
|
end
|
|
333
224
|
|
|
334
225
|
# Changes a word from camel case to underscores.
|
|
335
226
|
# Based on the method of the same name in Rails' Inflector,
|
|
336
227
|
# but copied here so it'll run properly without Rails.
|
|
337
228
|
def underscore(camel_cased_word)
|
|
338
|
-
camel_cased_word.to_s.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
229
|
+
word = camel_cased_word.to_s.dup
|
|
230
|
+
word.gsub!(/::/, '_')
|
|
231
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
232
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
233
|
+
word.tr!('-', '_')
|
|
234
|
+
word.downcase!
|
|
235
|
+
word
|
|
343
236
|
end
|
|
344
237
|
end
|
|
345
238
|
end
|
data/lib/haml/compiler.rb
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'haml/attribute_builder'
|
|
4
|
+
require 'haml/attribute_compiler'
|
|
5
|
+
require 'haml/temple_line_counter'
|
|
2
6
|
|
|
3
7
|
module Haml
|
|
4
8
|
class Compiler
|
|
@@ -7,15 +11,21 @@ module Haml
|
|
|
7
11
|
attr_accessor :options
|
|
8
12
|
|
|
9
13
|
def initialize(options)
|
|
10
|
-
@options = options
|
|
11
|
-
@output_tabs = 0
|
|
14
|
+
@options = Options.wrap(options)
|
|
12
15
|
@to_merge = []
|
|
13
|
-
@
|
|
16
|
+
@temple = [:multi]
|
|
17
|
+
@node = nil
|
|
18
|
+
@filters = Filters.defined.merge(options[:filters])
|
|
19
|
+
@attribute_compiler = AttributeCompiler.new(@options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(node)
|
|
23
|
+
compile(node)
|
|
24
|
+
@temple
|
|
14
25
|
end
|
|
15
26
|
|
|
16
27
|
def compile(node)
|
|
17
|
-
parent
|
|
18
|
-
@node = node
|
|
28
|
+
parent, @node = @node, node
|
|
19
29
|
if node.children.empty?
|
|
20
30
|
send(:"compile_#{node.type}")
|
|
21
31
|
else
|
|
@@ -25,79 +35,16 @@ module Haml
|
|
|
25
35
|
@node = parent
|
|
26
36
|
end
|
|
27
37
|
|
|
28
|
-
if RUBY_VERSION < "1.9"
|
|
29
|
-
# The source code that is evaluated to produce the Haml document.
|
|
30
|
-
#
|
|
31
|
-
# In Ruby 1.9, this is automatically converted to the correct encoding
|
|
32
|
-
# (see {file:REFERENCE.md#encodings the `:encoding` option}).
|
|
33
|
-
#
|
|
34
|
-
# @return [String]
|
|
35
|
-
def precompiled
|
|
36
|
-
@precompiled
|
|
37
|
-
end
|
|
38
|
-
else
|
|
39
|
-
def precompiled
|
|
40
|
-
encoding = Encoding.find(@options[:encoding])
|
|
41
|
-
return @precompiled.force_encoding(encoding) if encoding == Encoding::BINARY
|
|
42
|
-
return @precompiled.encode(encoding)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def precompiled_with_return_value
|
|
47
|
-
precompiled + ";" + precompiled_method_return_value
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Returns the precompiled string with the preamble and postamble.
|
|
51
|
-
#
|
|
52
|
-
# Initializes to ActionView::OutputBuffer when available; this is necessary
|
|
53
|
-
# to avoid ordering issues with partial layouts in Rails. If not available,
|
|
54
|
-
# initializes to nil.
|
|
55
|
-
def precompiled_with_ambles(local_names)
|
|
56
|
-
preamble = <<END.gsub("\n", ";")
|
|
57
|
-
begin
|
|
58
|
-
extend Haml::Helpers
|
|
59
|
-
_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, #{options.for_buffer.inspect})
|
|
60
|
-
_erbout = _hamlout.buffer
|
|
61
|
-
@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil
|
|
62
|
-
END
|
|
63
|
-
postamble = <<END.gsub("\n", ";")
|
|
64
|
-
#{precompiled_method_return_value}
|
|
65
|
-
ensure
|
|
66
|
-
@haml_buffer = @haml_buffer.upper if @haml_buffer
|
|
67
|
-
end
|
|
68
|
-
END
|
|
69
|
-
preamble + locals_code(local_names) + precompiled + postamble
|
|
70
|
-
end
|
|
71
|
-
|
|
72
38
|
private
|
|
73
39
|
|
|
74
|
-
# Returns the string used as the return value of the precompiled method.
|
|
75
|
-
# This method exists so it can be monkeypatched to return modified values.
|
|
76
|
-
def precompiled_method_return_value
|
|
77
|
-
"_erbout"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def locals_code(names)
|
|
81
|
-
names = names.keys if Hash == names
|
|
82
|
-
|
|
83
|
-
names.map do |name|
|
|
84
|
-
# Can't use || because someone might explicitly pass in false with a symbol
|
|
85
|
-
sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
|
|
86
|
-
str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
|
|
87
|
-
"#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
|
|
88
|
-
end.join(';') + ';'
|
|
89
|
-
end
|
|
90
|
-
|
|
91
40
|
def compile_root
|
|
92
|
-
@dont_indent_next_line = @dont_tab_up_next_text = false
|
|
93
41
|
@output_line = 1
|
|
94
|
-
|
|
95
|
-
yield
|
|
42
|
+
yield if block_given?
|
|
96
43
|
flush_merged_text
|
|
97
44
|
end
|
|
98
45
|
|
|
99
46
|
def compile_plain
|
|
100
|
-
push_text
|
|
47
|
+
push_text("#{@node.value[:text]}\n")
|
|
101
48
|
end
|
|
102
49
|
|
|
103
50
|
def nuke_inner_whitespace?(node)
|
|
@@ -119,15 +66,11 @@ END
|
|
|
119
66
|
end
|
|
120
67
|
|
|
121
68
|
def compile_silent_script
|
|
122
|
-
return if @options
|
|
69
|
+
return if @options.suppress_eval
|
|
123
70
|
push_silent(@node.value[:text])
|
|
124
71
|
keyword = @node.value[:keyword]
|
|
125
72
|
|
|
126
73
|
if block_given?
|
|
127
|
-
# Store these values because for conditional statements,
|
|
128
|
-
# we want to restore them for each branch
|
|
129
|
-
@node.value[:dont_indent_next_line] = @dont_indent_next_line
|
|
130
|
-
@node.value[:dont_tab_up_next_text] = @dont_tab_up_next_text
|
|
131
74
|
yield
|
|
132
75
|
push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
|
|
133
76
|
elsif keyword == "end"
|
|
@@ -137,10 +80,6 @@ END
|
|
|
137
80
|
@node.parent.value[:dont_push_end] = true
|
|
138
81
|
end
|
|
139
82
|
# Don't restore dont_* for end because it isn't a conditional branch.
|
|
140
|
-
elsif Parser::MID_BLOCK_KEYWORDS.include?(keyword)
|
|
141
|
-
# Restore dont_* for this conditional branch
|
|
142
|
-
@dont_indent_next_line = @node.parent.value[:dont_indent_next_line]
|
|
143
|
-
@dont_tab_up_next_text = @node.parent.value[:dont_tab_up_next_text]
|
|
144
83
|
end
|
|
145
84
|
end
|
|
146
85
|
|
|
@@ -152,117 +91,84 @@ END
|
|
|
152
91
|
# Get rid of whitespace outside of the tag if we need to
|
|
153
92
|
rstrip_buffer! if t[:nuke_outer_whitespace]
|
|
154
93
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
(t[:nuke_inner_whitespace] && block_given?)
|
|
158
|
-
|
|
159
|
-
if @options[:suppress_eval]
|
|
160
|
-
object_ref = "nil"
|
|
94
|
+
if @options.suppress_eval
|
|
95
|
+
object_ref = :nil
|
|
161
96
|
parse = false
|
|
162
97
|
value = t[:parse] ? nil : t[:value]
|
|
163
|
-
|
|
164
|
-
preserve_script = false
|
|
98
|
+
dynamic_attributes = Haml::Parser::DynamicAttributes.new
|
|
165
99
|
else
|
|
166
100
|
object_ref = t[:object_ref]
|
|
167
101
|
parse = t[:parse]
|
|
168
102
|
value = t[:value]
|
|
169
|
-
|
|
170
|
-
preserve_script = t[:preserve_script]
|
|
103
|
+
dynamic_attributes = t[:dynamic_attributes]
|
|
171
104
|
end
|
|
172
105
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
open_tag = prerender_tag(t[:name], t[:self_closing], t[:attributes])
|
|
178
|
-
if tag_closed
|
|
179
|
-
open_tag << "#{value}</#{t[:name]}>"
|
|
180
|
-
open_tag << "\n" unless t[:nuke_outer_whitespace]
|
|
181
|
-
elsif !(parse || t[:nuke_inner_whitespace] ||
|
|
182
|
-
(t[:self_closing] && t[:nuke_outer_whitespace]))
|
|
183
|
-
open_tag << "\n"
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
push_merged_text(open_tag,
|
|
187
|
-
tag_closed || t[:self_closing] || t[:nuke_inner_whitespace] ? 0 : 1,
|
|
188
|
-
!t[:nuke_outer_whitespace])
|
|
106
|
+
if @options[:trace]
|
|
107
|
+
t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
|
|
108
|
+
end
|
|
189
109
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
elsif attributes_hashes.size == 1
|
|
196
|
-
attributes_hashes = ", #{attributes_hashes.first}"
|
|
110
|
+
push_text("<#{t[:name]}")
|
|
111
|
+
push_temple(@attribute_compiler.compile(t[:attributes], object_ref, dynamic_attributes))
|
|
112
|
+
push_text(
|
|
113
|
+
if t[:self_closing] && @options.xhtml?
|
|
114
|
+
" />#{"\n" unless t[:nuke_outer_whitespace]}"
|
|
197
115
|
else
|
|
198
|
-
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
push_merged_text "<#{t[:name]}", 0, !t[:nuke_outer_whitespace]
|
|
202
|
-
push_generated_script(
|
|
203
|
-
"_hamlout.attributes(#{inspect_obj(t[:attributes])}, #{object_ref}#{attributes_hashes})")
|
|
204
|
-
concat_merged_text(
|
|
205
|
-
if t[:self_closing] && @options.xhtml?
|
|
206
|
-
" />" + (t[:nuke_outer_whitespace] ? "" : "\n")
|
|
207
|
-
else
|
|
208
|
-
">" + ((if t[:self_closing] && @options.html?
|
|
209
|
-
t[:nuke_outer_whitespace]
|
|
210
|
-
else
|
|
211
|
-
!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace]
|
|
212
|
-
end) ? "" : "\n")
|
|
213
|
-
end)
|
|
214
|
-
|
|
215
|
-
if value && !parse
|
|
216
|
-
concat_merged_text("#{value}</#{t[:name]}>#{t[:nuke_outer_whitespace] ? "" : "\n"}")
|
|
217
|
-
elsif !t[:nuke_inner_whitespace] && !t[:self_closing]
|
|
218
|
-
@to_merge << [:text, '', 1]
|
|
116
|
+
">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
|
|
219
117
|
end
|
|
118
|
+
)
|
|
220
119
|
|
|
221
|
-
|
|
120
|
+
if value && !parse
|
|
121
|
+
push_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
|
|
222
122
|
end
|
|
223
123
|
|
|
224
124
|
return if t[:self_closing]
|
|
225
125
|
|
|
226
126
|
if value.nil?
|
|
227
|
-
@output_tabs += 1 unless t[:nuke_inner_whitespace]
|
|
228
127
|
yield if block_given?
|
|
229
|
-
@output_tabs -= 1 unless t[:nuke_inner_whitespace]
|
|
230
128
|
rstrip_buffer! if t[:nuke_inner_whitespace]
|
|
231
|
-
|
|
232
|
-
t[:nuke_inner_whitespace] ? 0 : -1, !t[:nuke_inner_whitespace])
|
|
233
|
-
@dont_indent_next_line = t[:nuke_outer_whitespace]
|
|
129
|
+
push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
|
|
234
130
|
return
|
|
235
131
|
end
|
|
236
132
|
|
|
237
133
|
if parse
|
|
238
134
|
push_script(value, t.merge(:in_tag => true))
|
|
239
|
-
|
|
135
|
+
push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
|
|
240
136
|
end
|
|
241
137
|
end
|
|
242
138
|
|
|
243
139
|
def compile_comment
|
|
244
|
-
|
|
140
|
+
condition = "#{@node.value[:conditional]}>" if @node.value[:conditional]
|
|
141
|
+
revealed = @node.value[:revealed]
|
|
142
|
+
|
|
143
|
+
open = "<!--#{condition}#{'<!-->' if revealed}"
|
|
144
|
+
|
|
145
|
+
close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
|
|
245
146
|
|
|
246
|
-
# Render it statically if possible
|
|
247
147
|
unless block_given?
|
|
248
|
-
push_text("#{open}
|
|
148
|
+
push_text("#{open} ")
|
|
149
|
+
|
|
150
|
+
if @node.value[:parse]
|
|
151
|
+
push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
|
|
152
|
+
else
|
|
153
|
+
push_text(@node.value[:text])
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
push_text(" #{close}\n")
|
|
249
157
|
return
|
|
250
158
|
end
|
|
251
159
|
|
|
252
|
-
push_text(open
|
|
253
|
-
@output_tabs += 1
|
|
160
|
+
push_text("#{open}\n")
|
|
254
161
|
yield if block_given?
|
|
255
|
-
|
|
256
|
-
push_text(@node.value[:conditional] ? "<![endif]-->" : "-->", -1)
|
|
162
|
+
push_text("#{close}\n")
|
|
257
163
|
end
|
|
258
164
|
|
|
259
165
|
def compile_doctype
|
|
260
166
|
doctype = text_for_doctype
|
|
261
|
-
push_text
|
|
167
|
+
push_text("#{doctype}\n") if doctype
|
|
262
168
|
end
|
|
263
169
|
|
|
264
170
|
def compile_filter
|
|
265
|
-
unless filter =
|
|
171
|
+
unless (filter = @filters[@node.value[:name]])
|
|
266
172
|
name = @node.value[:name]
|
|
267
173
|
if ["maruku", "textile"].include?(name)
|
|
268
174
|
raise Error.new(Error.message(:install_haml_contrib, name), @node.line - 1)
|
|
@@ -276,36 +182,34 @@ END
|
|
|
276
182
|
def text_for_doctype
|
|
277
183
|
if @node.value[:type] == "xml"
|
|
278
184
|
return nil if @options.html?
|
|
279
|
-
wrapper = @options
|
|
185
|
+
wrapper = @options.attr_wrapper
|
|
280
186
|
return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
|
|
281
187
|
end
|
|
282
188
|
|
|
283
189
|
if @options.html5?
|
|
284
190
|
'<!DOCTYPE html>'
|
|
285
|
-
|
|
286
|
-
if @
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
else
|
|
292
|
-
case @node.value[:type]
|
|
293
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
|
294
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
|
295
|
-
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
|
296
|
-
when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
|
|
297
|
-
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
|
298
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
elsif @options.html4?
|
|
191
|
+
elsif @options.xhtml?
|
|
192
|
+
if @node.value[:version] == "1.1"
|
|
193
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
|
194
|
+
elsif @node.value[:version] == "5"
|
|
195
|
+
'<!DOCTYPE html>'
|
|
196
|
+
else
|
|
303
197
|
case @node.value[:type]
|
|
304
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD
|
|
305
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD
|
|
306
|
-
|
|
198
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
|
199
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
|
200
|
+
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
|
201
|
+
when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
|
|
202
|
+
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
|
203
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
|
307
204
|
end
|
|
308
205
|
end
|
|
206
|
+
|
|
207
|
+
elsif @options.html4?
|
|
208
|
+
case @node.value[:type]
|
|
209
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
|
210
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
|
211
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
|
212
|
+
end
|
|
309
213
|
end
|
|
310
214
|
end
|
|
311
215
|
|
|
@@ -315,83 +219,52 @@ END
|
|
|
315
219
|
flush_merged_text
|
|
316
220
|
return if can_suppress && @options.suppress_eval?
|
|
317
221
|
newline = (text == "end") ? ";" : "\n"
|
|
318
|
-
@
|
|
319
|
-
@output_line
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
# Adds `text` to `@buffer` with appropriate tabulation
|
|
323
|
-
# without parsing it.
|
|
324
|
-
def push_merged_text(text, tab_change = 0, indent = true)
|
|
325
|
-
text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
|
|
326
|
-
@to_merge << [:text, text, tab_change]
|
|
327
|
-
@dont_indent_next_line = false
|
|
222
|
+
@temple << [:code, "#{resolve_newlines}#{text}#{newline}"]
|
|
223
|
+
@output_line = @output_line + text.count("\n") + newline.count("\n")
|
|
328
224
|
end
|
|
329
225
|
|
|
330
|
-
#
|
|
331
|
-
def
|
|
332
|
-
@to_merge << [:text, text
|
|
226
|
+
# Adds `text` to `@buffer`.
|
|
227
|
+
def push_text(text)
|
|
228
|
+
@to_merge << [:text, text]
|
|
333
229
|
end
|
|
334
230
|
|
|
335
|
-
def
|
|
336
|
-
|
|
231
|
+
def push_temple(temple)
|
|
232
|
+
flush_merged_text
|
|
233
|
+
@temple.concat([[:newline]] * resolve_newlines.count("\n"))
|
|
234
|
+
@temple << temple
|
|
235
|
+
@output_line += TempleLineCounter.count_lines(temple)
|
|
337
236
|
end
|
|
338
237
|
|
|
339
238
|
def flush_merged_text
|
|
340
239
|
return if @to_merge.empty?
|
|
341
240
|
|
|
342
|
-
|
|
343
|
-
mtabs = 0
|
|
344
|
-
@to_merge.each do |type, val, tabs|
|
|
241
|
+
@to_merge.each do |type, val|
|
|
345
242
|
case type
|
|
346
243
|
when :text
|
|
347
|
-
|
|
348
|
-
mtabs += tabs
|
|
244
|
+
@temple << [:static, val]
|
|
349
245
|
when :script
|
|
350
|
-
|
|
351
|
-
val = "_hamlout.adjust_tabs(#{mtabs}); " + val
|
|
352
|
-
end
|
|
353
|
-
str << "\#{#{val}}"
|
|
354
|
-
mtabs = 0
|
|
246
|
+
@temple << [:dynamic, val]
|
|
355
247
|
else
|
|
356
248
|
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Compiler@to_merge.")
|
|
357
249
|
end
|
|
358
250
|
end
|
|
359
251
|
|
|
360
|
-
unless str.empty?
|
|
361
|
-
@precompiled <<
|
|
362
|
-
if @options[:ugly]
|
|
363
|
-
"_hamlout.buffer << \"#{str}\";"
|
|
364
|
-
else
|
|
365
|
-
"_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
|
|
366
|
-
end
|
|
367
|
-
end
|
|
368
252
|
@to_merge = []
|
|
369
|
-
@dont_tab_up_next_text = false
|
|
370
253
|
end
|
|
371
254
|
|
|
372
255
|
# Causes `text` to be evaluated in the context of
|
|
373
256
|
# the scope object and the result to be added to `@buffer`.
|
|
374
257
|
#
|
|
375
|
-
# If `opts[:preserve_script]` is true, Haml::Helpers#
|
|
258
|
+
# If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on
|
|
376
259
|
# the result before it is added to `@buffer`
|
|
377
260
|
def push_script(text, opts = {})
|
|
378
261
|
return if @options.suppress_eval?
|
|
379
262
|
|
|
380
|
-
|
|
381
|
-
args.map! {|name| opts[name.to_sym]}
|
|
382
|
-
args << !block_given? << @options[:ugly]
|
|
383
|
-
|
|
384
|
-
no_format = @options[:ugly] &&
|
|
385
|
-
!(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
|
|
386
|
-
output_expr = "(#{text}\n)"
|
|
387
|
-
static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
|
|
388
|
-
|
|
389
|
-
# Prerender tabulation unless we're in a tag
|
|
390
|
-
push_merged_text '' unless opts[:in_tag]
|
|
263
|
+
no_format = !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
|
|
391
264
|
|
|
392
265
|
unless block_given?
|
|
393
|
-
push_generated_script(no_format ? "#{text}\n" : "
|
|
394
|
-
|
|
266
|
+
push_generated_script(no_format ? "(#{text}\n).to_s" : build_script_formatter("(#{text}\n)", opts))
|
|
267
|
+
push_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
|
|
395
268
|
return
|
|
396
269
|
end
|
|
397
270
|
|
|
@@ -399,117 +272,35 @@ END
|
|
|
399
272
|
push_silent "haml_temp = #{text}"
|
|
400
273
|
yield
|
|
401
274
|
push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
|
|
402
|
-
@
|
|
403
|
-
concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly]
|
|
275
|
+
@temple << [:dynamic, no_format ? 'haml_temp.to_s;' : build_script_formatter('haml_temp', opts)]
|
|
404
276
|
end
|
|
405
277
|
|
|
406
|
-
def
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
# This is a class method so it can be accessed from Buffer.
|
|
412
|
-
def self.build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
|
|
413
|
-
# @TODO this is an absolutely ridiculous amount of arguments. At least
|
|
414
|
-
# some of this needs to be moved into an instance method.
|
|
415
|
-
quote_escape = attr_wrapper == '"' ? """ : "'"
|
|
416
|
-
other_quote_char = attr_wrapper == '"' ? "'" : '"'
|
|
417
|
-
join_char = hyphenate_data_attrs ? '-' : '_'
|
|
418
|
-
|
|
419
|
-
attributes.each do |key, value|
|
|
420
|
-
if value.is_a?(Hash)
|
|
421
|
-
data_attributes = attributes.delete(key)
|
|
422
|
-
data_attributes = flatten_data_attributes(data_attributes, '', join_char)
|
|
423
|
-
data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
|
|
424
|
-
attributes = data_attributes.merge(attributes)
|
|
425
|
-
end
|
|
278
|
+
def build_script_formatter(text, opts)
|
|
279
|
+
text = "(#{text}).to_s"
|
|
280
|
+
if opts[:escape_html]
|
|
281
|
+
text = "::Haml::Helpers.html_escape(#{text})"
|
|
426
282
|
end
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
next if value.nil?
|
|
430
|
-
|
|
431
|
-
value = filter_and_join(value, ' ') if attr == 'class'
|
|
432
|
-
value = filter_and_join(value, '_') if attr == 'id'
|
|
433
|
-
|
|
434
|
-
if value == true
|
|
435
|
-
next " #{attr}" if is_html
|
|
436
|
-
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
|
|
437
|
-
elsif value == false
|
|
438
|
-
next
|
|
439
|
-
end
|
|
440
|
-
|
|
441
|
-
escaped =
|
|
442
|
-
if escape_attrs == :once
|
|
443
|
-
Haml::Helpers.escape_once(value.to_s)
|
|
444
|
-
elsif escape_attrs
|
|
445
|
-
Haml::Helpers.html_escape(value.to_s)
|
|
446
|
-
else
|
|
447
|
-
value.to_s
|
|
448
|
-
end
|
|
449
|
-
value = Haml::Helpers.preserve(escaped)
|
|
450
|
-
if escape_attrs
|
|
451
|
-
# We want to decide whether or not to escape quotes
|
|
452
|
-
value.gsub!(/"|"/, '"')
|
|
453
|
-
this_attr_wrapper = attr_wrapper
|
|
454
|
-
if value.include? attr_wrapper
|
|
455
|
-
if value.include? other_quote_char
|
|
456
|
-
value.gsub!(attr_wrapper, quote_escape)
|
|
457
|
-
else
|
|
458
|
-
this_attr_wrapper = other_quote_char
|
|
459
|
-
end
|
|
460
|
-
end
|
|
461
|
-
else
|
|
462
|
-
this_attr_wrapper = attr_wrapper
|
|
463
|
-
end
|
|
464
|
-
" #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
|
|
283
|
+
if opts[:nuke_inner_whitespace]
|
|
284
|
+
text = "(#{text}).strip"
|
|
465
285
|
end
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
return "" if value == ""
|
|
471
|
-
value = [value] unless value.is_a?(Array)
|
|
472
|
-
value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
|
|
473
|
-
return !value.empty? && value
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def self.build_data_keys(data_hash, hyphenate, attr_name="data")
|
|
477
|
-
Hash[data_hash.map do |name, value|
|
|
478
|
-
if name == nil
|
|
479
|
-
[attr_name, value]
|
|
480
|
-
elsif hyphenate
|
|
481
|
-
["#{attr_name}-#{name.to_s.gsub(/_/, '-')}", value]
|
|
482
|
-
else
|
|
483
|
-
["#{attr_name}-#{name}", value]
|
|
484
|
-
end
|
|
485
|
-
end]
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
def self.flatten_data_attributes(data, key, join_char, seen = [])
|
|
489
|
-
return {key => data} unless data.is_a?(Hash)
|
|
490
|
-
|
|
491
|
-
return {key => nil} if seen.include? data.object_id
|
|
492
|
-
seen << data.object_id
|
|
493
|
-
|
|
494
|
-
data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, array|
|
|
495
|
-
k, v = array
|
|
496
|
-
joined = key == '' ? k : [key, k].join(join_char)
|
|
497
|
-
hash.merge! flatten_data_attributes(v, joined, join_char, seen)
|
|
286
|
+
if opts[:preserve_tag]
|
|
287
|
+
text = "_hamlout.fix_textareas!(::Haml::Helpers.preserve(#{text}))"
|
|
288
|
+
elsif opts[:preserve_script]
|
|
289
|
+
text = "_hamlout.fix_textareas!(::Haml::Helpers.find_and_preserve(#{text}, _hamlout.options[:preserve]))"
|
|
498
290
|
end
|
|
291
|
+
"#{text};"
|
|
499
292
|
end
|
|
500
293
|
|
|
501
|
-
def
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
@options.html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
|
|
505
|
-
"<#{name}#{attributes_string}#{self_close && @options.xhtml? ? ' /' : ''}>"
|
|
294
|
+
def push_generated_script(text)
|
|
295
|
+
@to_merge << [:script, resolve_newlines + text]
|
|
296
|
+
@output_line += text.count("\n")
|
|
506
297
|
end
|
|
507
298
|
|
|
508
299
|
def resolve_newlines
|
|
509
300
|
diff = @node.line - @output_line
|
|
510
301
|
return "" if diff <= 0
|
|
511
302
|
@output_line = @node.line
|
|
512
|
-
"\n" *
|
|
303
|
+
"\n" * diff
|
|
513
304
|
end
|
|
514
305
|
|
|
515
306
|
# Get rid of and whitespace at the end of the buffer
|
|
@@ -518,13 +309,12 @@ END
|
|
|
518
309
|
last = @to_merge[index]
|
|
519
310
|
if last.nil?
|
|
520
311
|
push_silent("_hamlout.rstrip!", false)
|
|
521
|
-
@dont_tab_up_next_text = true
|
|
522
312
|
return
|
|
523
313
|
end
|
|
524
314
|
|
|
525
315
|
case last.first
|
|
526
316
|
when :text
|
|
527
|
-
last[1].rstrip
|
|
317
|
+
last[1] = last[1].rstrip
|
|
528
318
|
if last[1].empty?
|
|
529
319
|
@to_merge.slice! index
|
|
530
320
|
rstrip_buffer! index
|