hamlit 2.9.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +45 -0
  4. data/CHANGELOG.md +666 -0
  5. data/Gemfile +28 -0
  6. data/LICENSE.txt +44 -0
  7. data/README.md +146 -0
  8. data/REFERENCE.md +266 -0
  9. data/Rakefile +117 -0
  10. data/benchmark/boolean_attribute.haml +6 -0
  11. data/benchmark/class_attribute.haml +5 -0
  12. data/benchmark/common_attribute.haml +3 -0
  13. data/benchmark/data_attribute.haml +4 -0
  14. data/benchmark/dynamic_attributes/boolean_attribute.haml +4 -0
  15. data/benchmark/dynamic_attributes/class_attribute.haml +4 -0
  16. data/benchmark/dynamic_attributes/common_attribute.haml +2 -0
  17. data/benchmark/dynamic_attributes/data_attribute.haml +2 -0
  18. data/benchmark/dynamic_attributes/id_attribute.haml +2 -0
  19. data/benchmark/dynamic_boolean_attribute.haml +4 -0
  20. data/benchmark/etc/attribute_builder.haml +5 -0
  21. data/benchmark/etc/real_sample.haml +888 -0
  22. data/benchmark/etc/real_sample.rb +11 -0
  23. data/benchmark/etc/static_analyzer.haml +1 -0
  24. data/benchmark/etc/string_interpolation.haml +2 -0
  25. data/benchmark/etc/tags.haml +3 -0
  26. data/benchmark/etc/tags_loop.haml +2 -0
  27. data/benchmark/ext/build_data.rb +17 -0
  28. data/benchmark/ext/build_id.rb +13 -0
  29. data/benchmark/id_attribute.haml +3 -0
  30. data/benchmark/plain.haml +4 -0
  31. data/benchmark/script.haml +4 -0
  32. data/benchmark/slim/LICENSE +21 -0
  33. data/benchmark/slim/context.rb +11 -0
  34. data/benchmark/slim/run-benchmarks.rb +94 -0
  35. data/benchmark/slim/view.erb +23 -0
  36. data/benchmark/slim/view.haml +18 -0
  37. data/benchmark/slim/view.slim +17 -0
  38. data/benchmark/utils/benchmark_ips_extension.rb +43 -0
  39. data/bin/bench +77 -0
  40. data/bin/console +11 -0
  41. data/bin/ruby +3 -0
  42. data/bin/setup +7 -0
  43. data/bin/stackprof +27 -0
  44. data/bin/test +24 -0
  45. data/exe/hamlit +6 -0
  46. data/ext/hamlit/extconf.rb +10 -0
  47. data/ext/hamlit/hamlit.c +553 -0
  48. data/ext/hamlit/hescape.c +108 -0
  49. data/ext/hamlit/hescape.h +20 -0
  50. data/hamlit.gemspec +45 -0
  51. data/lib/hamlit.rb +11 -0
  52. data/lib/hamlit/attribute_builder.rb +173 -0
  53. data/lib/hamlit/attribute_compiler.rb +123 -0
  54. data/lib/hamlit/attribute_parser.rb +110 -0
  55. data/lib/hamlit/cli.rb +130 -0
  56. data/lib/hamlit/compiler.rb +97 -0
  57. data/lib/hamlit/compiler/children_compiler.rb +112 -0
  58. data/lib/hamlit/compiler/comment_compiler.rb +36 -0
  59. data/lib/hamlit/compiler/doctype_compiler.rb +46 -0
  60. data/lib/hamlit/compiler/script_compiler.rb +101 -0
  61. data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
  62. data/lib/hamlit/compiler/tag_compiler.rb +74 -0
  63. data/lib/hamlit/engine.rb +37 -0
  64. data/lib/hamlit/error.rb +15 -0
  65. data/lib/hamlit/escapable.rb +13 -0
  66. data/lib/hamlit/filters.rb +75 -0
  67. data/lib/hamlit/filters/base.rb +12 -0
  68. data/lib/hamlit/filters/cdata.rb +20 -0
  69. data/lib/hamlit/filters/coffee.rb +17 -0
  70. data/lib/hamlit/filters/css.rb +33 -0
  71. data/lib/hamlit/filters/erb.rb +10 -0
  72. data/lib/hamlit/filters/escaped.rb +22 -0
  73. data/lib/hamlit/filters/javascript.rb +33 -0
  74. data/lib/hamlit/filters/less.rb +20 -0
  75. data/lib/hamlit/filters/markdown.rb +10 -0
  76. data/lib/hamlit/filters/plain.rb +29 -0
  77. data/lib/hamlit/filters/preserve.rb +22 -0
  78. data/lib/hamlit/filters/ruby.rb +10 -0
  79. data/lib/hamlit/filters/sass.rb +15 -0
  80. data/lib/hamlit/filters/scss.rb +15 -0
  81. data/lib/hamlit/filters/text_base.rb +25 -0
  82. data/lib/hamlit/filters/tilt_base.rb +49 -0
  83. data/lib/hamlit/force_escapable.rb +29 -0
  84. data/lib/hamlit/helpers.rb +15 -0
  85. data/lib/hamlit/html.rb +14 -0
  86. data/lib/hamlit/identity.rb +13 -0
  87. data/lib/hamlit/object_ref.rb +30 -0
  88. data/lib/hamlit/parser.rb +49 -0
  89. data/lib/hamlit/parser/MIT-LICENSE +20 -0
  90. data/lib/hamlit/parser/README.md +30 -0
  91. data/lib/hamlit/parser/haml_buffer.rb +348 -0
  92. data/lib/hamlit/parser/haml_compiler.rb +553 -0
  93. data/lib/hamlit/parser/haml_error.rb +61 -0
  94. data/lib/hamlit/parser/haml_helpers.rb +727 -0
  95. data/lib/hamlit/parser/haml_options.rb +286 -0
  96. data/lib/hamlit/parser/haml_parser.rb +800 -0
  97. data/lib/hamlit/parser/haml_util.rb +288 -0
  98. data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
  99. data/lib/hamlit/rails_helpers.rb +51 -0
  100. data/lib/hamlit/rails_template.rb +58 -0
  101. data/lib/hamlit/railtie.rb +10 -0
  102. data/lib/hamlit/ruby_expression.rb +32 -0
  103. data/lib/hamlit/string_splitter.rb +88 -0
  104. data/lib/hamlit/template.rb +28 -0
  105. data/lib/hamlit/utils.rb +18 -0
  106. data/lib/hamlit/version.rb +4 -0
  107. metadata +360 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2009 Hampton Catlin and Natalie Weizenbaum
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ # lib/hamlit/parser/haml\_\*.rb
2
+
3
+ Hamlit::HamlFoo is originally Haml::Foo in haml gem.
4
+
5
+ This directory is NOT intended to be updated other than following Haml's changes.
6
+
7
+ ## License
8
+
9
+ lib/hamlit/parser/haml\_\*.rb is:
10
+
11
+ Copyright (c) 2006-2009 Hampton Catlin and Natalie Weizenbaum
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining
14
+ a copy of this software and associated documentation files (the
15
+ "Software"), to deal in the Software without restriction, including
16
+ without limitation the rights to use, copy, modify, merge, publish,
17
+ distribute, sublicense, and/or sell copies of the Software, and to
18
+ permit persons to whom the Software is furnished to do so, subject to
19
+ the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be
22
+ included in all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,348 @@
1
+ require 'hamlit/parser/haml_helpers'
2
+ require 'hamlit/parser/haml_util'
3
+ require 'hamlit/parser/haml_compiler'
4
+
5
+ module Hamlit
6
+ # This class is used only internally. It holds the buffer of HTML that
7
+ # is eventually output as the resulting document.
8
+ # It's called from within the precompiled code,
9
+ # and helps reduce the amount of processing done within `instance_eval`ed code.
10
+ class HamlBuffer
11
+ ID_KEY = 'id'.freeze
12
+ CLASS_KEY = 'class'.freeze
13
+ DATA_KEY = 'data'.freeze
14
+
15
+ include ::Hamlit::HamlHelpers
16
+ include ::Hamlit::HamlUtil
17
+
18
+ # The string that holds the compiled HTML. This is aliased as
19
+ # `_erbout` for compatibility with ERB-specific code.
20
+ #
21
+ # @return [String]
22
+ attr_accessor :buffer
23
+
24
+ # The options hash passed in from {Haml::Engine}.
25
+ #
26
+ # @return [{String => Object}]
27
+ # @see Haml::Options#for_buffer
28
+ attr_accessor :options
29
+
30
+ # The {Buffer} for the enclosing Haml document.
31
+ # This is set for partials and similar sorts of nested templates.
32
+ # It's `nil` at the top level (see \{#toplevel?}).
33
+ #
34
+ # @return [Buffer]
35
+ attr_accessor :upper
36
+
37
+ # nil if there's no capture_haml block running,
38
+ # and the position at which it's beginning the capture if there is one.
39
+ #
40
+ # @return [Fixnum, nil]
41
+ attr_accessor :capture_position
42
+
43
+ # @return [Boolean]
44
+ # @see #active?
45
+ attr_writer :active
46
+
47
+ # @return [Boolean] Whether or not the format is XHTML
48
+ def xhtml?
49
+ not html?
50
+ end
51
+
52
+ # @return [Boolean] Whether or not the format is any flavor of HTML
53
+ def html?
54
+ html4? or html5?
55
+ end
56
+
57
+ # @return [Boolean] Whether or not the format is HTML4
58
+ def html4?
59
+ @options[:format] == :html4
60
+ end
61
+
62
+ # @return [Boolean] Whether or not the format is HTML5.
63
+ def html5?
64
+ @options[:format] == :html5
65
+ end
66
+
67
+ # @return [Boolean] Whether or not this buffer is a top-level template,
68
+ # as opposed to a nested partial
69
+ def toplevel?
70
+ upper.nil?
71
+ end
72
+
73
+ # Whether or not this buffer is currently being used to render a Haml template.
74
+ # Returns `false` if a subtemplate is being rendered,
75
+ # even if it's a subtemplate of this buffer's template.
76
+ #
77
+ # @return [Boolean]
78
+ def active?
79
+ @active
80
+ end
81
+
82
+ # @return [Fixnum] The current indentation level of the document
83
+ def tabulation
84
+ @real_tabs + @tabulation
85
+ end
86
+
87
+ # Sets the current tabulation of the document.
88
+ #
89
+ # @param val [Fixnum] The new tabulation
90
+ def tabulation=(val)
91
+ val = val - @real_tabs
92
+ @tabulation = val > -1 ? val : 0
93
+ end
94
+
95
+ # @param upper [Buffer] The parent buffer
96
+ # @param options [{Symbol => Object}] An options hash.
97
+ # See {Haml::Engine#options\_for\_buffer}
98
+ def initialize(upper = nil, options = {})
99
+ @active = true
100
+ @upper = upper
101
+ @options = options
102
+ @buffer = new_encoded_string
103
+ @tabulation = 0
104
+
105
+ # The number of tabs that Engine thinks we should have
106
+ # @real_tabs + @tabulation is the number of tabs actually output
107
+ @real_tabs = 0
108
+ end
109
+
110
+ # Appends text to the buffer, properly tabulated.
111
+ # Also modifies the document's indentation.
112
+ #
113
+ # @param text [String] The text to append
114
+ # @param tab_change [Fixnum] The number of tabs by which to increase
115
+ # or decrease the document's indentation
116
+ # @param dont_tab_up [Boolean] If true, don't indent the first line of `text`
117
+ def push_text(text, tab_change, dont_tab_up)
118
+ if @tabulation > 0
119
+ # Have to push every line in by the extra user set tabulation.
120
+ # Don't push lines with just whitespace, though,
121
+ # because that screws up precompiled indentation.
122
+ text.gsub!(/^(?!\s+$)/m, tabs)
123
+ text.sub!(tabs, '') if dont_tab_up
124
+ end
125
+
126
+ @real_tabs += tab_change
127
+ @buffer << text
128
+ end
129
+
130
+ # Modifies the indentation of the document.
131
+ #
132
+ # @param tab_change [Fixnum] The number of tabs by which to increase
133
+ # or decrease the document's indentation
134
+ def adjust_tabs(tab_change)
135
+ @real_tabs += tab_change
136
+ end
137
+
138
+ # the number of arguments here is insane, but passing in an options hash instead of named arguments
139
+ # causes a significant performance regression
140
+ def format_script(result, preserve_script, in_tag, preserve_tag, escape_html, nuke_inner_whitespace, interpolated, ugly)
141
+ result_name = escape_html ? html_escape(result.to_s) : result.to_s
142
+
143
+ if ugly
144
+ result = nuke_inner_whitespace ? result_name.strip : result_name
145
+ result = preserve(result, preserve_script, preserve_tag)
146
+ fix_textareas!(result) if toplevel? && result.include?('<textarea')
147
+ return result
148
+ end
149
+
150
+ # If we're interpolated,
151
+ # then the custom tabulation is handled in #push_text.
152
+ # The easiest way to avoid it here is to reset @tabulation.
153
+ if interpolated
154
+ old_tabulation = @tabulation
155
+ @tabulation = 0
156
+ end
157
+
158
+ in_tag_no_nuke = in_tag && !nuke_inner_whitespace
159
+ preserved_no_nuke = in_tag_no_nuke && preserve_tag
160
+ tabulation = !preserved_no_nuke && @real_tabs
161
+
162
+ result = nuke_inner_whitespace ? result_name.strip : result_name.rstrip
163
+ result = preserve(result, preserve_script, preserve_tag)
164
+
165
+ has_newline = !preserved_no_nuke && result.include?("\n")
166
+
167
+ if in_tag_no_nuke && (preserve_tag || !has_newline)
168
+ @real_tabs -= 1
169
+ @tabulation = old_tabulation if interpolated
170
+ return result
171
+ end
172
+
173
+ unless preserved_no_nuke
174
+ # Precompiled tabulation may be wrong
175
+ result = "#{tabs}#{result}" if !interpolated && !in_tag && @tabulation > 0
176
+
177
+ if has_newline
178
+ result.gsub! "\n", "\n#{tabs(tabulation)}"
179
+
180
+ # Add tabulation if it wasn't precompiled
181
+ result = "#{tabs(tabulation)}#{result}" if in_tag_no_nuke
182
+ end
183
+
184
+ fix_textareas!(result) if toplevel? && result.include?('<textarea')
185
+
186
+ if in_tag_no_nuke
187
+ result = "\n#{result}\n#{tabs(tabulation-1)}"
188
+ @real_tabs -= 1
189
+ end
190
+ @tabulation = old_tabulation if interpolated
191
+ result
192
+ end
193
+ end
194
+
195
+ def attributes(class_id, obj_ref, *attributes_hashes)
196
+ attributes = class_id
197
+ attributes_hashes.each do |old|
198
+ self.class.merge_attrs(attributes, Hash[old.map {|k, v| [k.to_s, v]}])
199
+ end
200
+ self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
201
+ ::Hamlit::HamlCompiler.build_attributes(
202
+ html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
203
+ end
204
+
205
+ # Remove the whitespace from the right side of the buffer string.
206
+ # Doesn't do anything if we're at the beginning of a capture_haml block.
207
+ def rstrip!
208
+ if capture_position.nil?
209
+ buffer.rstrip!
210
+ return
211
+ end
212
+
213
+ buffer << buffer.slice!(capture_position..-1).rstrip
214
+ end
215
+
216
+ # Merges two attribute hashes.
217
+ # This is the same as `to.merge!(from)`,
218
+ # except that it merges id, class, and data attributes.
219
+ #
220
+ # ids are concatenated with `"_"`,
221
+ # and classes are concatenated with `" "`.
222
+ # data hashes are simply merged.
223
+ #
224
+ # Destructively modifies both `to` and `from`.
225
+ #
226
+ # @param to [{String => String}] The attribute hash to merge into
227
+ # @param from [{String => #to_s}] The attribute hash to merge from
228
+ # @return [{String => String}] `to`, after being merged
229
+ def self.merge_attrs(to, from)
230
+ from[ID_KEY] = ::Hamlit::HamlCompiler.filter_and_join(from[ID_KEY], '_') if from[ID_KEY]
231
+ if to[ID_KEY] && from[ID_KEY]
232
+ to[ID_KEY] << "_#{from.delete(ID_KEY)}"
233
+ elsif to[ID_KEY] || from[ID_KEY]
234
+ from[ID_KEY] ||= to[ID_KEY]
235
+ end
236
+
237
+ from[CLASS_KEY] = ::Hamlit::HamlCompiler.filter_and_join(from[CLASS_KEY], ' ') if from[CLASS_KEY]
238
+ if to[CLASS_KEY] && from[CLASS_KEY]
239
+ # Make sure we don't duplicate class names
240
+ from[CLASS_KEY] = (from[CLASS_KEY].to_s.split(' ') | to[CLASS_KEY].split(' ')).sort.join(' ')
241
+ elsif to[CLASS_KEY] || from[CLASS_KEY]
242
+ from[CLASS_KEY] ||= to[CLASS_KEY]
243
+ end
244
+
245
+ from.keys.each do |key|
246
+ next unless from[key].kind_of?(Hash) || to[key].kind_of?(Hash)
247
+
248
+ from_data = from.delete(key)
249
+ # forces to_data & from_data into a hash
250
+ from_data = { nil => from_data } if from_data && !from_data.is_a?(Hash)
251
+ to[key] = { nil => to[key] } if to[key] && !to[key].is_a?(Hash)
252
+
253
+ if from_data && !to[key]
254
+ to[key] = from_data
255
+ elsif from_data && to[key]
256
+ to[key].merge! from_data
257
+ end
258
+ end
259
+
260
+ to.merge!(from)
261
+ end
262
+
263
+ private
264
+
265
+ def preserve(result, preserve_script, preserve_tag)
266
+ return ::Hamlit::HamlHelpers.preserve(result) if preserve_tag
267
+ return ::Hamlit::HamlHelpers.find_and_preserve(result, options[:preserve]) if preserve_script
268
+ result
269
+ end
270
+
271
+ # Works like #{find_and_preserve}, but allows the first newline after a
272
+ # preserved opening tag to remain unencoded, and then outdents the content.
273
+ # This change was motivated primarily by the change in Rails 3.2.3 to emit
274
+ # a newline after textarea helpers.
275
+ #
276
+ # @param input [String] The text to process
277
+ # @since Haml 4.0.1
278
+ # @private
279
+ def fix_textareas!(input)
280
+ pattern = /<(textarea)([^>]*)>(\n|&#x000A;)(.*?)<\/textarea>/im
281
+ input.gsub!(pattern) do |s|
282
+ match = pattern.match(s)
283
+ content = match[4]
284
+ if match[3] == '&#x000A;'
285
+ content.sub!(/\A /, '&#x0020;')
286
+ else
287
+ content.sub!(/\A[ ]*/, '')
288
+ end
289
+ "<#{match[1]}#{match[2]}>\n#{content}</#{match[1]}>"
290
+ end
291
+ end
292
+
293
+ def new_encoded_string
294
+ "".encode(Encoding.find(options[:encoding]))
295
+ end
296
+
297
+ @@tab_cache = {}
298
+ # Gets `count` tabs. Mostly for internal use.
299
+ def tabs(count = 0)
300
+ tabs = [count + @tabulation, 0].max
301
+ @@tab_cache[tabs] ||= ' ' * tabs
302
+ end
303
+
304
+ # Takes an array of objects and uses the class and id of the first
305
+ # one to create an attributes hash.
306
+ # The second object, if present, is used as a prefix,
307
+ # just like you can do with `dom_id()` and `dom_class()` in Rails
308
+ def parse_object_ref(ref)
309
+ prefix = ref[1]
310
+ ref = ref[0]
311
+ # Let's make sure the value isn't nil. If it is, return the default Hash.
312
+ return {} if ref.nil?
313
+ class_name =
314
+ if ref.respond_to?(:haml_object_ref)
315
+ ref.haml_object_ref
316
+ else
317
+ underscore(ref.class)
318
+ end
319
+ ref_id =
320
+ if ref.respond_to?(:to_key)
321
+ key = ref.to_key
322
+ key.join('_') unless key.nil?
323
+ else
324
+ ref.id
325
+ end
326
+ id = "#{class_name}_#{ref_id || 'new'}"
327
+ if prefix
328
+ class_name = "#{ prefix }_#{ class_name}"
329
+ id = "#{ prefix }_#{ id }"
330
+ end
331
+
332
+ {ID_KEY => id, CLASS_KEY => class_name}
333
+ end
334
+
335
+ # Changes a word from camel case to underscores.
336
+ # Based on the method of the same name in Rails' Inflector,
337
+ # but copied here so it'll run properly without Rails.
338
+ def underscore(camel_cased_word)
339
+ word = camel_cased_word.to_s.dup
340
+ word.gsub!(/::/, '_')
341
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
342
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
343
+ word.tr!('-', '_')
344
+ word.downcase!
345
+ word
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,553 @@
1
+ require 'hamlit/parser/haml_util'
2
+ require 'hamlit/parser/haml_parser'
3
+
4
+ module Hamlit
5
+ class HamlCompiler
6
+ include ::Hamlit::HamlUtil
7
+
8
+ attr_accessor :options
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ @output_tabs = 0
13
+ @to_merge = []
14
+ @precompiled = ''
15
+ @node = nil
16
+ end
17
+
18
+ def compile(node)
19
+ parent, @node = @node, node
20
+ if node.children.empty?
21
+ send(:"compile_#{node.type}")
22
+ else
23
+ send(:"compile_#{node.type}") {node.children.each {|c| compile c}}
24
+ end
25
+ ensure
26
+ @node = parent
27
+ end
28
+
29
+ # The source code that is evaluated to produce the Haml document.
30
+ #
31
+ # 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
+ encoding = Encoding.find(@options.encoding)
37
+ return @precompiled.force_encoding(encoding) if encoding == Encoding::ASCII_8BIT
38
+ return @precompiled.encode(encoding)
39
+ end
40
+
41
+ def precompiled_with_return_value
42
+ "#{precompiled};#{precompiled_method_return_value}"
43
+ end
44
+
45
+ # Returns the precompiled string with the preamble and postamble.
46
+ #
47
+ # Initializes to ActionView::OutputBuffer when available; this is necessary
48
+ # to avoid ordering issues with partial layouts in Rails. If not available,
49
+ # initializes to nil.
50
+ def precompiled_with_ambles(local_names)
51
+ preamble = <<END.tr!("\n", ';')
52
+ begin
53
+ extend ::Hamlit::HamlHelpers
54
+ _hamlout = @haml_buffer = ::Hamlit::HamlBuffer.new(haml_buffer, #{options.for_buffer.inspect})
55
+ _erbout = _hamlout.buffer
56
+ @output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil
57
+ END
58
+ postamble = <<END.tr!("\n", ';')
59
+ #{precompiled_method_return_value}
60
+ ensure
61
+ @haml_buffer = @haml_buffer.upper if @haml_buffer
62
+ end
63
+ END
64
+ "#{preamble}#{locals_code(local_names)}#{precompiled}#{postamble}"
65
+ end
66
+
67
+ private
68
+
69
+ # Returns the string used as the return value of the precompiled method.
70
+ # This method exists so it can be monkeypatched to return modified values.
71
+ def precompiled_method_return_value
72
+ "_erbout"
73
+ end
74
+
75
+ def locals_code(names)
76
+ names = names.keys if Hash === names
77
+
78
+ names.each_with_object('') do |name, code|
79
+ # Can't use || because someone might explicitly pass in false with a symbol
80
+ sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
81
+ str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
82
+ code << "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local};"
83
+ end
84
+ end
85
+
86
+ def compile_root
87
+ @dont_indent_next_line = @dont_tab_up_next_text = false
88
+ @output_line = 1
89
+ yield if block_given?
90
+ flush_merged_text
91
+ end
92
+
93
+ def compile_plain
94
+ push_text @node.value[:text]
95
+ end
96
+
97
+ def nuke_inner_whitespace?(node)
98
+ if node.value && node.value[:nuke_inner_whitespace]
99
+ true
100
+ elsif node.parent
101
+ nuke_inner_whitespace?(node.parent)
102
+ else
103
+ false
104
+ end
105
+ end
106
+
107
+ def compile_script(&block)
108
+ push_script(@node.value[:text],
109
+ :preserve_script => @node.value[:preserve],
110
+ :escape_html => @node.value[:escape_html],
111
+ :nuke_inner_whitespace => nuke_inner_whitespace?(@node),
112
+ &block)
113
+ end
114
+
115
+ def compile_silent_script
116
+ return if @options.suppress_eval
117
+ push_silent(@node.value[:text])
118
+ keyword = @node.value[:keyword]
119
+
120
+ if block_given?
121
+ # Store these values because for conditional statements,
122
+ # we want to restore them for each branch
123
+ @node.value[:dont_indent_next_line] = @dont_indent_next_line
124
+ @node.value[:dont_tab_up_next_text] = @dont_tab_up_next_text
125
+ yield
126
+ push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
127
+ elsif keyword == "end"
128
+ if @node.parent.children.last.equal?(@node)
129
+ # Since this "end" is ending the block,
130
+ # we don't need to generate an additional one
131
+ @node.parent.value[:dont_push_end] = true
132
+ end
133
+ # Don't restore dont_* for end because it isn't a conditional branch.
134
+ elsif ::Hamlit::HamlParser::MID_BLOCK_KEYWORDS.include?(keyword)
135
+ # Restore dont_* for this conditional branch
136
+ @dont_indent_next_line = @node.parent.value[:dont_indent_next_line]
137
+ @dont_tab_up_next_text = @node.parent.value[:dont_tab_up_next_text]
138
+ end
139
+ end
140
+
141
+ def compile_haml_comment; end
142
+
143
+ def compile_tag
144
+ t = @node.value
145
+
146
+ # Get rid of whitespace outside of the tag if we need to
147
+ rstrip_buffer! if t[:nuke_outer_whitespace]
148
+
149
+ dont_indent_next_line =
150
+ (t[:nuke_outer_whitespace] && !block_given?) ||
151
+ (t[:nuke_inner_whitespace] && block_given?)
152
+
153
+ if @options.suppress_eval
154
+ object_ref = :nil
155
+ parse = false
156
+ value = t[:parse] ? nil : t[:value]
157
+ attributes_hashes = {}
158
+ preserve_script = false
159
+ else
160
+ object_ref = t[:object_ref]
161
+ parse = t[:parse]
162
+ value = t[:value]
163
+ attributes_hashes = t[:attributes_hashes]
164
+ preserve_script = t[:preserve_script]
165
+ end
166
+
167
+ if @options[:trace]
168
+ t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
169
+ end
170
+
171
+ # Check if we can render the tag directly to text and not process it in the buffer
172
+ if (object_ref == :nil) && attributes_hashes.empty? && !preserve_script
173
+ tag_closed = !block_given? && !t[:self_closing] && !parse
174
+
175
+ open_tag = prerender_tag(t[:name], t[:self_closing], t[:attributes])
176
+ if tag_closed
177
+ open_tag << "#{value}</#{t[:name]}>"
178
+ open_tag << "\n" unless t[:nuke_outer_whitespace]
179
+ elsif !(parse || t[:nuke_inner_whitespace] ||
180
+ (t[:self_closing] && t[:nuke_outer_whitespace]))
181
+ open_tag << "\n"
182
+ end
183
+
184
+ push_merged_text(open_tag,
185
+ tag_closed || t[:self_closing] || t[:nuke_inner_whitespace] ? 0 : 1,
186
+ !t[:nuke_outer_whitespace])
187
+
188
+ @dont_indent_next_line = dont_indent_next_line
189
+ return if tag_closed
190
+ else
191
+ if attributes_hashes.empty?
192
+ attributes_hashes = ''
193
+ elsif attributes_hashes.size == 1
194
+ attributes_hashes = ", #{attributes_hashes.first}"
195
+ else
196
+ attributes_hashes = ", #{attributes_hashes.join(", ")}"
197
+ end
198
+
199
+ push_merged_text "<#{t[:name]}", 0, !t[:nuke_outer_whitespace]
200
+ push_generated_script(
201
+ "_hamlout.attributes(#{inspect_obj(t[:attributes])}, #{object_ref}#{attributes_hashes})")
202
+ concat_merged_text(
203
+ if t[:self_closing] && @options.xhtml?
204
+ " />#{"\n" unless t[:nuke_outer_whitespace]}"
205
+ else
206
+ ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
207
+ end)
208
+
209
+ if value && !parse
210
+ concat_merged_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
211
+ elsif !t[:nuke_inner_whitespace] && !t[:self_closing]
212
+ @to_merge << [:text, '', 1]
213
+ end
214
+
215
+ @dont_indent_next_line = dont_indent_next_line
216
+ end
217
+
218
+ return if t[:self_closing]
219
+
220
+ if value.nil?
221
+ @output_tabs += 1 unless t[:nuke_inner_whitespace]
222
+ yield if block_given?
223
+ @output_tabs -= 1 unless t[:nuke_inner_whitespace]
224
+ rstrip_buffer! if t[:nuke_inner_whitespace]
225
+ push_merged_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}",
226
+ t[:nuke_inner_whitespace] ? 0 : -1, !t[:nuke_inner_whitespace])
227
+ @dont_indent_next_line = t[:nuke_outer_whitespace]
228
+ return
229
+ end
230
+
231
+ if parse
232
+ push_script(value, t.merge(:in_tag => true))
233
+ concat_merged_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
234
+ end
235
+ end
236
+
237
+ def compile_comment
238
+ condition = "#{@node.value[:conditional]}>" if @node.value[:conditional]
239
+ revealed = @node.value[:revealed]
240
+
241
+ open = "<!--#{condition}#{'<!-->' if revealed}"
242
+
243
+ close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
244
+
245
+ unless block_given?
246
+ push_merged_text("#{open} ")
247
+
248
+ if @node.value[:parse]
249
+ push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
250
+ else
251
+ push_merged_text(@node.value[:text], 0, false)
252
+ end
253
+
254
+ push_merged_text(" #{close}\n", 0, false)
255
+ return
256
+ end
257
+
258
+ push_text(open, 1)
259
+ @output_tabs += 1
260
+ yield if block_given?
261
+ @output_tabs -= 1
262
+ push_text(close, -1)
263
+ end
264
+
265
+ def compile_doctype
266
+ doctype = text_for_doctype
267
+ push_text doctype if doctype
268
+ end
269
+
270
+ def compile_filter
271
+ unless filter = Filters.defined[@node.value[:name]]
272
+ name = @node.value[:name]
273
+ if ["maruku", "textile"].include?(name)
274
+ raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:install_haml_contrib, name), @node.line - 1)
275
+ else
276
+ raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:filter_not_defined, name), @node.line - 1)
277
+ end
278
+ end
279
+ filter.internal_compile(self, @node.value[:text])
280
+ end
281
+
282
+ def text_for_doctype
283
+ if @node.value[:type] == "xml"
284
+ return nil if @options.html?
285
+ wrapper = @options.attr_wrapper
286
+ return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
287
+ end
288
+
289
+ if @options.html5?
290
+ '<!DOCTYPE html>'
291
+ else
292
+ if @options.xhtml?
293
+ if @node.value[:version] == "1.1"
294
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
295
+ elsif @node.value[:version] == "5"
296
+ '<!DOCTYPE html>'
297
+ else
298
+ case @node.value[:type]
299
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
300
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
301
+ when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
302
+ when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
303
+ when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
304
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
305
+ end
306
+ end
307
+
308
+ elsif @options.html4?
309
+ case @node.value[:type]
310
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
311
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
312
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
313
+ end
314
+ end
315
+ end
316
+ end
317
+
318
+ # Evaluates `text` in the context of the scope object, but
319
+ # does not output the result.
320
+ def push_silent(text, can_suppress = false)
321
+ flush_merged_text
322
+ return if can_suppress && @options.suppress_eval?
323
+ newline = (text == "end") ? ";" : "\n"
324
+ @precompiled << "#{resolve_newlines}#{text}#{newline}"
325
+ @output_line = @output_line + text.count("\n") + newline.count("\n")
326
+ end
327
+
328
+ # Adds `text` to `@buffer` with appropriate tabulation
329
+ # without parsing it.
330
+ def push_merged_text(text, tab_change = 0, indent = true)
331
+ text = !indent || @dont_indent_next_line || @options.ugly ? text : "#{' ' * @output_tabs}#{text}"
332
+ @to_merge << [:text, text, tab_change]
333
+ @dont_indent_next_line = false
334
+ end
335
+
336
+ # Concatenate `text` to `@buffer` without tabulation.
337
+ def concat_merged_text(text)
338
+ @to_merge << [:text, text, 0]
339
+ end
340
+
341
+ def push_text(text, tab_change = 0)
342
+ push_merged_text("#{text}\n", tab_change)
343
+ end
344
+
345
+ def flush_merged_text
346
+ return if @to_merge.empty?
347
+
348
+ mtabs = 0
349
+ @to_merge.map! do |type, val, tabs|
350
+ case type
351
+ when :text
352
+ mtabs += tabs
353
+ inspect_obj(val)[1...-1]
354
+ when :script
355
+ if mtabs != 0 && !@options.ugly
356
+ val = "_hamlout.adjust_tabs(#{mtabs}); " + val
357
+ end
358
+ mtabs = 0
359
+ "\#{#{val}}"
360
+ else
361
+ raise ::Hamlit::HamlSyntaxError.new("[HAML BUG] Undefined entry in ::Hamlit::HamlCompiler@to_merge.")
362
+ end
363
+ end
364
+ str = @to_merge.join
365
+
366
+ unless str.empty?
367
+ @precompiled <<
368
+ if @options.ugly
369
+ "_hamlout.buffer << \"#{str}\";"
370
+ else
371
+ "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
372
+ end
373
+ end
374
+ @to_merge = []
375
+ @dont_tab_up_next_text = false
376
+ end
377
+
378
+ # Causes `text` to be evaluated in the context of
379
+ # the scope object and the result to be added to `@buffer`.
380
+ #
381
+ # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on
382
+ # the result before it is added to `@buffer`
383
+ def push_script(text, opts = {})
384
+ return if @options.suppress_eval?
385
+
386
+ args = [:preserve_script, :in_tag, :preserve_tag, :escape_html, :nuke_inner_whitespace]
387
+ args.map! {|name| !!opts[name]}
388
+ args << !block_given? << @options.ugly
389
+
390
+ no_format = @options.ugly &&
391
+ !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
392
+
393
+ # Prerender tabulation unless we're in a tag
394
+ push_merged_text '' unless opts[:in_tag]
395
+
396
+ unless block_given?
397
+ format_script_method = "_hamlout.format_script((#{text}\n),#{args.join(',')});"
398
+ push_generated_script(no_format ? "#{text}\n" : format_script_method)
399
+ concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
400
+ return
401
+ end
402
+
403
+ flush_merged_text
404
+ push_silent "haml_temp = #{text}"
405
+ yield
406
+ push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
407
+ format_script_method = "_hamlout.format_script(haml_temp,#{args.join(',')});"
408
+ @precompiled << "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : format_script_method}"
409
+ concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options.ugly
410
+ end
411
+
412
+ def push_generated_script(text)
413
+ @to_merge << [:script, resolve_newlines + text]
414
+ @output_line += text.count("\n")
415
+ end
416
+
417
+ # This is a class method so it can be accessed from Buffer.
418
+ def self.build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
419
+ # @TODO this is an absolutely ridiculous amount of arguments. At least
420
+ # some of this needs to be moved into an instance method.
421
+ quote_escape = attr_wrapper == '"' ? "&#x0022;" : "&#x0027;"
422
+ other_quote_char = attr_wrapper == '"' ? "'" : '"'
423
+ join_char = hyphenate_data_attrs ? '-' : '_'
424
+
425
+ attributes.each do |key, value|
426
+ if value.is_a?(Hash)
427
+ data_attributes = attributes.delete(key)
428
+ data_attributes = flatten_data_attributes(data_attributes, '', join_char)
429
+ data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
430
+ attributes = data_attributes.merge(attributes)
431
+ end
432
+ end
433
+
434
+ result = attributes.collect do |attr, value|
435
+ next if value.nil?
436
+
437
+ value = filter_and_join(value, ' ') if attr == 'class'
438
+ value = filter_and_join(value, '_') if attr == 'id'
439
+
440
+ if value == true
441
+ next " #{attr}" if is_html
442
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
443
+ elsif value == false
444
+ next
445
+ end
446
+
447
+ escaped =
448
+ if escape_attrs == :once
449
+ ::Hamlit::HamlHelpers.escape_once(value.to_s)
450
+ elsif escape_attrs
451
+ ::Hamlit::HamlHelpers.html_escape(value.to_s)
452
+ else
453
+ value.to_s
454
+ end
455
+ value = ::Hamlit::HamlHelpers.preserve(escaped)
456
+ if escape_attrs
457
+ # We want to decide whether or not to escape quotes
458
+ value.gsub!(/&quot;|&#x0022;/, '"')
459
+ this_attr_wrapper = attr_wrapper
460
+ if value.include? attr_wrapper
461
+ if value.include? other_quote_char
462
+ value.gsub!(attr_wrapper, quote_escape)
463
+ else
464
+ this_attr_wrapper = other_quote_char
465
+ end
466
+ end
467
+ else
468
+ this_attr_wrapper = attr_wrapper
469
+ end
470
+ " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
471
+ end
472
+ result.compact!
473
+ result.sort!
474
+ result.join
475
+ end
476
+
477
+ def self.filter_and_join(value, separator)
478
+ return '' if (value.respond_to?(:empty?) && value.empty?)
479
+
480
+ if value.is_a?(Array)
481
+ value.flatten!
482
+ value.map! {|item| item ? item.to_s : nil}
483
+ value.compact!
484
+ value = value.join(separator)
485
+ else
486
+ value = value ? value.to_s : nil
487
+ end
488
+ !value.nil? && !value.empty? && value
489
+ end
490
+
491
+ def self.build_data_keys(data_hash, hyphenate, attr_name="data")
492
+ Hash[data_hash.map do |name, value|
493
+ if name == nil
494
+ [attr_name, value]
495
+ elsif hyphenate
496
+ ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
497
+ else
498
+ ["#{attr_name}-#{name}", value]
499
+ end
500
+ end]
501
+ end
502
+
503
+ def self.flatten_data_attributes(data, key, join_char, seen = [])
504
+ return {key => data} unless data.is_a?(Hash)
505
+
506
+ return {key => nil} if seen.include? data.object_id
507
+ seen << data.object_id
508
+
509
+ data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
510
+ joined = key == '' ? k : [key, k].join(join_char)
511
+ hash.merge! flatten_data_attributes(v, joined, join_char, seen)
512
+ end
513
+ end
514
+
515
+ def prerender_tag(name, self_close, attributes)
516
+ attributes_string = ::Hamlit::HamlCompiler.build_attributes(
517
+ @options.html?, @options.attr_wrapper, @options.escape_attrs, @options.hyphenate_data_attrs, attributes)
518
+ "<#{name}#{attributes_string}#{self_close && @options.xhtml? ? ' /' : ''}>"
519
+ end
520
+
521
+ def resolve_newlines
522
+ diff = @node.line - @output_line
523
+ return "" if diff <= 0
524
+ @output_line = @node.line
525
+ "\n" * diff
526
+ end
527
+
528
+ # Get rid of and whitespace at the end of the buffer
529
+ # or the merged text
530
+ def rstrip_buffer!(index = -1)
531
+ last = @to_merge[index]
532
+ if last.nil?
533
+ push_silent("_hamlout.rstrip!", false)
534
+ @dont_tab_up_next_text = true
535
+ return
536
+ end
537
+
538
+ case last.first
539
+ when :text
540
+ last[1].rstrip!
541
+ if last[1].empty?
542
+ @to_merge.slice! index
543
+ rstrip_buffer! index
544
+ end
545
+ when :script
546
+ last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
547
+ rstrip_buffer! index - 1
548
+ else
549
+ raise ::Hamlit::HamlSyntaxError.new("[HAML BUG] Undefined entry in ::Hamlit::HamlCompiler@to_merge.")
550
+ end
551
+ end
552
+ end
553
+ end