merbjedi-haml 2.1.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.
- data/FAQ +138 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +332 -0
- data/REVISION +1 -0
- data/Rakefile +184 -0
- data/VERSION +1 -0
- data/bin/css2sass +7 -0
- data/bin/haml +9 -0
- data/bin/html2haml +7 -0
- data/bin/sass +8 -0
- data/extra/haml-mode.el +434 -0
- data/extra/sass-mode.el +98 -0
- data/init.rb +8 -0
- data/lib/haml.rb +1025 -0
- data/lib/haml/buffer.rb +255 -0
- data/lib/haml/engine.rb +268 -0
- data/lib/haml/error.rb +22 -0
- data/lib/haml/exec.rb +395 -0
- data/lib/haml/filters.rb +276 -0
- data/lib/haml/helpers.rb +465 -0
- data/lib/haml/helpers/action_view_extensions.rb +45 -0
- data/lib/haml/helpers/action_view_mods.rb +181 -0
- data/lib/haml/html.rb +218 -0
- data/lib/haml/precompiler.rb +896 -0
- data/lib/haml/shared.rb +45 -0
- data/lib/haml/template.rb +51 -0
- data/lib/haml/template/patch.rb +58 -0
- data/lib/haml/template/plugin.rb +72 -0
- data/lib/haml/util.rb +77 -0
- data/lib/haml/version.rb +47 -0
- data/lib/sass.rb +1062 -0
- data/lib/sass/css.rb +388 -0
- data/lib/sass/engine.rb +501 -0
- data/lib/sass/environment.rb +33 -0
- data/lib/sass/error.rb +35 -0
- data/lib/sass/plugin.rb +203 -0
- data/lib/sass/plugin/merb.rb +56 -0
- data/lib/sass/plugin/rails.rb +24 -0
- data/lib/sass/repl.rb +44 -0
- data/lib/sass/script.rb +38 -0
- data/lib/sass/script/bool.rb +13 -0
- data/lib/sass/script/color.rb +97 -0
- data/lib/sass/script/funcall.rb +28 -0
- data/lib/sass/script/functions.rb +122 -0
- data/lib/sass/script/lexer.rb +144 -0
- data/lib/sass/script/literal.rb +60 -0
- data/lib/sass/script/number.rb +231 -0
- data/lib/sass/script/operation.rb +30 -0
- data/lib/sass/script/parser.rb +142 -0
- data/lib/sass/script/string.rb +42 -0
- data/lib/sass/script/unary_operation.rb +21 -0
- data/lib/sass/script/variable.rb +20 -0
- data/lib/sass/tree/attr_node.rb +64 -0
- data/lib/sass/tree/comment_node.rb +30 -0
- data/lib/sass/tree/debug_node.rb +22 -0
- data/lib/sass/tree/directive_node.rb +50 -0
- data/lib/sass/tree/file_node.rb +27 -0
- data/lib/sass/tree/for_node.rb +29 -0
- data/lib/sass/tree/if_node.rb +27 -0
- data/lib/sass/tree/mixin_def_node.rb +18 -0
- data/lib/sass/tree/mixin_node.rb +34 -0
- data/lib/sass/tree/node.rb +97 -0
- data/lib/sass/tree/rule_node.rb +120 -0
- data/lib/sass/tree/variable_node.rb +24 -0
- data/lib/sass/tree/while_node.rb +20 -0
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +99 -0
- data/test/haml/engine_test.rb +852 -0
- data/test/haml/helper_test.rb +224 -0
- data/test/haml/html2haml_test.rb +92 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/mocks/article.rb +6 -0
- data/test/haml/results/content_for_layout.xhtml +15 -0
- data/test/haml/results/eval_suppressed.xhtml +9 -0
- data/test/haml/results/filters.xhtml +62 -0
- data/test/haml/results/helpers.xhtml +93 -0
- data/test/haml/results/helpful.xhtml +10 -0
- data/test/haml/results/just_stuff.xhtml +68 -0
- data/test/haml/results/list.xhtml +12 -0
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +20 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/results/partials.xhtml +21 -0
- data/test/haml/results/render_layout.xhtml +3 -0
- data/test/haml/results/silent_script.xhtml +74 -0
- data/test/haml/results/standard.xhtml +42 -0
- data/test/haml/results/tag_parsing.xhtml +23 -0
- data/test/haml/results/very_basic.xhtml +5 -0
- data/test/haml/results/whitespace_handling.xhtml +89 -0
- data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
- data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
- data/test/haml/rhtml/action_view.rhtml +62 -0
- data/test/haml/rhtml/standard.rhtml +54 -0
- data/test/haml/template_test.rb +204 -0
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
- data/test/haml/templates/_layout.erb +3 -0
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/_partial.haml +8 -0
- data/test/haml/templates/_text_area.haml +3 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/action_view_ugly.haml +47 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/haml/templates/content_for_layout.haml +10 -0
- data/test/haml/templates/eval_suppressed.haml +11 -0
- data/test/haml/templates/filters.haml +66 -0
- data/test/haml/templates/helpers.haml +95 -0
- data/test/haml/templates/helpful.haml +11 -0
- data/test/haml/templates/just_stuff.haml +83 -0
- data/test/haml/templates/list.haml +12 -0
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/haml/templates/partial_layout.haml +3 -0
- data/test/haml/templates/partialize.haml +1 -0
- data/test/haml/templates/partials.haml +12 -0
- data/test/haml/templates/render_layout.haml +2 -0
- data/test/haml/templates/silent_script.haml +40 -0
- data/test/haml/templates/standard.haml +42 -0
- data/test/haml/templates/standard_ugly.haml +42 -0
- data/test/haml/templates/tag_parsing.haml +21 -0
- data/test/haml/templates/very_basic.haml +4 -0
- data/test/haml/templates/whitespace_handling.haml +87 -0
- data/test/linked_rails.rb +12 -0
- data/test/sass/css2sass_test.rb +193 -0
- data/test/sass/engine_test.rb +752 -0
- data/test/sass/functions_test.rb +96 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +208 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +87 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/import.css +29 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +152 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +309 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/import.sass +11 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +21 -0
- metadata +273 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'haml/helpers/action_view_mods'
|
2
|
+
|
3
|
+
if defined?(ActionView)
|
4
|
+
module Haml
|
5
|
+
module Helpers
|
6
|
+
# This module contains various useful helper methods
|
7
|
+
# that either tie into ActionView or the rest of the ActionPack stack,
|
8
|
+
# or are only useful in that context.
|
9
|
+
# Thus, the methods defined here are only available
|
10
|
+
# if ActionView is installed.
|
11
|
+
module ActionViewExtensions
|
12
|
+
# Returns a value for the "class" attribute
|
13
|
+
# unique to this controller/action pair.
|
14
|
+
# This can be used to target styles specifically at this action or controller.
|
15
|
+
# For example, if the current action were EntryController#show,
|
16
|
+
#
|
17
|
+
# %div{:class => page_class} My Div
|
18
|
+
#
|
19
|
+
# would become
|
20
|
+
#
|
21
|
+
# <div class="entry show">My Div</div>
|
22
|
+
#
|
23
|
+
# Then, in a stylesheet
|
24
|
+
# (shown here as Sass),
|
25
|
+
# you could refer to this specific action:
|
26
|
+
#
|
27
|
+
# .entry.show
|
28
|
+
# :font-weight bold
|
29
|
+
#
|
30
|
+
# or to all actions in the entry controller:
|
31
|
+
#
|
32
|
+
# .entry
|
33
|
+
# :color #00f
|
34
|
+
#
|
35
|
+
def page_class
|
36
|
+
controller.controller_name + " " + controller.action_name
|
37
|
+
end
|
38
|
+
|
39
|
+
# :stopdoc:
|
40
|
+
alias_method :generate_content_class_names, :page_class
|
41
|
+
# :startdoc:
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
if defined?(ActionView) and not defined?(Merb::Plugins)
|
2
|
+
module ActionView
|
3
|
+
class Base # :nodoc:
|
4
|
+
def render_with_haml(*args, &block)
|
5
|
+
options = args.first
|
6
|
+
|
7
|
+
# If render :layout is used with a block,
|
8
|
+
# it concats rather than returning a string
|
9
|
+
# so we need it to keep thinking it's Haml
|
10
|
+
# until it hits the sub-render
|
11
|
+
if is_haml? && !(options.is_a?(Hash) && options[:layout] && block_given?)
|
12
|
+
return non_haml { render_without_haml(*args, &block) }
|
13
|
+
end
|
14
|
+
render_without_haml(*args, &block)
|
15
|
+
end
|
16
|
+
alias_method :render_without_haml, :render
|
17
|
+
alias_method :render, :render_with_haml
|
18
|
+
|
19
|
+
# Rails >2.1
|
20
|
+
if Haml::Util.has?(:instance_method, self, :output_buffer)
|
21
|
+
def output_buffer_with_haml
|
22
|
+
return haml_buffer.buffer if is_haml?
|
23
|
+
output_buffer_without_haml
|
24
|
+
end
|
25
|
+
alias_method :output_buffer_without_haml, :output_buffer
|
26
|
+
alias_method :output_buffer, :output_buffer_with_haml
|
27
|
+
|
28
|
+
def set_output_buffer_with_haml(new)
|
29
|
+
if is_haml?
|
30
|
+
haml_buffer.buffer = new
|
31
|
+
else
|
32
|
+
set_output_buffer_without_haml new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
alias_method :set_output_buffer_without_haml, :output_buffer=
|
36
|
+
alias_method :output_buffer=, :set_output_buffer_with_haml
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# This overrides various helpers in ActionView
|
41
|
+
# to make them work more effectively with Haml.
|
42
|
+
module Helpers
|
43
|
+
# :stopdoc:
|
44
|
+
# In Rails <=2.1, we've got to override considerable capturing infrastructure.
|
45
|
+
# In Rails >2.1, we can make do with only overriding #capture
|
46
|
+
# (which no longer behaves differently in helper contexts).
|
47
|
+
unless Haml::Util.has?(:instance_method, ActionView::Base, :output_buffer)
|
48
|
+
module CaptureHelper
|
49
|
+
def capture_with_haml(*args, &block)
|
50
|
+
# Rails' #capture helper will just return the value of the block
|
51
|
+
# if it's not actually in the template context,
|
52
|
+
# as detected by the existance of an _erbout variable.
|
53
|
+
# We've got to do the same thing for compatibility.
|
54
|
+
|
55
|
+
if is_haml? && block_is_haml?(block)
|
56
|
+
capture_haml(*args, &block)
|
57
|
+
else
|
58
|
+
capture_without_haml(*args, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias_method :capture_without_haml, :capture
|
62
|
+
alias_method :capture, :capture_with_haml
|
63
|
+
|
64
|
+
def capture_erb_with_buffer_with_haml(buffer, *args, &block)
|
65
|
+
if is_haml?
|
66
|
+
capture_haml(*args, &block)
|
67
|
+
else
|
68
|
+
capture_erb_with_buffer_without_haml(buffer, *args, &block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
alias_method :capture_erb_with_buffer_without_haml, :capture_erb_with_buffer
|
72
|
+
alias_method :capture_erb_with_buffer, :capture_erb_with_buffer_with_haml
|
73
|
+
end
|
74
|
+
|
75
|
+
module TextHelper
|
76
|
+
def concat_with_haml(string, binding = nil)
|
77
|
+
if is_haml?
|
78
|
+
haml_buffer.buffer.concat(string)
|
79
|
+
else
|
80
|
+
concat_without_haml(string, binding)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
alias_method :concat_without_haml, :concat
|
84
|
+
alias_method :concat, :concat_with_haml
|
85
|
+
end
|
86
|
+
else
|
87
|
+
module CaptureHelper
|
88
|
+
def capture_with_haml(*args, &block)
|
89
|
+
if Haml::Helpers.block_is_haml?(block)
|
90
|
+
capture_haml(*args, &block)
|
91
|
+
else
|
92
|
+
capture_without_haml(*args, &block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
alias_method :capture_without_haml, :capture
|
96
|
+
alias_method :capture, :capture_with_haml
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module TagHelper
|
101
|
+
def content_tag_with_haml(name, *args, &block)
|
102
|
+
return content_tag_without_haml(name, *args, &block) unless is_haml?
|
103
|
+
|
104
|
+
preserve = haml_buffer.options[:preserve].include?(name.to_s)
|
105
|
+
|
106
|
+
if block_given? && block_is_haml?(block) && preserve
|
107
|
+
return content_tag_without_haml(name, *args) {preserve(&block)}
|
108
|
+
end
|
109
|
+
|
110
|
+
returning content_tag_without_haml(name, *args, &block) do |content|
|
111
|
+
return Haml::Helpers.preserve(content) if preserve && content
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
alias_method :content_tag_without_haml, :content_tag
|
116
|
+
alias_method :content_tag, :content_tag_with_haml
|
117
|
+
end
|
118
|
+
|
119
|
+
class InstanceTag
|
120
|
+
# Includes TagHelper
|
121
|
+
|
122
|
+
def haml_buffer
|
123
|
+
@template_object.send :haml_buffer
|
124
|
+
end
|
125
|
+
|
126
|
+
def is_haml?
|
127
|
+
@template_object.send :is_haml?
|
128
|
+
end
|
129
|
+
|
130
|
+
alias_method :content_tag_without_haml, :content_tag
|
131
|
+
alias_method :content_tag, :content_tag_with_haml
|
132
|
+
end
|
133
|
+
|
134
|
+
module FormTagHelper
|
135
|
+
def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
|
136
|
+
if is_haml?
|
137
|
+
if block_given?
|
138
|
+
oldproc = proc
|
139
|
+
proc = haml_bind_proc do |*args|
|
140
|
+
concat "\n"
|
141
|
+
tab_up
|
142
|
+
oldproc.call(*args)
|
143
|
+
tab_down
|
144
|
+
concat haml_indent
|
145
|
+
end
|
146
|
+
concat haml_indent
|
147
|
+
end
|
148
|
+
res = form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc) + "\n"
|
149
|
+
concat "\n" if block_given?
|
150
|
+
res
|
151
|
+
else
|
152
|
+
form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
alias_method :form_tag_without_haml, :form_tag
|
156
|
+
alias_method :form_tag, :form_tag_with_haml
|
157
|
+
end
|
158
|
+
|
159
|
+
module FormHelper
|
160
|
+
def form_for_with_haml(object_name, *args, &proc)
|
161
|
+
if block_given? && is_haml?
|
162
|
+
oldproc = proc
|
163
|
+
proc = haml_bind_proc do |*args|
|
164
|
+
tab_up
|
165
|
+
oldproc.call(*args)
|
166
|
+
tab_down
|
167
|
+
concat haml_indent
|
168
|
+
end
|
169
|
+
concat haml_indent
|
170
|
+
end
|
171
|
+
form_for_without_haml(object_name, *args, &proc)
|
172
|
+
concat "\n" if block_given? && is_haml?
|
173
|
+
end
|
174
|
+
alias_method :form_for_without_haml, :form_for
|
175
|
+
alias_method :form_for, :form_for_with_haml
|
176
|
+
end
|
177
|
+
# :startdoc:
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
data/lib/haml/html.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../haml'
|
2
|
+
|
3
|
+
require 'haml/engine'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'hpricot'
|
6
|
+
require 'cgi'
|
7
|
+
|
8
|
+
module Haml
|
9
|
+
# This class contains the functionality used in the +html2haml+ utility,
|
10
|
+
# namely converting HTML documents to Haml templates.
|
11
|
+
# It depends on Hpricot for HTML parsing (http://code.whytheluckystiff.net/hpricot/).
|
12
|
+
class HTML
|
13
|
+
# Creates a new instance of Haml::HTML that will compile the given template,
|
14
|
+
# which can either be a string containing HTML or an Hpricot node,
|
15
|
+
# to a Haml string when +render+ is called.
|
16
|
+
def initialize(template, options = {})
|
17
|
+
@@options = options
|
18
|
+
|
19
|
+
if template.is_a? Hpricot::Node
|
20
|
+
@template = template
|
21
|
+
else
|
22
|
+
if template.is_a? IO
|
23
|
+
template = template.read
|
24
|
+
end
|
25
|
+
|
26
|
+
if @@options[:rhtml]
|
27
|
+
match_to_html(template, /<%=(.*?)-?%>/m, 'loud')
|
28
|
+
match_to_html(template, /<%-?(.*?)-?%>/m, 'silent')
|
29
|
+
end
|
30
|
+
|
31
|
+
method = @@options[:xhtml] ? Hpricot.method(:XML) : method(:Hpricot)
|
32
|
+
@template = method.call(template.gsub('&', '&'))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Processes the document and returns the result as a string
|
37
|
+
# containing the Haml template.
|
38
|
+
def render
|
39
|
+
@template.to_haml(0)
|
40
|
+
end
|
41
|
+
alias_method :to_haml, :render
|
42
|
+
|
43
|
+
module ::Hpricot::Node
|
44
|
+
# Returns the Haml representation of the given node,
|
45
|
+
# at the given tabulation.
|
46
|
+
def to_haml(tabs = 0)
|
47
|
+
parse_text(self.to_s, tabs)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def tabulate(tabs)
|
53
|
+
' ' * tabs
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_text(text, tabs)
|
57
|
+
text.strip!
|
58
|
+
if text.empty?
|
59
|
+
String.new
|
60
|
+
else
|
61
|
+
lines = text.split("\n")
|
62
|
+
|
63
|
+
lines.map do |line|
|
64
|
+
line.strip!
|
65
|
+
"#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
|
66
|
+
end.join
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# :stopdoc:
|
72
|
+
|
73
|
+
def self.options
|
74
|
+
@@options
|
75
|
+
end
|
76
|
+
|
77
|
+
TEXT_REGEXP = /^(\s*).*$/
|
78
|
+
|
79
|
+
class ::Hpricot::Doc
|
80
|
+
def to_haml(tabs = 0)
|
81
|
+
output = ''
|
82
|
+
children.each { |child| output += child.to_haml(0) } if children
|
83
|
+
output
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class ::Hpricot::XMLDecl
|
88
|
+
def to_haml(tabs = 0)
|
89
|
+
"#{tabulate(tabs)}!!! XML\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class ::Hpricot::DocType
|
94
|
+
def to_haml(tabs = 0)
|
95
|
+
attrs = public_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0]
|
96
|
+
if attrs == nil
|
97
|
+
raise Exception.new("Invalid doctype")
|
98
|
+
end
|
99
|
+
|
100
|
+
type, version, strictness = attrs.map { |a| a.downcase }
|
101
|
+
if type == "html"
|
102
|
+
version = "1.0"
|
103
|
+
strictness = "transitional"
|
104
|
+
end
|
105
|
+
|
106
|
+
if version == "1.0" || version.empty?
|
107
|
+
version = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
if strictness == 'transitional' || strictness.empty?
|
111
|
+
strictness = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
version = " #{version}" if version
|
115
|
+
if strictness
|
116
|
+
strictness[0] = strictness[0] - 32
|
117
|
+
strictness = " #{strictness}"
|
118
|
+
end
|
119
|
+
|
120
|
+
"#{tabulate(tabs)}!!!#{version}#{strictness}\n"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class ::Hpricot::Comment
|
125
|
+
def to_haml(tabs = 0)
|
126
|
+
"#{tabulate(tabs)}/\n#{parse_text(self.content, tabs + 1)}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class ::Hpricot::Elem
|
131
|
+
def to_haml(tabs = 0)
|
132
|
+
output = "#{tabulate(tabs)}"
|
133
|
+
if HTML.options[:rhtml] && name[0...5] == 'haml:'
|
134
|
+
return output + HTML.send("haml_tag_#{name[5..-1]}", CGI.unescapeHTML(self.inner_text))
|
135
|
+
end
|
136
|
+
|
137
|
+
output += "%#{name}" unless name == 'div' && (static_id? || static_classname?)
|
138
|
+
|
139
|
+
if attributes
|
140
|
+
if static_id?
|
141
|
+
output += "##{attributes['id']}"
|
142
|
+
remove_attribute('id')
|
143
|
+
end
|
144
|
+
if static_classname?
|
145
|
+
attributes['class'].split(' ').each { |c| output += ".#{c}" }
|
146
|
+
remove_attribute('class')
|
147
|
+
end
|
148
|
+
output += haml_attributes if attributes.length > 0
|
149
|
+
end
|
150
|
+
|
151
|
+
(self.children || []).inject(output + "\n") do |output, child|
|
152
|
+
output + child.to_haml(tabs + 1)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def dynamic_attributes
|
159
|
+
@dynamic_attributes ||= begin
|
160
|
+
Haml::Util.map_hash(attributes) do |name, value|
|
161
|
+
next if value.empty?
|
162
|
+
full_match = nil
|
163
|
+
ruby_value = value.gsub(%r{<haml:loud>\s*(.+?)\s*</haml:loud>}) do
|
164
|
+
full_match = $`.empty? && $'.empty?
|
165
|
+
full_match ? $1: "\#{#{$1}}"
|
166
|
+
end
|
167
|
+
next if ruby_value == value
|
168
|
+
[name, full_match ? ruby_value : %("#{ruby_value}")]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def static_attribute?(name)
|
174
|
+
attributes[name] and !dynamic_attribute?(name)
|
175
|
+
end
|
176
|
+
|
177
|
+
def dynamic_attribute?(name)
|
178
|
+
HTML.options[:rhtml] and dynamic_attributes.key?(name)
|
179
|
+
end
|
180
|
+
|
181
|
+
def static_id?
|
182
|
+
static_attribute? 'id'
|
183
|
+
end
|
184
|
+
|
185
|
+
def static_classname?
|
186
|
+
static_attribute? 'class'
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns a string representation of an attributes hash
|
190
|
+
# that's prettier than that produced by Hash#inspect
|
191
|
+
def haml_attributes
|
192
|
+
attrs = attributes.map do |name, value|
|
193
|
+
value = dynamic_attribute?(name) ? dynamic_attributes[name] : value.inspect
|
194
|
+
name = name.index(/\W/) ? name.inspect : ":#{name}"
|
195
|
+
"#{name} => #{value}"
|
196
|
+
end
|
197
|
+
"{ #{attrs.join(', ')} }"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.haml_tag_loud(text)
|
202
|
+
"= #{text.gsub(/\n\s*/, ' ').strip}\n"
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.haml_tag_silent(text)
|
206
|
+
text.split("\n").map { |line| "- #{line.strip}\n" }.join
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def match_to_html(string, regex, tag)
|
212
|
+
string.gsub!(regex) do
|
213
|
+
"<haml:#{tag}>#{CGI.escapeHTML($1)}</haml:#{tag}>"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
# :startdoc:
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,896 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require 'haml/shared'
|
3
|
+
|
4
|
+
module Haml
|
5
|
+
module Precompiler
|
6
|
+
include Haml::Util
|
7
|
+
|
8
|
+
# Designates an XHTML/XML element.
|
9
|
+
ELEMENT = ?%
|
10
|
+
|
11
|
+
# Designates a <tt><div></tt> element with the given class.
|
12
|
+
DIV_CLASS = ?.
|
13
|
+
|
14
|
+
# Designates a <tt><div></tt> element with the given id.
|
15
|
+
DIV_ID = ?#
|
16
|
+
|
17
|
+
# Designates an XHTML/XML comment.
|
18
|
+
COMMENT = ?/
|
19
|
+
|
20
|
+
# Designates an XHTML doctype or script that is never HTML-escaped.
|
21
|
+
DOCTYPE_OR_UNESCAPE = ?!
|
22
|
+
|
23
|
+
# Designates script, the result of which is output.
|
24
|
+
SCRIPT = ?=
|
25
|
+
|
26
|
+
# Designates script that is always HTML-escaped.
|
27
|
+
SANITIZE = ?&
|
28
|
+
|
29
|
+
# Designates script, the result of which is flattened and output.
|
30
|
+
FLAT_SCRIPT = ?~
|
31
|
+
|
32
|
+
# Designates script which is run but not output.
|
33
|
+
SILENT_SCRIPT = ?-
|
34
|
+
|
35
|
+
# When following SILENT_SCRIPT, designates a comment that is not output.
|
36
|
+
SILENT_COMMENT = ?#
|
37
|
+
|
38
|
+
# Designates a non-parsed line.
|
39
|
+
ESCAPE = ?\\
|
40
|
+
|
41
|
+
# Designates a block of filtered text.
|
42
|
+
FILTER = ?:
|
43
|
+
|
44
|
+
# Designates a non-parsed line. Not actually a character.
|
45
|
+
PLAIN_TEXT = -1
|
46
|
+
|
47
|
+
# Keeps track of the ASCII values of the characters that begin a
|
48
|
+
# specially-interpreted line.
|
49
|
+
SPECIAL_CHARACTERS = [
|
50
|
+
ELEMENT,
|
51
|
+
DIV_CLASS,
|
52
|
+
DIV_ID,
|
53
|
+
COMMENT,
|
54
|
+
DOCTYPE_OR_UNESCAPE,
|
55
|
+
SCRIPT,
|
56
|
+
SANITIZE,
|
57
|
+
FLAT_SCRIPT,
|
58
|
+
SILENT_SCRIPT,
|
59
|
+
ESCAPE,
|
60
|
+
FILTER
|
61
|
+
]
|
62
|
+
|
63
|
+
# The value of the character that designates that a line is part
|
64
|
+
# of a multiline string.
|
65
|
+
MULTILINE_CHAR_VALUE = ?|
|
66
|
+
|
67
|
+
# Keywords that appear in the middle of a Ruby block with lowered
|
68
|
+
# indentation. If a block has been started using indentation,
|
69
|
+
# lowering the indentation with one of these won't end the block.
|
70
|
+
# For example:
|
71
|
+
#
|
72
|
+
# - if foo
|
73
|
+
# %p yes!
|
74
|
+
# - else
|
75
|
+
# %p no!
|
76
|
+
#
|
77
|
+
# The block is ended after <tt>%p no!</tt>, because <tt>else</tt>
|
78
|
+
# is a member of this array.
|
79
|
+
MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
|
80
|
+
|
81
|
+
# The Regex that matches a Doctype command.
|
82
|
+
DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i
|
83
|
+
|
84
|
+
# The Regex that matches a literal string or symbol value
|
85
|
+
LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Returns the precompiled string with the preamble and postamble
|
90
|
+
def precompiled_with_ambles(local_names)
|
91
|
+
preamble = <<END.gsub("\n", ";")
|
92
|
+
extend Haml::Helpers
|
93
|
+
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
94
|
+
_erbout = _hamlout.buffer
|
95
|
+
__in_erb_template = true
|
96
|
+
END
|
97
|
+
postamble = <<END.gsub("\n", ";")
|
98
|
+
@haml_buffer = @haml_buffer.upper
|
99
|
+
_erbout
|
100
|
+
END
|
101
|
+
preamble + locals_code(local_names) + @precompiled + postamble
|
102
|
+
end
|
103
|
+
|
104
|
+
def locals_code(names)
|
105
|
+
names = names.keys if Hash == names
|
106
|
+
|
107
|
+
names.map do |name|
|
108
|
+
# Can't use || because someone might explicitly pass in false with a symbol
|
109
|
+
sym_local = "_haml_locals[#{name.to_sym.inspect}]"
|
110
|
+
str_local = "_haml_locals[#{name.to_s.inspect}]"
|
111
|
+
"#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
|
112
|
+
end.join(';') + ';'
|
113
|
+
end
|
114
|
+
|
115
|
+
class Line < Struct.new(:text, :unstripped, :full, :index, :precompiler, :eod)
|
116
|
+
alias_method :eod?, :eod
|
117
|
+
|
118
|
+
def tabs
|
119
|
+
line = self
|
120
|
+
@tabs ||= precompiler.instance_eval do
|
121
|
+
break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
|
122
|
+
|
123
|
+
if @indentation.nil?
|
124
|
+
@indentation = whitespace
|
125
|
+
|
126
|
+
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
127
|
+
raise SyntaxError.new("Indentation can't use both tabs and spaces.", line.index)
|
128
|
+
end
|
129
|
+
|
130
|
+
@flat_spaces = @indentation * @template_tabs if flat?
|
131
|
+
break 1
|
132
|
+
end
|
133
|
+
|
134
|
+
tabs = whitespace.length / @indentation.length
|
135
|
+
break tabs if whitespace == @indentation * tabs
|
136
|
+
break @template_tabs if flat? && whitespace =~ /^#{@indentation * @template_tabs}/
|
137
|
+
|
138
|
+
raise SyntaxError.new(<<END.strip.gsub("\n", ' '), line.index)
|
139
|
+
Inconsistent indentation: #{Haml::Shared.human_indentation whitespace, true} used for indentation,
|
140
|
+
but the rest of the document was indented using #{Haml::Shared.human_indentation @indentation}.
|
141
|
+
END
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def precompile
|
147
|
+
@haml_comment = @dont_indent_next_line = @dont_tab_up_next_text = false
|
148
|
+
@indentation = nil
|
149
|
+
@line = next_line
|
150
|
+
resolve_newlines
|
151
|
+
newline
|
152
|
+
|
153
|
+
raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line.index) if @line.tabs != 0
|
154
|
+
|
155
|
+
while next_line
|
156
|
+
process_indent(@line) unless @line.text.empty?
|
157
|
+
|
158
|
+
if flat?
|
159
|
+
push_flat(@line)
|
160
|
+
@line = @next_line
|
161
|
+
newline
|
162
|
+
next
|
163
|
+
end
|
164
|
+
|
165
|
+
process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment
|
166
|
+
|
167
|
+
if !flat? && @next_line.tabs - @line.tabs > 1
|
168
|
+
raise SyntaxError.new("The line was indented #{@next_line.tabs - @line.tabs} levels deeper than the previous line.", @next_line.index)
|
169
|
+
end
|
170
|
+
|
171
|
+
resolve_newlines unless @next_line.eod?
|
172
|
+
@line = @next_line
|
173
|
+
newline unless @next_line.eod?
|
174
|
+
end
|
175
|
+
|
176
|
+
# Close all the open tags
|
177
|
+
close until @to_close_stack.empty?
|
178
|
+
flush_merged_text
|
179
|
+
end
|
180
|
+
|
181
|
+
# Processes and deals with lowering indentation.
|
182
|
+
def process_indent(line)
|
183
|
+
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
184
|
+
|
185
|
+
to_close = @template_tabs - line.tabs
|
186
|
+
to_close.times { |i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text) }
|
187
|
+
end
|
188
|
+
|
189
|
+
# Processes a single line of Haml.
|
190
|
+
#
|
191
|
+
# This method doesn't return anything; it simply processes the line and
|
192
|
+
# adds the appropriate code to <tt>@precompiled</tt>.
|
193
|
+
def process_line(text, index)
|
194
|
+
@index = index + 1
|
195
|
+
|
196
|
+
case text[0]
|
197
|
+
when DIV_CLASS, DIV_ID
|
198
|
+
return push_interpolate(text) if text[0..1] == '#{' and contains_interpolation?(text)
|
199
|
+
render_div(text)
|
200
|
+
when ELEMENT; render_tag(text)
|
201
|
+
when COMMENT; render_comment(text[1..-1].strip)
|
202
|
+
when SANITIZE
|
203
|
+
return push_script(unescape_interpolation(text[3..-1].strip), false, false, false, true) if text[1..2] == "=="
|
204
|
+
return push_script(text[2..-1].strip, false, false, false, true) if text[1] == SCRIPT
|
205
|
+
return push_script(unescape_interpolation(text[1..-1].strip), false, false, false, true) if text[1] == ?\s
|
206
|
+
return push_interpolate(text) if contains_interpolation?(text)
|
207
|
+
push_plain text
|
208
|
+
when SCRIPT
|
209
|
+
return push_script(unescape_interpolation(text[2..-1].strip), false) if text[1] == SCRIPT
|
210
|
+
return push_script(text[1..-1], false, false, false, true) if options[:escape_html]
|
211
|
+
push_script(text[1..-1], false)
|
212
|
+
when FLAT_SCRIPT; push_flat_script(text[1..-1])
|
213
|
+
when SILENT_SCRIPT
|
214
|
+
return start_haml_comment if text[1] == SILENT_COMMENT
|
215
|
+
|
216
|
+
raise SyntaxError.new(<<END.rstrip, index) if text[1..-1].strip == "end"
|
217
|
+
You don't need to use "- end" in Haml. Use indentation instead:
|
218
|
+
- if foo?
|
219
|
+
%strong Foo!
|
220
|
+
- else
|
221
|
+
Not foo.
|
222
|
+
END
|
223
|
+
#'
|
224
|
+
push_silent(text[1..-1], true)
|
225
|
+
newline_now
|
226
|
+
|
227
|
+
case_stmt = text[1..-1].split(' ', 2)[0] == "case"
|
228
|
+
block = block_opened? && !mid_block_keyword?(text)
|
229
|
+
push_and_tabulate([:script]) if block || case_stmt
|
230
|
+
push_and_tabulate(:nil) if block && case_stmt
|
231
|
+
when FILTER; start_filtered(text[1..-1].downcase)
|
232
|
+
when DOCTYPE_OR_UNESCAPE
|
233
|
+
return render_doctype(text) if text[0...3] == '!!!'
|
234
|
+
return push_script(unescape_interpolation(text[3..-1].strip), false) if text[1..2] == "=="
|
235
|
+
return push_script(text[2..-1].strip, false) if text[1] == SCRIPT
|
236
|
+
return push_script(unescape_interpolation(text[1..-1].strip), false) if text[1] == ?\s
|
237
|
+
return push_interpolate(text) if contains_interpolation?(text)
|
238
|
+
push_plain text
|
239
|
+
when ESCAPE
|
240
|
+
return push_interpolate(text[1..-1]) if contains_interpolation?(text)
|
241
|
+
push_plain text[1..-1]
|
242
|
+
else
|
243
|
+
return push_interpolate(text) if contains_interpolation?(text)
|
244
|
+
push_plain text
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns whether or not the text is a silent script text with one
|
249
|
+
# of Ruby's mid-block keywords.
|
250
|
+
def mid_block_keyword?(text)
|
251
|
+
text.length > 2 && text[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(text[1..-1].split[0])
|
252
|
+
end
|
253
|
+
|
254
|
+
# Evaluates <tt>text</tt> in the context of the scope object, but
|
255
|
+
# does not output the result.
|
256
|
+
def push_silent(text, can_suppress = false)
|
257
|
+
flush_merged_text
|
258
|
+
return if can_suppress && options[:suppress_eval]
|
259
|
+
@precompiled << "#{text};"
|
260
|
+
end
|
261
|
+
|
262
|
+
# Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
|
263
|
+
# without parsing it.
|
264
|
+
def push_merged_text(text, tab_change = 0, indent = true)
|
265
|
+
text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
|
266
|
+
@to_merge << [:text, text, tab_change]
|
267
|
+
@dont_indent_next_line = false
|
268
|
+
end
|
269
|
+
|
270
|
+
# Concatenate <tt>text</tt> to <tt>@buffer</tt> without tabulation.
|
271
|
+
def concat_merged_text(text)
|
272
|
+
@to_merge << [:text, text, 0]
|
273
|
+
end
|
274
|
+
|
275
|
+
def push_text(text, tab_change = 0)
|
276
|
+
push_merged_text("#{text}\n", tab_change)
|
277
|
+
end
|
278
|
+
|
279
|
+
def push_interpolate(text)
|
280
|
+
push_script(unescape_interpolation(text.strip), false)
|
281
|
+
end
|
282
|
+
|
283
|
+
def flush_merged_text
|
284
|
+
return if @to_merge.empty?
|
285
|
+
|
286
|
+
text, tab_change = @to_merge.inject(["", 0]) do |(str, mtabs), (type, val, tabs)|
|
287
|
+
case type
|
288
|
+
when :text
|
289
|
+
[str << val.gsub('#{', "\\\#{"), mtabs + tabs]
|
290
|
+
when :script
|
291
|
+
if mtabs != 0 && !@options[:ugly]
|
292
|
+
val = "_hamlout.adjust_tabs(#{mtabs}); " + val
|
293
|
+
end
|
294
|
+
[str << "\#{#{val}}", 0]
|
295
|
+
else
|
296
|
+
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
@precompiled <<
|
301
|
+
if @options[:ugly]
|
302
|
+
"_erbout << #{unescape_interpolation(text)};"
|
303
|
+
else
|
304
|
+
"_hamlout.push_text(#{unescape_interpolation(text)}, #{tab_change}, #{@dont_tab_up_next_text.inspect});"
|
305
|
+
end
|
306
|
+
@to_merge = []
|
307
|
+
@dont_tab_up_next_text = false
|
308
|
+
end
|
309
|
+
|
310
|
+
# Renders a block of text as plain text.
|
311
|
+
# Also checks for an illegally opened block.
|
312
|
+
def push_plain(text)
|
313
|
+
if block_opened?
|
314
|
+
raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
|
315
|
+
end
|
316
|
+
|
317
|
+
push_text text
|
318
|
+
end
|
319
|
+
|
320
|
+
# Adds +text+ to <tt>@buffer</tt> while flattening text.
|
321
|
+
def push_flat(line)
|
322
|
+
text = line.full.dup
|
323
|
+
text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
|
324
|
+
@filter_buffer << "#{text}\n"
|
325
|
+
end
|
326
|
+
|
327
|
+
# Causes <tt>text</tt> to be evaluated in the context of
|
328
|
+
# the scope object and the result to be added to <tt>@buffer</tt>.
|
329
|
+
#
|
330
|
+
# If <tt>preserve_script</tt> is true, Haml::Helpers#find_and_flatten is run on
|
331
|
+
# the result before it is added to <tt>@buffer</tt>
|
332
|
+
def push_script(text, preserve_script, in_tag = false, preserve_tag = false,
|
333
|
+
escape_html = false, nuke_inner_whitespace = false)
|
334
|
+
raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
|
335
|
+
return if options[:suppress_eval]
|
336
|
+
|
337
|
+
args = [preserve_script, in_tag, preserve_tag, escape_html,
|
338
|
+
nuke_inner_whitespace, !block_opened?, @options[:ugly]]
|
339
|
+
no_format = @options[:ugly] && !(preserve_script || preserve_tag || escape_html)
|
340
|
+
temp = "haml_temp_#{@temp_count}"
|
341
|
+
@temp_count += 1
|
342
|
+
out = "_hamlout.#{static_method_name(:format_script, *args)}(#{temp});"
|
343
|
+
|
344
|
+
# Prerender tabulation unless we're in a tag
|
345
|
+
push_merged_text '' unless in_tag
|
346
|
+
|
347
|
+
unless block_opened?
|
348
|
+
@to_merge << [:script, no_format ? "#{text}\n" : "#{temp} = #{text}\n#{out}"]
|
349
|
+
concat_merged_text("\n") unless in_tag || nuke_inner_whitespace
|
350
|
+
@newlines -= 1
|
351
|
+
return
|
352
|
+
end
|
353
|
+
|
354
|
+
flush_merged_text
|
355
|
+
|
356
|
+
push_silent "#{temp} = #{text}"
|
357
|
+
newline_now
|
358
|
+
push_and_tabulate([:loud, "_erbout << #{no_format ? "#{temp}.to_s;" : out}",
|
359
|
+
!(in_tag || nuke_inner_whitespace || @options[:ugly])])
|
360
|
+
end
|
361
|
+
|
362
|
+
# Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
|
363
|
+
# to be run on it afterwards.
|
364
|
+
def push_flat_script(text)
|
365
|
+
flush_merged_text
|
366
|
+
|
367
|
+
raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
|
368
|
+
push_script(text, true)
|
369
|
+
end
|
370
|
+
|
371
|
+
def start_haml_comment
|
372
|
+
return unless block_opened?
|
373
|
+
|
374
|
+
@haml_comment = true
|
375
|
+
push_and_tabulate([:haml_comment])
|
376
|
+
end
|
377
|
+
|
378
|
+
# Closes the most recent item in <tt>@to_close_stack</tt>.
|
379
|
+
def close
|
380
|
+
tag, *rest = @to_close_stack.pop
|
381
|
+
send("close_#{tag}", *rest)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Puts a line in <tt>@precompiled</tt> that will add the closing tag of
|
385
|
+
# the most recently opened tag.
|
386
|
+
def close_element(value)
|
387
|
+
tag, nuke_outer_whitespace, nuke_inner_whitespace = value
|
388
|
+
@output_tabs -= 1 unless nuke_inner_whitespace
|
389
|
+
@template_tabs -= 1
|
390
|
+
rstrip_buffer! if nuke_inner_whitespace
|
391
|
+
push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
|
392
|
+
nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
|
393
|
+
@dont_indent_next_line = nuke_outer_whitespace
|
394
|
+
end
|
395
|
+
|
396
|
+
# Closes a Ruby block.
|
397
|
+
def close_script
|
398
|
+
push_silent "end", true
|
399
|
+
@template_tabs -= 1
|
400
|
+
end
|
401
|
+
|
402
|
+
# Closes a comment.
|
403
|
+
def close_comment(has_conditional)
|
404
|
+
@output_tabs -= 1
|
405
|
+
@template_tabs -= 1
|
406
|
+
close_tag = has_conditional ? "<![endif]-->" : "-->"
|
407
|
+
push_text(close_tag, -1)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Closes a loud Ruby block.
|
411
|
+
def close_loud(command, add_newline)
|
412
|
+
push_silent 'end', true
|
413
|
+
@precompiled << command
|
414
|
+
@template_tabs -= 1
|
415
|
+
concat_merged_text("\n") if add_newline
|
416
|
+
end
|
417
|
+
|
418
|
+
# Closes a filtered block.
|
419
|
+
def close_filtered(filter)
|
420
|
+
filter.internal_compile(self, @filter_buffer)
|
421
|
+
@flat = false
|
422
|
+
@flat_spaces = nil
|
423
|
+
@filter_buffer = nil
|
424
|
+
@template_tabs -= 1
|
425
|
+
end
|
426
|
+
|
427
|
+
def close_haml_comment
|
428
|
+
@haml_comment = false
|
429
|
+
@template_tabs -= 1
|
430
|
+
end
|
431
|
+
|
432
|
+
def close_nil
|
433
|
+
@template_tabs -= 1
|
434
|
+
end
|
435
|
+
|
436
|
+
# Iterates through the classes and ids supplied through <tt>.</tt>
|
437
|
+
# and <tt>#</tt> syntax, and returns a hash with them as attributes,
|
438
|
+
# that can then be merged with another attributes hash.
|
439
|
+
def parse_class_and_id(list)
|
440
|
+
attributes = {}
|
441
|
+
list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
|
442
|
+
case type
|
443
|
+
when '.'
|
444
|
+
if attributes['class']
|
445
|
+
attributes['class'] += " "
|
446
|
+
else
|
447
|
+
attributes['class'] = ""
|
448
|
+
end
|
449
|
+
attributes['class'] += property
|
450
|
+
when '#'; attributes['id'] = property
|
451
|
+
end
|
452
|
+
end
|
453
|
+
attributes
|
454
|
+
end
|
455
|
+
|
456
|
+
def parse_literal_value(text)
|
457
|
+
return nil unless text
|
458
|
+
text.match(LITERAL_VALUE_REGEX)
|
459
|
+
|
460
|
+
# $2 holds the value matched by a symbol, but is nil for a string match
|
461
|
+
# $5 holds the value matched by a string
|
462
|
+
$2 || $5
|
463
|
+
end
|
464
|
+
|
465
|
+
def parse_static_hash(text)
|
466
|
+
return {} unless text
|
467
|
+
|
468
|
+
attributes = {}
|
469
|
+
text.split(',').each do |attrib|
|
470
|
+
key, value, more = attrib.split('=>')
|
471
|
+
|
472
|
+
# Make sure the key and value and only the key and value exist
|
473
|
+
# Otherwise, it's too complicated or dynamic and we'll defer it to the actual Ruby parser
|
474
|
+
key = parse_literal_value key
|
475
|
+
value = parse_literal_value value
|
476
|
+
return nil if more || key.nil? || value.nil?
|
477
|
+
|
478
|
+
attributes[key] = value
|
479
|
+
end
|
480
|
+
text.count("\n").times { newline }
|
481
|
+
attributes
|
482
|
+
end
|
483
|
+
|
484
|
+
# This is a class method so it can be accessed from Buffer.
|
485
|
+
def self.build_attributes(is_html, attr_wrapper, attributes = {})
|
486
|
+
quote_escape = attr_wrapper == '"' ? """ : "'"
|
487
|
+
other_quote_char = attr_wrapper == '"' ? "'" : '"'
|
488
|
+
|
489
|
+
result = attributes.collect do |attr, value|
|
490
|
+
next if value.nil?
|
491
|
+
|
492
|
+
if value == true
|
493
|
+
next " #{attr}" if is_html
|
494
|
+
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
|
495
|
+
elsif value == false
|
496
|
+
next
|
497
|
+
end
|
498
|
+
|
499
|
+
value = Haml::Helpers.preserve(Haml::Helpers.escape_once(value.to_s))
|
500
|
+
# We want to decide whether or not to escape quotes
|
501
|
+
value.gsub!('"', '"')
|
502
|
+
this_attr_wrapper = attr_wrapper
|
503
|
+
if value.include? attr_wrapper
|
504
|
+
if value.include? other_quote_char
|
505
|
+
value = value.gsub(attr_wrapper, quote_escape)
|
506
|
+
else
|
507
|
+
this_attr_wrapper = other_quote_char
|
508
|
+
end
|
509
|
+
end
|
510
|
+
" #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
|
511
|
+
end
|
512
|
+
result.compact.sort.join
|
513
|
+
end
|
514
|
+
|
515
|
+
def prerender_tag(name, self_close, attributes)
|
516
|
+
attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
|
517
|
+
"<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
|
518
|
+
end
|
519
|
+
|
520
|
+
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
521
|
+
def parse_tag(line)
|
522
|
+
raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
|
523
|
+
tag_name, attributes, rest = match
|
524
|
+
attributes_hash, rest, last_line = parse_attributes(rest) if rest[0] == ?{
|
525
|
+
if rest
|
526
|
+
object_ref, rest = balance(rest, ?[, ?]) if rest[0] == ?[
|
527
|
+
attributes_hash, rest, last_line = parse_attributes(rest) if rest[0] == ?{ && attributes_hash.nil?
|
528
|
+
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
529
|
+
nuke_whitespace ||= ''
|
530
|
+
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
531
|
+
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
532
|
+
end
|
533
|
+
value = value.to_s.strip
|
534
|
+
[tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
|
535
|
+
nuke_inner_whitespace, action, value, last_line || @index]
|
536
|
+
end
|
537
|
+
|
538
|
+
def parse_attributes(line)
|
539
|
+
line = line.dup
|
540
|
+
last_line = @index
|
541
|
+
|
542
|
+
begin
|
543
|
+
attributes_hash, rest = balance(line, ?{, ?})
|
544
|
+
rescue SyntaxError => e
|
545
|
+
if line.strip[-1] == ?, && e.message == "Unbalanced brackets."
|
546
|
+
line << "\n" << @next_line.text
|
547
|
+
last_line += 1
|
548
|
+
next_line
|
549
|
+
retry
|
550
|
+
end
|
551
|
+
|
552
|
+
raise e
|
553
|
+
end
|
554
|
+
|
555
|
+
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
556
|
+
return attributes_hash, rest, last_line
|
557
|
+
end
|
558
|
+
|
559
|
+
# Parses a line that will render as an XHTML tag, and adds the code that will
|
560
|
+
# render that tag to <tt>@precompiled</tt>.
|
561
|
+
def render_tag(line)
|
562
|
+
tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
|
563
|
+
nuke_inner_whitespace, action, value, last_line = parse_tag(line)
|
564
|
+
|
565
|
+
raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
|
566
|
+
|
567
|
+
# Get rid of whitespace outside of the tag if we need to
|
568
|
+
rstrip_buffer! if nuke_outer_whitespace
|
569
|
+
|
570
|
+
preserve_tag = options[:preserve].include?(tag_name)
|
571
|
+
nuke_inner_whitespace ||= preserve_tag
|
572
|
+
preserve_tag &&= !options[:ugly]
|
573
|
+
|
574
|
+
case action
|
575
|
+
when '/'; self_closing = true
|
576
|
+
when '~'; parse = preserve_script = true
|
577
|
+
when '='
|
578
|
+
parse = true
|
579
|
+
value = unescape_interpolation(value[1..-1].strip) if value[0] == ?=
|
580
|
+
when '&', '!'
|
581
|
+
if value[0] == ?=
|
582
|
+
parse = true
|
583
|
+
value = (value[1] == ?= ? unescape_interpolation(value[2..-1].strip) : value[1..-1].strip)
|
584
|
+
elsif contains_interpolation?(value)
|
585
|
+
parse = true
|
586
|
+
value = unescape_interpolation(value.strip)
|
587
|
+
end
|
588
|
+
else
|
589
|
+
if contains_interpolation?(value)
|
590
|
+
# switch to parse if interpolation is detected
|
591
|
+
parse = true
|
592
|
+
value = unescape_interpolation(value.strip)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
if parse && @options[:suppress_eval]
|
597
|
+
parse = false
|
598
|
+
value = ''
|
599
|
+
end
|
600
|
+
|
601
|
+
escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
|
602
|
+
|
603
|
+
object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
|
604
|
+
|
605
|
+
static_attributes = parse_static_hash(attributes_hash) # Try pre-compiling a static attributes hash
|
606
|
+
attributes_hash = nil if static_attributes || @options[:suppress_eval]
|
607
|
+
attributes = parse_class_and_id(attributes)
|
608
|
+
Buffer.merge_attrs(attributes, static_attributes) if static_attributes
|
609
|
+
|
610
|
+
raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing
|
611
|
+
raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", @next_line.index) if block_opened? && !value.empty?
|
612
|
+
raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty?
|
613
|
+
raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty?
|
614
|
+
|
615
|
+
self_closing ||= !!( !block_opened? && value.empty? && @options[:autoclose].include?(tag_name) )
|
616
|
+
|
617
|
+
dont_indent_next_line =
|
618
|
+
(nuke_outer_whitespace && !block_opened?) ||
|
619
|
+
(nuke_inner_whitespace && block_opened?)
|
620
|
+
|
621
|
+
# Check if we can render the tag directly to text and not process it in the buffer
|
622
|
+
if object_ref == "nil" && attributes_hash.nil? && !preserve_script
|
623
|
+
tag_closed = !block_opened? && !self_closing && !parse
|
624
|
+
|
625
|
+
open_tag = prerender_tag(tag_name, self_closing, attributes)
|
626
|
+
if tag_closed
|
627
|
+
open_tag << "#{value}</#{tag_name}>"
|
628
|
+
open_tag << "\n" unless nuke_outer_whitespace
|
629
|
+
else
|
630
|
+
open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
|
631
|
+
end
|
632
|
+
|
633
|
+
push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
|
634
|
+
!nuke_outer_whitespace)
|
635
|
+
|
636
|
+
@dont_indent_next_line = dont_indent_next_line
|
637
|
+
return if tag_closed
|
638
|
+
else
|
639
|
+
flush_merged_text
|
640
|
+
content = value.empty? || parse ? 'nil' : value.dump
|
641
|
+
attributes_hash = ', ' + attributes_hash if attributes_hash
|
642
|
+
args = [tag_name, self_closing, !block_opened?, preserve_tag, escape_html,
|
643
|
+
attributes, nuke_outer_whitespace, nuke_inner_whitespace
|
644
|
+
].map { |v| v.inspect }.join(', ')
|
645
|
+
push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hash})"
|
646
|
+
@dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
|
647
|
+
end
|
648
|
+
|
649
|
+
return if self_closing
|
650
|
+
|
651
|
+
if value.empty?
|
652
|
+
push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
|
653
|
+
@output_tabs += 1 unless nuke_inner_whitespace
|
654
|
+
return
|
655
|
+
end
|
656
|
+
|
657
|
+
if parse
|
658
|
+
push_script(value, preserve_script, true, preserve_tag, escape_html, nuke_inner_whitespace)
|
659
|
+
concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
# Renders a line that creates an XHTML tag and has an implicit div because of
|
664
|
+
# <tt>.</tt> or <tt>#</tt>.
|
665
|
+
def render_div(line)
|
666
|
+
render_tag('%div' + line)
|
667
|
+
end
|
668
|
+
|
669
|
+
# Renders an XHTML comment.
|
670
|
+
def render_comment(line)
|
671
|
+
conditional, line = balance(line, ?[, ?]) if line[0] == ?[
|
672
|
+
line.strip!
|
673
|
+
conditional << ">" if conditional
|
674
|
+
|
675
|
+
if block_opened? && !line.empty?
|
676
|
+
raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
|
677
|
+
end
|
678
|
+
|
679
|
+
open = "<!--#{conditional} "
|
680
|
+
|
681
|
+
# Render it statically if possible
|
682
|
+
unless line.empty?
|
683
|
+
return push_text("#{open}#{line} #{conditional ? "<![endif]-->" : "-->"}")
|
684
|
+
end
|
685
|
+
|
686
|
+
push_text(open, 1)
|
687
|
+
@output_tabs += 1
|
688
|
+
push_and_tabulate([:comment, !conditional.nil?])
|
689
|
+
unless line.empty?
|
690
|
+
push_text(line)
|
691
|
+
close
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
# Renders an XHTML doctype or XML shebang.
|
696
|
+
def render_doctype(line)
|
697
|
+
raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened?
|
698
|
+
doctype = text_for_doctype(line)
|
699
|
+
push_text doctype if doctype
|
700
|
+
end
|
701
|
+
|
702
|
+
def text_for_doctype(text)
|
703
|
+
text = text[3..-1].lstrip.downcase
|
704
|
+
if text.index("xml") == 0
|
705
|
+
return nil if html?
|
706
|
+
wrapper = @options[:attr_wrapper]
|
707
|
+
return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
|
708
|
+
end
|
709
|
+
|
710
|
+
if html5?
|
711
|
+
'<!DOCTYPE html>'
|
712
|
+
else
|
713
|
+
version, type = text.scan(DOCTYPE_REGEX)[0]
|
714
|
+
|
715
|
+
if xhtml?
|
716
|
+
if version == "1.1"
|
717
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
718
|
+
else
|
719
|
+
case type
|
720
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
721
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
722
|
+
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
723
|
+
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
724
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
elsif html4?
|
729
|
+
case type
|
730
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
731
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
732
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
# Starts a filtered block.
|
739
|
+
def start_filtered(name)
|
740
|
+
raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
|
741
|
+
raise Error.new("Filter \"#{name}\" is not defined.") unless filter = Filters.defined[name]
|
742
|
+
|
743
|
+
push_and_tabulate([:filtered, filter])
|
744
|
+
@flat = true
|
745
|
+
@filter_buffer = String.new
|
746
|
+
|
747
|
+
# If we don't know the indentation by now, it'll be set in Line#tabs
|
748
|
+
@flat_spaces = @indentation * @template_tabs if @indentation
|
749
|
+
end
|
750
|
+
|
751
|
+
def raw_next_line
|
752
|
+
text = @template.shift
|
753
|
+
return unless text
|
754
|
+
|
755
|
+
index = @template_index
|
756
|
+
@template_index += 1
|
757
|
+
|
758
|
+
return text, index
|
759
|
+
end
|
760
|
+
|
761
|
+
def next_line
|
762
|
+
text, index = raw_next_line
|
763
|
+
return unless text
|
764
|
+
|
765
|
+
# :eod is a special end-of-document marker
|
766
|
+
line =
|
767
|
+
if text == :eod
|
768
|
+
Line.new '-#', '-#', '-#', index, self, true
|
769
|
+
else
|
770
|
+
Line.new text.strip, text.lstrip.chomp, text, index, self, false
|
771
|
+
end
|
772
|
+
|
773
|
+
# `flat?' here is a little outdated,
|
774
|
+
# so we have to manually check if the previous line closes the flat block.
|
775
|
+
unless flat? && (@line.text.empty? || @line.tabs >= @template_tabs)
|
776
|
+
if line.text.empty?
|
777
|
+
newline
|
778
|
+
return next_line
|
779
|
+
end
|
780
|
+
|
781
|
+
handle_multiline(line)
|
782
|
+
end
|
783
|
+
|
784
|
+
@next_line = line
|
785
|
+
end
|
786
|
+
|
787
|
+
def un_next_line(line)
|
788
|
+
@template.unshift line
|
789
|
+
@template_index -= 1
|
790
|
+
end
|
791
|
+
|
792
|
+
def handle_multiline(line)
|
793
|
+
if is_multiline?(line.text)
|
794
|
+
line.text.slice!(-1)
|
795
|
+
while new_line = raw_next_line.first
|
796
|
+
break if new_line == :eod
|
797
|
+
newline and next if new_line.strip.empty?
|
798
|
+
break unless is_multiline?(new_line.strip)
|
799
|
+
line.text << new_line.strip[0...-1]
|
800
|
+
newline
|
801
|
+
end
|
802
|
+
un_next_line new_line
|
803
|
+
resolve_newlines
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
# Checks whether or not +line+ is in a multiline sequence.
|
808
|
+
def is_multiline?(text)
|
809
|
+
text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
|
810
|
+
end
|
811
|
+
|
812
|
+
def contains_interpolation?(str)
|
813
|
+
interpolation_index = str.index('#{')
|
814
|
+
if interpolation_index
|
815
|
+
# checks the rest of the line for a closing brace
|
816
|
+
return !str.index("}", interpolation_index).nil?
|
817
|
+
else
|
818
|
+
return false
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
def unescape_interpolation(str)
|
823
|
+
res = ''
|
824
|
+
rest = Haml::Shared.handle_interpolation str.dump do |scan|
|
825
|
+
escapes = (scan[2].size - 1) / 2
|
826
|
+
res << scan.matched[0...-3 - escapes]
|
827
|
+
if escapes % 2 == 1
|
828
|
+
res << '#{'
|
829
|
+
else
|
830
|
+
res << '#{' + eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + "}"# Use eval to get rid of string escapes
|
831
|
+
end
|
832
|
+
end
|
833
|
+
res + rest
|
834
|
+
end
|
835
|
+
|
836
|
+
def balance(*args)
|
837
|
+
res = Haml::Shared.balance(*args)
|
838
|
+
return res if res
|
839
|
+
raise SyntaxError.new("Unbalanced brackets.")
|
840
|
+
end
|
841
|
+
|
842
|
+
def block_opened?
|
843
|
+
!flat? && @next_line.tabs > @line.tabs
|
844
|
+
end
|
845
|
+
|
846
|
+
# Pushes value onto <tt>@to_close_stack</tt> and increases
|
847
|
+
# <tt>@template_tabs</tt>.
|
848
|
+
def push_and_tabulate(value)
|
849
|
+
@to_close_stack.push(value)
|
850
|
+
@template_tabs += 1
|
851
|
+
end
|
852
|
+
|
853
|
+
def flat?
|
854
|
+
@flat
|
855
|
+
end
|
856
|
+
|
857
|
+
def newline
|
858
|
+
@newlines += 1
|
859
|
+
end
|
860
|
+
|
861
|
+
def newline_now
|
862
|
+
@precompiled << "\n"
|
863
|
+
@newlines -= 1
|
864
|
+
end
|
865
|
+
|
866
|
+
def resolve_newlines
|
867
|
+
return unless @newlines > 0
|
868
|
+
@precompiled << "\n" * @newlines
|
869
|
+
@newlines = 0
|
870
|
+
end
|
871
|
+
|
872
|
+
# Get rid of and whitespace at the end of the buffer
|
873
|
+
# or the merged text
|
874
|
+
def rstrip_buffer!
|
875
|
+
if @to_merge.empty?
|
876
|
+
push_silent("_erbout.rstrip!", false)
|
877
|
+
@dont_tab_up_next_text = true
|
878
|
+
return
|
879
|
+
end
|
880
|
+
|
881
|
+
last = @to_merge.last
|
882
|
+
case last.first
|
883
|
+
when :text
|
884
|
+
last[1].rstrip!
|
885
|
+
if last[1].empty?
|
886
|
+
@to_merge.pop
|
887
|
+
rstrip_buffer!
|
888
|
+
end
|
889
|
+
when :script
|
890
|
+
last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
|
891
|
+
else
|
892
|
+
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
|
893
|
+
end
|
894
|
+
end
|
895
|
+
end
|
896
|
+
end
|