haml 1.7.2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of haml might be problematic. Click here for more details.
- data/README +17 -9
- data/Rakefile +12 -4
- data/VERSION +1 -1
- data/init.rb +1 -6
- data/lib/haml.rb +65 -7
- data/lib/haml/buffer.rb +49 -84
- data/lib/haml/engine.rb +155 -797
- data/lib/haml/error.rb +3 -33
- data/lib/haml/exec.rb +86 -65
- data/lib/haml/filters.rb +57 -27
- data/lib/haml/helpers.rb +52 -9
- data/lib/haml/helpers/action_view_mods.rb +1 -1
- data/lib/haml/html.rb +20 -5
- data/lib/haml/precompiler.rb +671 -0
- data/lib/haml/template.rb +20 -73
- data/lib/haml/template/patch.rb +51 -0
- data/lib/haml/template/plugin.rb +21 -0
- data/lib/sass.rb +78 -3
- data/lib/sass/constant.rb +45 -19
- data/lib/sass/constant.rb.rej +42 -0
- data/lib/sass/constant/string.rb +4 -0
- data/lib/sass/css.rb +162 -39
- data/lib/sass/engine.rb +38 -14
- data/lib/sass/plugin.rb +79 -44
- data/lib/sass/tree/attr_node.rb +12 -11
- data/lib/sass/tree/comment_node.rb +9 -3
- data/lib/sass/tree/directive_node.rb +51 -0
- data/lib/sass/tree/node.rb +13 -6
- data/lib/sass/tree/rule_node.rb +34 -12
- data/test/benchmark.rb +85 -52
- data/test/haml/engine_test.rb +172 -84
- data/test/haml/helper_test.rb +31 -3
- data/test/haml/html2haml_test.rb +60 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/results/eval_suppressed.xhtml +4 -1
- data/test/haml/results/helpers.xhtml +15 -4
- data/test/haml/results/just_stuff.xhtml +9 -1
- data/test/haml/results/standard.xhtml +0 -1
- 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 +0 -1
- data/test/haml/template_test.rb +41 -21
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/eval_suppressed.haml +1 -0
- data/test/haml/templates/helpers.haml +9 -3
- data/test/haml/templates/just_stuff.haml +10 -1
- data/test/haml/templates/partials.haml +1 -1
- data/test/haml/templates/standard.haml +0 -1
- data/test/profile.rb +2 -2
- data/test/sass/engine_test.rb +113 -3
- data/test/sass/engine_test.rb.rej +18 -0
- data/test/sass/plugin_test.rb +34 -11
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +1 -1
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/constants.css +3 -1
- data/test/sass/results/expanded.css +2 -1
- data/test/sass/results/import.css +2 -0
- data/test/sass/results/nested.css +2 -1
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/compact.sass +2 -0
- data/test/sass/templates/complex.sass +1 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/constants.sass +9 -0
- data/test/sass/templates/expanded.sass +2 -0
- data/test/sass/templates/import.sass +1 -1
- data/test/sass/templates/nested.sass +2 -0
- metadata +22 -2
data/README
CHANGED
@@ -233,16 +233,24 @@ the documentation for the Sass module.
|
|
233
233
|
|
234
234
|
== Authors
|
235
235
|
|
236
|
-
Haml and Sass are designed by Hampton Catlin (hcatlin)
|
237
|
-
|
238
|
-
|
236
|
+
Haml and Sass are designed by Hampton Catlin (hcatlin) and he is the author
|
237
|
+
of the original implementation. However, Hampton doesn't even know his way
|
238
|
+
around the code anymore and mostly just concentrates on the language issues.
|
239
|
+
Hampton lives in Toronto, Ontario (though he's an American by birth) and
|
240
|
+
is a partner at Unspace Interactive.
|
239
241
|
|
240
|
-
Nathan Weizenbaum
|
241
|
-
|
242
|
-
|
243
|
-
|
242
|
+
Nathan Weizenbaum is the primary maintainer and architect of the "modern" Ruby
|
243
|
+
implementation of Haml. His hard work has kept the project alive by endlessly
|
244
|
+
answering forum posts, fixing bugs, refactoring, finding speed improvements,
|
245
|
+
writing documentation, implementing new features, and getting Hampton
|
246
|
+
coffee (a fitting task for a boy-genius). Nathan lives in Seattle, Washington and
|
247
|
+
while not being a student at University of Washington he consults for
|
248
|
+
Unspace Interactive and Microsoft.
|
249
|
+
|
250
|
+
If you use this software, you must pay Hampton a compliment. And
|
251
|
+
buy Nathan some jelly beans. Maybe pet a kitten. Yeah. Pet that kitty.
|
252
|
+
|
253
|
+
Some of the work on Haml was supported by Unspace Interactive.
|
244
254
|
|
245
|
-
If you use this software, you must pay Hampton a compliment.
|
246
|
-
Say something nice about it.
|
247
255
|
Beyond that, the implementation is licensed under the MIT License.
|
248
256
|
Ok, fine, I guess that means compliments aren't *required*.
|
data/Rakefile
CHANGED
@@ -22,11 +22,9 @@ desc temp_desc.chomp
|
|
22
22
|
task :benchmark do
|
23
23
|
require 'test/benchmark'
|
24
24
|
|
25
|
-
puts '
|
26
|
-
puts "Running benchmark #{ENV['TIMES']} times..." if ENV['TIMES']
|
25
|
+
puts "Running benchmarks #{ENV['TIMES']} times..." if ENV['TIMES']
|
27
26
|
times = ENV['TIMES'].to_i if ENV['TIMES']
|
28
|
-
|
29
|
-
puts benchmarker.benchmark(times || 100)
|
27
|
+
Haml.benchmark(times || 100)
|
30
28
|
puts '-'*51
|
31
29
|
end
|
32
30
|
|
@@ -99,6 +97,16 @@ unless ARGV[0] == 'benchmark'
|
|
99
97
|
sh %{gem install --no-ri pkg/haml-#{File.read('VERSION').strip}}
|
100
98
|
end
|
101
99
|
|
100
|
+
task :release => [:package] do
|
101
|
+
name, version = ENV['NAME'], ENV['VERSION']
|
102
|
+
raise "Must supply NAME and VERSION for release task." unless name && version
|
103
|
+
sh %{rubyforge login}
|
104
|
+
sh %{rubyforge add_release haml haml "#{name} (v#{version})" pkg/haml-#{version}.gem}
|
105
|
+
sh %{rubyforge add_file haml haml "#{name} (v#{version})" pkg/haml-#{version}.tar.gz}
|
106
|
+
sh %{rubyforge add_file haml haml "#{name} (v#{version})" pkg/haml-#{version}.tar.bz2}
|
107
|
+
sh %{rubyforge add_file haml haml "#{name} (v#{version})" pkg/haml-#{version}.zip}
|
108
|
+
end
|
109
|
+
|
102
110
|
# ----- Documentation -----
|
103
111
|
|
104
112
|
require 'rake/rdoctask'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.8.0
|
data/init.rb
CHANGED
data/lib/haml.rb
CHANGED
@@ -143,6 +143,45 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
143
143
|
# <script src='javascripts/script_9' type='text/javascript'>
|
144
144
|
# </script>
|
145
145
|
# </head>
|
146
|
+
#
|
147
|
+
# A Ruby method call that returns a hash
|
148
|
+
# can be substituted for the hash contents.
|
149
|
+
# For example, Haml::Helpers defines the following method:
|
150
|
+
#
|
151
|
+
# def html_attrs(lang = 'en-US')
|
152
|
+
# {:xmlns => "http://www.w3.org/1999/xhtml", 'xml:lang' => lang, :lang => lang}
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# This can then be used in Haml, like so:
|
156
|
+
#
|
157
|
+
# %html{html_attrs('fr-fr')}
|
158
|
+
#
|
159
|
+
# This is compiled to:
|
160
|
+
#
|
161
|
+
# <html lang='fr-fr' xml:lang='fr=fr' xmlns='http://www.w3.org/1999/xhtml'>
|
162
|
+
# </html>
|
163
|
+
#
|
164
|
+
# You can use as many such attribute methods as you want
|
165
|
+
# by separating them with commas,
|
166
|
+
# like a Ruby argument list.
|
167
|
+
# All the hashes will me merged together, from left to right.
|
168
|
+
# For example, if you defined
|
169
|
+
#
|
170
|
+
# def hash1
|
171
|
+
# {:bread => 'white', :filling => 'peanut butter and jelly'}
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# def hash2
|
175
|
+
# {:bread => 'whole wheat'}
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# then
|
179
|
+
#
|
180
|
+
# %sandwich{hash1, hash2, :delicious => true}/
|
181
|
+
#
|
182
|
+
# would compile to:
|
183
|
+
#
|
184
|
+
# <sandwich bread='whole wheat' delicious='true' filling='peanut butter and jelly' />
|
146
185
|
#
|
147
186
|
# ==== []
|
148
187
|
#
|
@@ -583,7 +622,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
583
622
|
# <p>
|
584
623
|
# hello there you!
|
585
624
|
# </p>
|
586
|
-
#
|
625
|
+
#
|
587
626
|
# ===== Blocks
|
588
627
|
#
|
589
628
|
# Ruby blocks, like XHTML tags, don't need to be explicitly closed in Haml.
|
@@ -650,6 +689,21 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
650
689
|
#
|
651
690
|
# <p>foo</p>
|
652
691
|
# <p>bar</p>
|
692
|
+
#
|
693
|
+
# You can also nest text beneath a silent comment.
|
694
|
+
# None of this text will be rendered.
|
695
|
+
# For example:
|
696
|
+
#
|
697
|
+
# %p foo
|
698
|
+
# -#
|
699
|
+
# This won't be displayed
|
700
|
+
# Nor will this
|
701
|
+
# %p bar
|
702
|
+
#
|
703
|
+
# is compiled to:
|
704
|
+
#
|
705
|
+
# <p>foo</p>
|
706
|
+
# <p>bar</p>
|
653
707
|
#
|
654
708
|
# == Other Useful Things
|
655
709
|
#
|
@@ -693,16 +747,20 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
|
693
747
|
# * An +initialize+ method that accepts one parameter,
|
694
748
|
# the text to be filtered.
|
695
749
|
# * A +render+ method that returns the result of the filtering.
|
696
|
-
#
|
697
|
-
# [<tt>:locals</tt>] The local variables that will be available within the
|
698
|
-
# template. For instance, if <tt>:locals</tt> is
|
699
|
-
# <tt>{ :foo => "bar" }</tt>, then within the template,
|
700
|
-
# <tt>= foo</tt> will produce <tt>bar</tt>.
|
701
750
|
#
|
702
751
|
# [<tt>:autoclose</tt>] A list of tag names that should be automatically self-closed
|
703
752
|
# if they have no content.
|
704
753
|
# Defaults to <tt>['meta', 'img', 'link', 'script', 'br', 'hr']</tt>.
|
705
754
|
#
|
706
|
-
module Haml
|
755
|
+
module Haml
|
756
|
+
# This method is called by init.rb,
|
757
|
+
# which is run by Rails on startup.
|
758
|
+
# We use it rather than putting stuff straight into init.rb
|
759
|
+
# so we can change the initialization behavior
|
760
|
+
# without modifying the file itself.
|
761
|
+
def self.init_rails(binding)
|
762
|
+
%w[haml/template sass sass/plugin].each(&method(:require))
|
763
|
+
end
|
764
|
+
end
|
707
765
|
|
708
766
|
require 'haml/engine'
|
data/lib/haml/buffer.rb
CHANGED
@@ -15,6 +15,9 @@ module Haml
|
|
15
15
|
# _erbout for compatibility with ERB-specific code.
|
16
16
|
attr_accessor :buffer
|
17
17
|
|
18
|
+
# The options hash passed in from Haml::Engine.
|
19
|
+
attr_accessor :options
|
20
|
+
|
18
21
|
# Gets the current tabulation of the document.
|
19
22
|
def tabulation
|
20
23
|
@real_tabs + @tabulation
|
@@ -28,11 +31,10 @@ module Haml
|
|
28
31
|
|
29
32
|
# Creates a new buffer.
|
30
33
|
def initialize(options = {})
|
31
|
-
@options =
|
32
|
-
|
33
|
-
|
34
|
+
@options = {
|
35
|
+
:attr_wrapper => "'"
|
36
|
+
}.merge options
|
34
37
|
@buffer = ""
|
35
|
-
@one_liner_pending = false
|
36
38
|
@tabulation = 0
|
37
39
|
|
38
40
|
# The number of tabs that Engine thinks we should have
|
@@ -42,63 +44,80 @@ module Haml
|
|
42
44
|
|
43
45
|
# Renders +text+ with the proper tabulation. This also deals with
|
44
46
|
# making a possible one-line tag one line or not.
|
45
|
-
def push_text(text,
|
46
|
-
if
|
47
|
-
|
48
|
-
|
49
|
-
if @one_liner_pending
|
50
|
-
@buffer << "\n"
|
51
|
-
@one_liner_pending = false
|
52
|
-
end
|
53
|
-
@buffer << "#{tabs(tabulation)}#{text}\n"
|
47
|
+
def push_text(text, tab_change = 0)
|
48
|
+
if(@tabulation > 0)
|
49
|
+
# Have to push every line in by the extra user set tabulation
|
50
|
+
text.gsub!(/^/m, ' ' * @tabulation)
|
54
51
|
end
|
52
|
+
|
53
|
+
@buffer << "#{text}"
|
54
|
+
@real_tabs += tab_change
|
55
55
|
end
|
56
56
|
|
57
57
|
# Properly formats the output of a script that was run in the
|
58
58
|
# instance_eval.
|
59
|
-
def push_script(result,
|
59
|
+
def push_script(result, flattened, close_tag = nil)
|
60
|
+
tabulation = @real_tabs
|
61
|
+
|
60
62
|
if flattened
|
61
63
|
result = Haml::Helpers.find_and_preserve(result)
|
62
64
|
end
|
63
|
-
|
65
|
+
|
64
66
|
result = result.to_s
|
65
|
-
while result[-1] ==
|
67
|
+
while result[-1] == ?\n
|
66
68
|
# String#chomp is slow
|
67
69
|
result = result[0...-1]
|
68
70
|
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
+
if close_tag && Buffer.one_liner?(result)
|
73
|
+
@buffer << result
|
74
|
+
@buffer << "</#{close_tag}>\n"
|
75
|
+
@real_tabs -= 1
|
76
|
+
else
|
77
|
+
if close_tag
|
78
|
+
@buffer << "\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
result = result.gsub(/^/m, tabs(tabulation))
|
82
|
+
@buffer << "#{result}\n"
|
83
|
+
|
84
|
+
if close_tag
|
85
|
+
@buffer << "#{tabs(tabulation-1)}</#{close_tag}>\n"
|
86
|
+
@real_tabs -= 1
|
87
|
+
end
|
88
|
+
end
|
72
89
|
nil
|
73
90
|
end
|
74
91
|
|
75
|
-
|
76
|
-
def open_prerendered_tag(tag, tabulation)
|
77
|
-
@buffer << "#{tabs(tabulation)}#{tag}"
|
78
|
-
@real_tabs += 1
|
79
|
-
end
|
80
|
-
|
81
92
|
# Takes the various information about the opening tag for an
|
82
93
|
# element, formats it, and adds it to the buffer.
|
83
|
-
def open_tag(name,
|
94
|
+
def open_tag(name, atomic, try_one_line, class_id, obj_ref, content, *attributes_hashes)
|
95
|
+
tabulation = @real_tabs
|
96
|
+
|
84
97
|
attributes = class_id
|
85
|
-
|
98
|
+
attributes_hashes.each do |attributes_hash|
|
86
99
|
attributes_hash.keys.each { |key| attributes_hash[key.to_s] = attributes_hash.delete(key) }
|
87
100
|
self.class.merge_attrs(attributes, attributes_hash)
|
88
101
|
end
|
89
102
|
self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
|
90
103
|
|
91
|
-
@one_liner_pending = false
|
92
104
|
if atomic
|
93
105
|
str = " />\n"
|
94
106
|
elsif try_one_line
|
95
|
-
@one_liner_pending = true
|
96
107
|
str = ">"
|
97
108
|
else
|
98
109
|
str = ">\n"
|
99
110
|
end
|
100
|
-
@buffer << "#{tabs(tabulation)}<#{name}#{build_attributes(attributes)}#{str}"
|
101
|
-
|
111
|
+
@buffer << "#{tabs(tabulation)}<#{name}#{Precompiler.build_attributes(@options[:attr_wrapper], attributes)}#{str}"
|
112
|
+
if content
|
113
|
+
if Buffer.one_liner?(content)
|
114
|
+
@buffer << "#{content}</#{name}>\n"
|
115
|
+
else
|
116
|
+
@buffer << "\n#{tabs(@real_tabs+1)}#{content}\n#{tabs(@real_tabs)}</#{name}>\n"
|
117
|
+
end
|
118
|
+
else
|
119
|
+
@real_tabs += 1
|
120
|
+
end
|
102
121
|
end
|
103
122
|
|
104
123
|
def self.merge_attrs(to, from)
|
@@ -114,62 +133,9 @@ module Haml
|
|
114
133
|
to.merge!(from)
|
115
134
|
end
|
116
135
|
|
117
|
-
# Creates a closing tag with the given name.
|
118
|
-
def close_tag(name, tabulation)
|
119
|
-
if @one_liner_pending
|
120
|
-
@buffer << "</#{name}>\n"
|
121
|
-
@one_liner_pending = false
|
122
|
-
else
|
123
|
-
push_text("</#{name}>", tabulation)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# Opens an XHTML comment.
|
128
|
-
def open_comment(try_one_line, conditional, tabulation)
|
129
|
-
conditional << ">" if conditional
|
130
|
-
@buffer << "#{tabs(tabulation)}<!--#{conditional.to_s} "
|
131
|
-
if try_one_line
|
132
|
-
@one_liner_pending = true
|
133
|
-
else
|
134
|
-
@buffer << "\n"
|
135
|
-
@real_tabs += 1
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# Closes an XHTML comment.
|
140
|
-
def close_comment(has_conditional, tabulation)
|
141
|
-
close_tag = has_conditional ? "<![endif]-->" : "-->"
|
142
|
-
if @one_liner_pending
|
143
|
-
@buffer << " #{close_tag}\n"
|
144
|
-
@one_liner_pending = false
|
145
|
-
else
|
146
|
-
push_text(close_tag, tabulation)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
136
|
# Some of these methods are exposed as public class methods
|
151
137
|
# so they can be re-used in helpers.
|
152
138
|
|
153
|
-
# Takes a hash and builds a list of XHTML attributes from it, returning
|
154
|
-
# the result.
|
155
|
-
def build_attributes(attributes = {})
|
156
|
-
result = attributes.collect do |a,v|
|
157
|
-
v = v.to_s
|
158
|
-
unless v.nil? || v.empty?
|
159
|
-
attr_wrapper = @options[:attr_wrapper]
|
160
|
-
if v.include? attr_wrapper
|
161
|
-
if v.include? @other_quote_char
|
162
|
-
v = v.gsub(attr_wrapper, @quote_escape)
|
163
|
-
else
|
164
|
-
attr_wrapper = @other_quote_char
|
165
|
-
end
|
166
|
-
end
|
167
|
-
" #{a}=#{attr_wrapper}#{v}#{attr_wrapper}"
|
168
|
-
end
|
169
|
-
end
|
170
|
-
result.compact.sort.join
|
171
|
-
end
|
172
|
-
|
173
139
|
# Returns whether or not the given value is short enough to be rendered
|
174
140
|
# on one line.
|
175
141
|
def self.one_liner?(value)
|
@@ -181,7 +147,6 @@ module Haml
|
|
181
147
|
@@tab_cache = {}
|
182
148
|
# Gets <tt>count</tt> tabs. Mostly for internal use.
|
183
149
|
def tabs(count)
|
184
|
-
@real_tabs = count
|
185
150
|
tabs = count + @tabulation
|
186
151
|
' ' * tabs
|
187
152
|
@@tab_cache[tabs] ||= ' ' * tabs
|
data/lib/haml/engine.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'haml/helpers'
|
2
2
|
require 'haml/buffer'
|
3
|
+
require 'haml/precompiler'
|
3
4
|
require 'haml/filters'
|
4
5
|
require 'haml/error'
|
5
6
|
require 'haml/util'
|
@@ -14,101 +15,11 @@ module Haml
|
|
14
15
|
# output = haml_engine.to_html
|
15
16
|
# puts output
|
16
17
|
class Engine
|
18
|
+
include Precompiler
|
19
|
+
|
17
20
|
# Allow reading and writing of the options hash
|
18
21
|
attr :options, true
|
19
22
|
|
20
|
-
# Designates an XHTML/XML element.
|
21
|
-
ELEMENT = ?%
|
22
|
-
|
23
|
-
# Designates a <tt><div></tt> element with the given class.
|
24
|
-
DIV_CLASS = ?.
|
25
|
-
|
26
|
-
# Designates a <tt><div></tt> element with the given id.
|
27
|
-
DIV_ID = ?#
|
28
|
-
|
29
|
-
# Designates an XHTML/XML comment.
|
30
|
-
COMMENT = ?/
|
31
|
-
|
32
|
-
# Designates an XHTML doctype.
|
33
|
-
DOCTYPE = ?!
|
34
|
-
|
35
|
-
# Designates script, the result of which is output.
|
36
|
-
SCRIPT = ?=
|
37
|
-
|
38
|
-
# Designates script, the result of which is flattened and output.
|
39
|
-
FLAT_SCRIPT = ?~
|
40
|
-
|
41
|
-
# Designates script which is run but not output.
|
42
|
-
SILENT_SCRIPT = ?-
|
43
|
-
|
44
|
-
# When following SILENT_SCRIPT, designates a comment that is not output.
|
45
|
-
SILENT_COMMENT = ?#
|
46
|
-
|
47
|
-
# Designates a non-parsed line.
|
48
|
-
ESCAPE = ?\\
|
49
|
-
|
50
|
-
# Designates a block of filtered text.
|
51
|
-
FILTER = ?:
|
52
|
-
|
53
|
-
# Designates a non-parsed line. Not actually a character.
|
54
|
-
PLAIN_TEXT = -1
|
55
|
-
|
56
|
-
# Keeps track of the ASCII values of the characters that begin a
|
57
|
-
# specially-interpreted line.
|
58
|
-
SPECIAL_CHARACTERS = [
|
59
|
-
ELEMENT,
|
60
|
-
DIV_CLASS,
|
61
|
-
DIV_ID,
|
62
|
-
COMMENT,
|
63
|
-
DOCTYPE,
|
64
|
-
SCRIPT,
|
65
|
-
FLAT_SCRIPT,
|
66
|
-
SILENT_SCRIPT,
|
67
|
-
ESCAPE,
|
68
|
-
FILTER
|
69
|
-
]
|
70
|
-
|
71
|
-
# The value of the character that designates that a line is part
|
72
|
-
# of a multiline string.
|
73
|
-
MULTILINE_CHAR_VALUE = ?|
|
74
|
-
|
75
|
-
# Characters that designate that a multiline string may be about
|
76
|
-
# to begin.
|
77
|
-
MULTILINE_STARTERS = SPECIAL_CHARACTERS - [?/]
|
78
|
-
|
79
|
-
# Keywords that appear in the middle of a Ruby block with lowered
|
80
|
-
# indentation. If a block has been started using indentation,
|
81
|
-
# lowering the indentation with one of these won't end the block.
|
82
|
-
# For example:
|
83
|
-
#
|
84
|
-
# - if foo
|
85
|
-
# %p yes!
|
86
|
-
# - else
|
87
|
-
# %p no!
|
88
|
-
#
|
89
|
-
# The block is ended after <tt>%p no!</tt>, because <tt>else</tt>
|
90
|
-
# is a member of this array.
|
91
|
-
MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
|
92
|
-
|
93
|
-
# The Regex that matches an HTML comment command.
|
94
|
-
COMMENT_REGEX = /\/(\[[\w\s\.]*\])?(.*)/
|
95
|
-
|
96
|
-
# The Regex that matches a Doctype command.
|
97
|
-
DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i
|
98
|
-
|
99
|
-
# The Regex that matches an HTML tag command.
|
100
|
-
TAG_REGEX = /[%]([-:\w]+)([-\w\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/
|
101
|
-
|
102
|
-
# The Regex that matches a literal string or symbol value
|
103
|
-
LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/
|
104
|
-
|
105
|
-
FLAT_WARNING = <<END
|
106
|
-
Haml deprecation warning:
|
107
|
-
The ~ command is deprecated and will be removed in future Haml versions.
|
108
|
-
Use the :preserve filter, the preserve helper, or the find_and_preserve
|
109
|
-
helper instead.
|
110
|
-
END
|
111
|
-
|
112
23
|
# Creates a new instace of Haml::Engine that will compile the given
|
113
24
|
# template string when <tt>render</tt> is called.
|
114
25
|
# See README for available options.
|
@@ -118,30 +29,21 @@ END
|
|
118
29
|
# to README!
|
119
30
|
#++
|
120
31
|
#
|
121
|
-
def initialize(template,
|
32
|
+
def initialize(template, options = {})
|
122
33
|
@options = {
|
123
34
|
:suppress_eval => false,
|
124
35
|
:attr_wrapper => "'",
|
125
|
-
:locals => {},
|
126
36
|
:autoclose => ['meta', 'img', 'link', 'br', 'hr', 'input', 'area'],
|
127
37
|
:filters => {
|
128
38
|
'sass' => Sass::Engine,
|
129
39
|
'plain' => Haml::Filters::Plain,
|
130
|
-
'preserve' => Haml::Filters::Preserve
|
131
|
-
|
132
|
-
|
133
|
-
if !NOT_LOADED.include? 'redcloth'
|
134
|
-
@options[:filters].merge!({
|
135
|
-
'redcloth' => RedCloth,
|
40
|
+
'preserve' => Haml::Filters::Preserve,
|
41
|
+
'redcloth' => Haml::Filters::RedCloth,
|
136
42
|
'textile' => Haml::Filters::Textile,
|
137
|
-
'markdown' => Haml::Filters::Markdown
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
@options[:filters]['markdown'] = Haml::Filters::Markdown
|
142
|
-
end
|
143
|
-
|
144
|
-
@options.rec_merge! l_options
|
43
|
+
'markdown' => Haml::Filters::Markdown },
|
44
|
+
:filename => '(haml)'
|
45
|
+
}
|
46
|
+
@options.rec_merge! options
|
145
47
|
|
146
48
|
unless @options[:suppress_eval]
|
147
49
|
@options[:filters].merge!({
|
@@ -149,728 +51,184 @@ END
|
|
149
51
|
'ruby' => Haml::Filters::Ruby
|
150
52
|
})
|
151
53
|
end
|
152
|
-
@options[:filters].rec_merge!
|
54
|
+
@options[:filters].rec_merge! options[:filters] if options[:filters]
|
55
|
+
|
56
|
+
if @options[:locals]
|
57
|
+
warn <<END
|
58
|
+
DEPRECATION WARNING:
|
59
|
+
The Haml :locals option is deprecated and will be removed in version 2.0.
|
60
|
+
Use the locals option for Haml::Engine#render instead.
|
61
|
+
END
|
62
|
+
end
|
153
63
|
|
154
64
|
@template = template.strip #String
|
155
65
|
@to_close_stack = []
|
156
66
|
@output_tabs = 0
|
157
67
|
@template_tabs = 0
|
158
68
|
@index = 0
|
159
|
-
|
160
|
-
# This is the base tabulation of the currently active
|
161
|
-
# flattened block. -1 signifies that there is no such block.
|
162
69
|
@flat_spaces = -1
|
163
70
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
if @@method_names[@template]
|
169
|
-
# Check that the compiled method supports a superset of the local assigns we want to do
|
170
|
-
supported_assigns = @@supported_local_assigns[@template]
|
171
|
-
requires_precompile = !@options[:locals].keys.all? {|var| supported_assigns.include? var}
|
172
|
-
end
|
173
|
-
do_precompile if requires_precompile
|
174
|
-
rescue Haml::Error => e
|
175
|
-
e.add_backtrace_entry(@index, @options[:filename])
|
176
|
-
raise e
|
177
|
-
end
|
71
|
+
precompile
|
72
|
+
rescue Haml::Error
|
73
|
+
$!.backtrace.unshift "#{@options[:filename]}:#{@index}"
|
74
|
+
raise
|
178
75
|
end
|
179
76
|
|
180
77
|
# Processes the template and returns the result as a string.
|
181
|
-
def render(scope = Object.new, &block)
|
182
|
-
@scope_object = scope
|
183
|
-
@buffer = Haml::Buffer.new(@options)
|
184
|
-
|
185
|
-
# Run the compiled evaluator function
|
186
|
-
compile &block
|
187
|
-
|
188
|
-
# Return the result string
|
189
|
-
@buffer.buffer
|
190
|
-
end
|
191
|
-
|
192
|
-
alias_method :to_html, :render
|
193
|
-
|
194
|
-
# This method is deprecated and shouldn't be used.
|
195
|
-
def precompiled
|
196
|
-
$stderr.puts <<END
|
197
|
-
The Haml precompiled method and :precompiled option
|
198
|
-
are deprecated and will be removed in version 2.0.
|
199
|
-
Haml::Engine now automatically handles caching.
|
200
|
-
END
|
201
|
-
nil
|
202
|
-
end
|
203
|
-
|
204
|
-
private
|
205
|
-
|
206
|
-
#Precompile each line
|
207
|
-
def do_precompile
|
208
|
-
@precompiled = ''
|
209
|
-
method_name = assign_method_name(@template, options[:filename])
|
210
|
-
push_silent <<-END
|
211
|
-
def #{method_name}(_haml_local_assigns)
|
212
|
-
@haml_is_haml = true
|
213
|
-
_hamlout = @haml_stack[-1]
|
214
|
-
_erbout = _hamlout.buffer
|
215
|
-
END
|
216
|
-
|
217
|
-
supported_local_assigns = {}
|
218
|
-
@@supported_local_assigns[@template] = supported_local_assigns
|
219
|
-
@options[:locals].each do |k,v|
|
220
|
-
supported_local_assigns[k] = true
|
221
|
-
push_silent "#{k} = _haml_local_assigns[:#{k}]"
|
222
|
-
end
|
223
|
-
|
224
|
-
old_line = nil
|
225
|
-
old_index = nil
|
226
|
-
old_spaces = nil
|
227
|
-
old_tabs = nil
|
228
|
-
old_uline = nil
|
229
|
-
(@template + "\n-#\n-#").each_with_index do |line, index|
|
230
|
-
spaces, tabs = count_soft_tabs(line)
|
231
|
-
uline = line.lstrip.chomp
|
232
|
-
line = uline.rstrip
|
233
|
-
|
234
|
-
if !line.empty?
|
235
|
-
if old_line
|
236
|
-
block_opened = tabs > old_tabs && !line.empty?
|
237
|
-
|
238
|
-
suppress_render = handle_multiline(old_tabs, old_line, old_index) unless @flat_spaces != -1
|
239
|
-
|
240
|
-
if !suppress_render
|
241
|
-
line_empty = old_line.empty?
|
242
|
-
|
243
|
-
process_indent(old_tabs, old_line) unless line_empty
|
244
|
-
flat = @flat_spaces != -1
|
245
|
-
|
246
|
-
|
247
|
-
if !flat && old_spaces != old_tabs * 2
|
248
|
-
raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
|
249
|
-
end
|
250
|
-
|
251
|
-
if flat
|
252
|
-
push_flat(old_uline, old_spaces)
|
253
|
-
elsif !line_empty && !@haml_comment
|
254
|
-
process_line(old_line, old_index, block_opened)
|
255
|
-
end
|
256
|
-
|
257
|
-
if @flat_spaces == -1 && tabs - old_tabs > 1
|
258
|
-
raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.")
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
old_line = line
|
264
|
-
old_index = index
|
265
|
-
old_spaces = spaces
|
266
|
-
old_tabs = tabs
|
267
|
-
old_uline = uline
|
268
|
-
elsif @flat_spaces != -1
|
269
|
-
process_indent(old_tabs, old_line) unless old_line.empty?
|
270
|
-
|
271
|
-
if @flat_spaces != -1
|
272
|
-
push_flat(old_line, old_spaces)
|
273
|
-
old_line = ''
|
274
|
-
old_uline = ''
|
275
|
-
old_spaces = 0
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
# Close all the open tags
|
281
|
-
@template_tabs.times { close }
|
282
|
-
|
283
|
-
push_silent "@haml_is_haml = false\nend\n"
|
284
|
-
end
|
285
|
-
|
286
|
-
# Processes and deals with lowering indentation.
|
287
|
-
def process_indent(count, line)
|
288
|
-
if count <= @template_tabs && @template_tabs > 0
|
289
|
-
to_close = @template_tabs - count
|
290
|
-
|
291
|
-
to_close.times do |i|
|
292
|
-
offset = to_close - 1 - i
|
293
|
-
unless offset == 0 && mid_block_keyword?(line)
|
294
|
-
close
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
# Processes a single line of Haml.
|
301
78
|
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
render_tag(line)
|
313
|
-
when COMMENT
|
314
|
-
render_comment(line)
|
315
|
-
when SCRIPT
|
316
|
-
sub_line = line[1..-1]
|
317
|
-
if sub_line[0] == SCRIPT
|
318
|
-
push_script(unescape_interpolation(sub_line[1..-1].strip), false)
|
319
|
-
else
|
320
|
-
push_script(sub_line, false)
|
321
|
-
end
|
322
|
-
when FLAT_SCRIPT
|
323
|
-
push_flat_script(line[1..-1])
|
324
|
-
when SILENT_SCRIPT
|
325
|
-
sub_line = line[1..-1]
|
326
|
-
unless sub_line[0] == SILENT_COMMENT
|
327
|
-
mbk = mid_block_keyword?(line)
|
328
|
-
push_silent(sub_line, !mbk, true)
|
329
|
-
if (@block_opened && !mbk) || line[1..-1].split(' ', 2)[0] == "case"
|
330
|
-
push_and_tabulate([:script])
|
331
|
-
end
|
332
|
-
else
|
333
|
-
start_haml_comment
|
334
|
-
end
|
335
|
-
when FILTER
|
336
|
-
name = line[1..-1].downcase
|
337
|
-
start_filtered(options[:filters][name.to_s] || name)
|
338
|
-
when DOCTYPE
|
339
|
-
if line[0...3] == '!!!'
|
340
|
-
render_doctype(line)
|
341
|
-
else
|
342
|
-
push_plain line
|
343
|
-
end
|
344
|
-
when ESCAPE
|
345
|
-
push_plain line[1..-1]
|
346
|
-
else
|
347
|
-
push_plain line
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
# Returns whether or not the line is a silent script line with one
|
352
|
-
# of Ruby's mid-block keywords.
|
353
|
-
def mid_block_keyword?(line)
|
354
|
-
line.length > 2 && line[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(line[1..-1].split[0])
|
355
|
-
end
|
356
|
-
|
357
|
-
# Deals with all the logic of figuring out whether a given line is
|
358
|
-
# the beginning, continuation, or end of a multiline sequence.
|
79
|
+
# +scope+ is the context in which the template is evaluated.
|
80
|
+
# If it's a Binding or Proc object,
|
81
|
+
# Haml uses it as the second argument to Kernel#eval;
|
82
|
+
# otherwise, Haml just uses its #instance_eval context.
|
83
|
+
#
|
84
|
+
# Note that Haml modifies the evaluation context
|
85
|
+
# (either the scope object or the "self" object of the scope binding).
|
86
|
+
# It extends Haml::Helpers, and various instance variables are set
|
87
|
+
# (all prefixed with "haml").
|
88
|
+
# For example:
|
359
89
|
#
|
360
|
-
#
|
361
|
-
#
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
90
|
+
# s = "foobar"
|
91
|
+
# Haml::Engine.new("%p= upcase").render(s) #=> "<p>FOOBAR</p>"
|
92
|
+
#
|
93
|
+
# # s now extends Haml::Helpers
|
94
|
+
# s.responds_to?(:html_attrs) #=> true
|
95
|
+
#
|
96
|
+
# +locals+ is a hash of local variables to make available to the template.
|
97
|
+
# For example:
|
98
|
+
#
|
99
|
+
# Haml::Engine.new("%p= foo").render(Object.new, :foo => "Hello, world!") #=> "<p>Hello, world!</p>"
|
100
|
+
#
|
101
|
+
# If a block is passed to render,
|
102
|
+
# that block is run when +yield+ is called
|
103
|
+
# within the template.
|
104
|
+
#
|
105
|
+
# Due to some Ruby quirks,
|
106
|
+
# if scope is a Binding or Proc object and a block is given,
|
107
|
+
# the evaluation context may not be quite what the user expects.
|
108
|
+
# In particular, it's equivalent to passing <tt>eval("self", scope)</tt> as scope.
|
109
|
+
# This won't have an effect in most cases,
|
110
|
+
# but if you're relying on local variables defined in the context of scope,
|
111
|
+
# they won't work.
|
112
|
+
def render(scope = Object.new, locals = {}, &block)
|
113
|
+
locals = (@options[:locals] || {}).merge(locals)
|
114
|
+
buffer = Haml::Buffer.new(options_for_buffer)
|
115
|
+
|
116
|
+
if scope.is_a?(Binding) || scope.is_a?(Proc)
|
117
|
+
scope_object = eval("self", scope)
|
118
|
+
scope = scope_object.instance_eval{binding} if block_given?
|
119
|
+
else
|
120
|
+
scope_object = scope
|
121
|
+
scope = scope_object.instance_eval{binding}
|
382
122
|
end
|
383
123
|
|
384
|
-
|
385
|
-
end
|
386
|
-
|
387
|
-
# Checks whether or not +line+ is in a multiline sequence.
|
388
|
-
def is_multiline?(line) # ' '[0] == 32
|
389
|
-
line && line.length > 1 && line[-1] == MULTILINE_CHAR_VALUE && line[-2] == 32
|
390
|
-
end
|
391
|
-
|
392
|
-
# Method for generating compiled method names basically ripped out of ActiveView::Base
|
393
|
-
# If Haml is to be used as a standalone module without rails and still use the precompiled
|
394
|
-
# methods technique, it will end up duplicating this stuff. I can't decide whether
|
395
|
-
# checking compile times to decide whether to recompile a template belongs in here or
|
396
|
-
# out in template.rb
|
397
|
-
@@method_names = {}
|
398
|
-
@@supported_local_assigns = {}
|
399
|
-
@@render_method_count = 0
|
400
|
-
def assign_method_name(template, file_name)
|
401
|
-
@@render_method_count += 1
|
402
|
-
@@method_names[template] = "_render_haml_#{@@render_method_count}".intern
|
403
|
-
end
|
404
|
-
|
405
|
-
module CompiledTemplates
|
406
|
-
# holds compiled template code
|
407
|
-
end
|
124
|
+
set_locals(locals.merge(:_hamlout => buffer, :_erbout => buffer.buffer), scope, scope_object)
|
408
125
|
|
409
|
-
|
410
|
-
|
411
|
-
# <tt>@scope_object</tt>. The code in <tt>@precompiled</tt> populates
|
412
|
-
# <tt>@buffer</tt> with the compiled XHTML code.
|
413
|
-
def compile(&block)
|
414
|
-
# Set the local variables pointing to the buffer
|
415
|
-
buffer = @buffer
|
416
|
-
@scope_object.extend Haml::Helpers
|
417
|
-
@scope_object.instance_eval do
|
126
|
+
scope_object.instance_eval do
|
127
|
+
extend Haml::Helpers
|
418
128
|
@haml_stack ||= Array.new
|
419
129
|
@haml_stack.push(buffer)
|
420
|
-
|
421
|
-
class << self
|
422
|
-
attr :haml_lineno # :nodoc:
|
423
|
-
end
|
424
|
-
end
|
425
|
-
@scope_object.class.instance_eval do
|
426
|
-
include CompiledTemplates
|
130
|
+
@haml_is_haml = true
|
427
131
|
end
|
428
132
|
|
429
|
-
|
430
|
-
method_name = @@method_names[@template]
|
431
|
-
|
432
|
-
unless @scope_object.respond_to?(method_name)
|
433
|
-
CompiledTemplates.module_eval @precompiled
|
434
|
-
end
|
435
|
-
@scope_object.send(method_name, options[:locals], &block)
|
436
|
-
rescue Exception => e
|
437
|
-
class << e
|
438
|
-
include Haml::Error
|
439
|
-
end
|
440
|
-
|
441
|
-
lineno = @scope_object.haml_lineno
|
442
|
-
|
443
|
-
# Get information from the exception and format it so that
|
444
|
-
# Rails can understand it.
|
445
|
-
compile_error = e.message.scan(/\(eval\):([0-9]*):in `[-_a-zA-Z]*': compile error/)[0]
|
446
|
-
|
447
|
-
if compile_error
|
448
|
-
if @precompiled
|
449
|
-
eval_line = compile_error[0].to_i
|
450
|
-
line_marker = @precompiled.split("\n")[0...eval_line].grep(/@haml_lineno = [0-9]*/)[-1]
|
451
|
-
lineno = line_marker.scan(/[0-9]+/)[0].to_i if line_marker
|
452
|
-
else
|
453
|
-
lineno = -1
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
|
-
e.add_backtrace_entry(lineno, @options[:filename])
|
458
|
-
raise e
|
459
|
-
end
|
133
|
+
eval(@precompiled, scope, @options[:filename], 0)
|
460
134
|
|
461
135
|
# Get rid of the current buffer
|
462
|
-
|
136
|
+
scope_object.instance_eval do
|
463
137
|
@haml_stack.pop
|
138
|
+
@haml_is_haml = false
|
464
139
|
end
|
465
|
-
end
|
466
140
|
|
467
|
-
|
468
|
-
# does not output the result.
|
469
|
-
def push_silent(text, add_index = false, can_suppress = false)
|
470
|
-
unless (can_suppress && options[:suppress_eval])
|
471
|
-
if add_index
|
472
|
-
@precompiled << "@haml_lineno = #{@index}\n#{text}\n"
|
473
|
-
else
|
474
|
-
# Not really DRY, but probably faster
|
475
|
-
@precompiled << "#{text}\n"
|
476
|
-
end
|
477
|
-
end
|
478
|
-
end
|
479
|
-
|
480
|
-
# Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
|
481
|
-
# without parsing it.
|
482
|
-
def push_text(text)
|
483
|
-
@precompiled << "_hamlout.push_text(#{text.dump}, #{@output_tabs})\n"
|
484
|
-
end
|
485
|
-
|
486
|
-
# Renders a block of text as plain text.
|
487
|
-
# Also checks for an illegally opened block.
|
488
|
-
def push_plain(text)
|
489
|
-
if @block_opened
|
490
|
-
raise SyntaxError.new("Illegal Nesting: Nesting within plain text is illegal.")
|
491
|
-
end
|
492
|
-
push_text text
|
493
|
-
end
|
494
|
-
|
495
|
-
# Adds +text+ to <tt>@buffer</tt> while flattening text.
|
496
|
-
def push_flat(text, spaces)
|
497
|
-
tabulation = spaces - @flat_spaces
|
498
|
-
tabulation = tabulation > -1 ? tabulation : 0
|
499
|
-
@filter_buffer << "#{' ' * tabulation}#{text}\n"
|
141
|
+
buffer.buffer
|
500
142
|
end
|
143
|
+
alias_method :to_html, :render
|
501
144
|
|
502
|
-
#
|
503
|
-
#
|
145
|
+
# Returns a proc that, when called,
|
146
|
+
# renders the template and returns the result as a string.
|
504
147
|
#
|
505
|
-
#
|
506
|
-
#
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
#
|
520
|
-
#
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
end
|
527
|
-
end
|
528
|
-
|
529
|
-
def start_haml_comment
|
530
|
-
if @block_opened
|
531
|
-
@haml_comment = true
|
532
|
-
push_and_tabulate([:haml_comment])
|
533
|
-
end
|
534
|
-
end
|
535
|
-
|
536
|
-
# Closes the most recent item in <tt>@to_close_stack</tt>.
|
537
|
-
def close
|
538
|
-
tag, value = @to_close_stack.pop
|
539
|
-
case tag
|
540
|
-
when :script
|
541
|
-
close_block
|
542
|
-
when :comment
|
543
|
-
close_comment value
|
544
|
-
when :element
|
545
|
-
close_tag value
|
546
|
-
when :loud
|
547
|
-
close_loud value
|
548
|
-
when :filtered
|
549
|
-
close_filtered value
|
550
|
-
when :haml_comment
|
551
|
-
close_haml_comment
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
# Puts a line in <tt>@precompiled</tt> that will add the closing tag of
|
556
|
-
# the most recently opened tag.
|
557
|
-
def close_tag(tag)
|
558
|
-
@output_tabs -= 1
|
559
|
-
@template_tabs -= 1
|
560
|
-
@precompiled << "_hamlout.close_tag(#{tag.dump}, #{@output_tabs})\n"
|
561
|
-
end
|
562
|
-
|
563
|
-
# Closes a Ruby block.
|
564
|
-
def close_block
|
565
|
-
push_silent "end", false, true
|
566
|
-
@template_tabs -= 1
|
567
|
-
end
|
568
|
-
|
569
|
-
# Closes a comment.
|
570
|
-
def close_comment(has_conditional)
|
571
|
-
@output_tabs -= 1
|
572
|
-
@template_tabs -= 1
|
573
|
-
push_silent "_hamlout.close_comment(#{has_conditional}, #{@output_tabs})"
|
574
|
-
end
|
575
|
-
|
576
|
-
# Closes a loud Ruby block.
|
577
|
-
def close_loud(command)
|
578
|
-
push_silent 'end', false, true
|
579
|
-
@precompiled << command
|
580
|
-
@template_tabs -= 1
|
581
|
-
end
|
582
|
-
|
583
|
-
# Closes a filtered block.
|
584
|
-
def close_filtered(filter)
|
585
|
-
@flat_spaces = -1
|
586
|
-
if filter.is_a? String
|
587
|
-
if filter == 'redcloth' || filter == 'markdown' || filter == 'textile'
|
588
|
-
raise HamlError.new("You must have the RedCloth gem installed to use #{filter}")
|
589
|
-
else
|
590
|
-
raise HamlError.new("Filter \"#{filter}\" is not defined!")
|
591
|
-
end
|
592
|
-
else
|
593
|
-
filtered = filter.new(@filter_buffer).render
|
594
|
-
|
595
|
-
unless filter == Haml::Filters::Preserve
|
596
|
-
push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
|
597
|
-
else
|
598
|
-
push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\"\n")
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
|
-
@filter_buffer = nil
|
603
|
-
@template_tabs -= 1
|
604
|
-
end
|
605
|
-
|
606
|
-
def close_haml_comment
|
607
|
-
@haml_comment = false
|
608
|
-
@template_tabs -= 1
|
609
|
-
end
|
610
|
-
|
611
|
-
# Iterates through the classes and ids supplied through <tt>.</tt>
|
612
|
-
# and <tt>#</tt> syntax, and returns a hash with them as attributes,
|
613
|
-
# that can then be merged with another attributes hash.
|
614
|
-
def parse_class_and_id(list)
|
615
|
-
attributes = {}
|
616
|
-
list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
|
617
|
-
case type
|
618
|
-
when '.'
|
619
|
-
if attributes['class']
|
620
|
-
attributes['class'] += " "
|
621
|
-
else
|
622
|
-
attributes['class'] = ""
|
623
|
-
end
|
624
|
-
attributes['class'] += property
|
625
|
-
when '#'
|
626
|
-
attributes['id'] = property
|
627
|
-
end
|
628
|
-
end
|
629
|
-
attributes
|
630
|
-
end
|
631
|
-
|
632
|
-
def parse_literal_value(text)
|
633
|
-
text.match(LITERAL_VALUE_REGEX)
|
634
|
-
|
635
|
-
# $2 holds the value matched by a symbol, but is nil for a string match
|
636
|
-
# $5 holds the value matched by a string
|
637
|
-
$2 || $5
|
638
|
-
end
|
639
|
-
|
640
|
-
def parse_literal_hash(text)
|
641
|
-
unless text
|
642
|
-
return {}
|
643
|
-
end
|
644
|
-
|
645
|
-
attributes = {}
|
646
|
-
if inner = text.scan(/^\{(.*)\}$/)[0]
|
647
|
-
inner[0].split(',').each do |attrib|
|
648
|
-
key, value, more = attrib.split('=>')
|
649
|
-
|
650
|
-
# Make sure the key and value and only the key and value exist
|
651
|
-
# Otherwise, it's too complicated and we'll defer it to the actual Ruby parser
|
652
|
-
if more || (key = parse_literal_value(key)).nil? ||
|
653
|
-
(value = parse_literal_value(value)).nil?
|
654
|
-
return nil
|
655
|
-
end
|
656
|
-
|
657
|
-
attributes[key] = value
|
658
|
-
end
|
659
|
-
end
|
660
|
-
attributes
|
661
|
-
end
|
662
|
-
|
663
|
-
def build_attributes(attributes = {})
|
664
|
-
@quote_escape = @options[:attr_wrapper] == '"' ? """ : "'"
|
665
|
-
@other_quote_char = @options[:attr_wrapper] == '"' ? "'" : '"'
|
666
|
-
|
667
|
-
result = attributes.collect do |a,v|
|
668
|
-
v = v.to_s
|
669
|
-
unless v.nil? || v.empty?
|
670
|
-
attr_wrapper = @options[:attr_wrapper]
|
671
|
-
if v.include? attr_wrapper
|
672
|
-
if v.include? @other_quote_char
|
673
|
-
# An imperfection in LITERAL_VALUE_REGEX prevents this
|
674
|
-
# from ever actually being reached,
|
675
|
-
# but in case it becomes possible,
|
676
|
-
# I'm leaving it in.
|
677
|
-
v = v.gsub(attr_wrapper, @quote_escape)
|
678
|
-
else
|
679
|
-
attr_wrapper = @other_quote_char
|
680
|
-
end
|
681
|
-
end
|
682
|
-
" #{a}=#{attr_wrapper}#{v}#{attr_wrapper}"
|
683
|
-
end
|
684
|
-
end
|
685
|
-
result.sort.join
|
686
|
-
end
|
687
|
-
|
688
|
-
def prerender_tag(name, atomic, attributes)
|
689
|
-
if atomic
|
690
|
-
str = " />"
|
691
|
-
else
|
692
|
-
str = ">"
|
693
|
-
end
|
694
|
-
|
695
|
-
"<#{name}#{build_attributes(attributes)}#{str}"
|
696
|
-
end
|
697
|
-
|
698
|
-
# Parses a line that will render as an XHTML tag, and adds the code that will
|
699
|
-
# render that tag to <tt>@precompiled</tt>.
|
700
|
-
def render_tag(line)
|
701
|
-
matched = false
|
702
|
-
line.scan(TAG_REGEX) do |tag_name, attributes, attributes_hash, object_ref, action, value|
|
703
|
-
matched = true
|
704
|
-
value = value.to_s.strip
|
705
|
-
|
706
|
-
case action
|
707
|
-
when '/'
|
708
|
-
atomic = true
|
709
|
-
when '=', '~'
|
710
|
-
parse = true
|
711
|
-
|
712
|
-
if value[0] == ?=
|
713
|
-
value = value[1..-1].strip.dump.gsub('\\#', '#')
|
714
|
-
end
|
715
|
-
end
|
716
|
-
|
717
|
-
flattened = (action == '~')
|
718
|
-
|
719
|
-
value_exists = !value.empty?
|
720
|
-
literal_attributes = parse_literal_hash(attributes_hash)
|
721
|
-
attributes_hash = "{nil}" if attributes_hash.nil? || literal_attributes || @options[:suppress_eval]
|
722
|
-
object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
|
723
|
-
|
724
|
-
if attributes =~ /[\.#](\.|#|\z)/
|
725
|
-
raise SyntaxError.new("Illegal element: classes and ids must have values. Use %div instead.")
|
726
|
-
end
|
727
|
-
|
728
|
-
# Preparse the attributes hash
|
729
|
-
attributes = parse_class_and_id(attributes)
|
730
|
-
Buffer.merge_attrs(attributes, literal_attributes) if literal_attributes
|
731
|
-
|
732
|
-
if @block_opened
|
733
|
-
if atomic
|
734
|
-
raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.")
|
735
|
-
elsif action == '=' || value_exists
|
736
|
-
raise SyntaxError.new("Illegal Nesting: Nesting within a tag that already has content is illegal.")
|
737
|
-
end
|
738
|
-
elsif atomic && value_exists
|
739
|
-
raise SyntaxError.new("Atomic tags can't have content.")
|
740
|
-
elsif parse && !value_exists
|
741
|
-
raise SyntaxError.new("Tag has no content.")
|
742
|
-
end
|
743
|
-
|
744
|
-
if !@block_opened && !value_exists && @options[:autoclose].include?(tag_name)
|
745
|
-
atomic = true
|
746
|
-
end
|
747
|
-
|
748
|
-
do_one_liner = value_exists && !parse && Buffer.one_liner?(value)
|
749
|
-
|
750
|
-
if object_ref == "nil" && attributes_hash == "{nil}" && !flattened && (do_one_liner || !value_exists)
|
751
|
-
# This means that we can render the tag directly to text and not process it in the buffer
|
752
|
-
open_tag = prerender_tag(tag_name, atomic, attributes)
|
753
|
-
|
754
|
-
if do_one_liner
|
755
|
-
open_tag += value
|
756
|
-
open_tag += "</#{tag_name}>"
|
757
|
-
end
|
758
|
-
|
759
|
-
open_tag += "\n"
|
760
|
-
|
761
|
-
push_silent "_hamlout.open_prerendered_tag(#{open_tag.dump}, #{@output_tabs})"
|
762
|
-
return if do_one_liner
|
763
|
-
else
|
764
|
-
push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{@output_tabs}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{object_ref}, #{attributes_hash[1...-1]})", true
|
765
|
-
end
|
766
|
-
|
767
|
-
unless atomic
|
768
|
-
push_and_tabulate([:element, tag_name])
|
769
|
-
@output_tabs += 1
|
770
|
-
|
771
|
-
if value_exists
|
772
|
-
if parse
|
773
|
-
push_script(value, flattened)
|
774
|
-
else
|
775
|
-
push_text(value)
|
776
|
-
end
|
777
|
-
close
|
778
|
-
elsif flattened
|
779
|
-
raise SyntaxError.new("Tag has no content.")
|
780
|
-
end
|
781
|
-
end
|
782
|
-
end
|
783
|
-
|
784
|
-
unless matched
|
785
|
-
raise SyntaxError.new("Invalid tag: \"#{line}\"")
|
786
|
-
end
|
787
|
-
end
|
788
|
-
|
789
|
-
# Renders a line that creates an XHTML tag and has an implicit div because of
|
790
|
-
# <tt>.</tt> or <tt>#</tt>.
|
791
|
-
def render_div(line)
|
792
|
-
render_tag('%div' + line)
|
793
|
-
end
|
794
|
-
|
795
|
-
# Renders an XHTML comment.
|
796
|
-
def render_comment(line)
|
797
|
-
conditional, content = line.scan(COMMENT_REGEX)[0]
|
798
|
-
content.strip!
|
799
|
-
|
800
|
-
if @block_opened && !content.empty?
|
801
|
-
raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.')
|
802
|
-
end
|
803
|
-
|
804
|
-
try_one_line = !content.empty?
|
805
|
-
push_silent "_hamlout.open_comment(#{try_one_line}, #{conditional.inspect}, #{@output_tabs})"
|
806
|
-
@output_tabs += 1
|
807
|
-
push_and_tabulate([:comment, !conditional.nil?])
|
808
|
-
if try_one_line
|
809
|
-
push_text content
|
810
|
-
close
|
811
|
-
end
|
812
|
-
end
|
813
|
-
|
814
|
-
# Renders an XHTML doctype or XML shebang.
|
815
|
-
def render_doctype(line)
|
816
|
-
if @block_opened
|
817
|
-
raise SyntaxError.new("Illegal Nesting: Nesting within a header command is illegal.")
|
818
|
-
end
|
819
|
-
line = line[3..-1].lstrip.downcase
|
820
|
-
if line[0...3] == "xml"
|
821
|
-
encoding = line.split[1] || "utf-8"
|
822
|
-
wrapper = @options[:attr_wrapper]
|
823
|
-
doctype = "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{encoding}#{wrapper} ?>"
|
148
|
+
# +scope+ works the same as it does for render.
|
149
|
+
#
|
150
|
+
# The first argument of the returned proc is a hash of local variable names to values.
|
151
|
+
# However, due to an unfortunate Ruby quirk,
|
152
|
+
# the local variables which can be assigned must be pre-declared.
|
153
|
+
# This is done with the +local_names+ argument.
|
154
|
+
# For example:
|
155
|
+
#
|
156
|
+
# # This works
|
157
|
+
# Haml::Engine.new("%p= foo").render_proc(Object.new, :foo).call :foo => "Hello!"
|
158
|
+
# #=> "<p>Hello!</p>"
|
159
|
+
#
|
160
|
+
# # This doesn't
|
161
|
+
# Haml::Engine.new("%p= foo").render_proc.call :foo => "Hello!"
|
162
|
+
# #=> NameError: undefined local variable or method `foo'
|
163
|
+
#
|
164
|
+
# The proc doesn't take a block;
|
165
|
+
# any yields in the template will fail.
|
166
|
+
def render_proc(scope = Object.new, *local_names)
|
167
|
+
if scope.is_a?(Binding) || scope.is_a?(Proc)
|
168
|
+
scope_object = eval("self", scope)
|
824
169
|
else
|
825
|
-
|
826
|
-
|
827
|
-
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
828
|
-
else
|
829
|
-
case type
|
830
|
-
when "strict"
|
831
|
-
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
832
|
-
when "frameset"
|
833
|
-
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
834
|
-
else
|
835
|
-
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
836
|
-
end
|
837
|
-
end
|
170
|
+
scope_object = scope
|
171
|
+
scope = scope_object.instance_eval{binding}
|
838
172
|
end
|
839
|
-
push_text doctype
|
840
|
-
end
|
841
173
|
|
842
|
-
|
843
|
-
|
844
|
-
unless @block_opened
|
845
|
-
raise SyntaxError.new('Filters must have nested text.')
|
846
|
-
end
|
847
|
-
push_and_tabulate([:filtered, filter])
|
848
|
-
@flat_spaces = @template_tabs * 2
|
849
|
-
@filter_buffer = String.new
|
174
|
+
eval("Proc.new { |*_haml_locals| _haml_locals = _haml_locals[0] || {};" +
|
175
|
+
precompiled_with_ambles(local_names) + "}\n", scope, @options[:filename], 0)
|
850
176
|
end
|
851
177
|
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
#
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
#
|
870
|
-
#
|
871
|
-
|
872
|
-
|
873
|
-
|
178
|
+
# Defines a method on +object+
|
179
|
+
# with the given name
|
180
|
+
# that renders the template and returns the result as a string.
|
181
|
+
#
|
182
|
+
# If +object+ is a class or module,
|
183
|
+
# the method will instead by defined as an instance method.
|
184
|
+
# For example:
|
185
|
+
#
|
186
|
+
# t = Time.now
|
187
|
+
# Haml::Engine.new("%p\n Today's date is\n .date= self.to_s").def_method(t, :render)
|
188
|
+
# t.render #=> "<p>\n Today's date is\n <div class='date'>Fri Nov 23 18:28:29 -0800 2007</div>\n</p>\n"
|
189
|
+
#
|
190
|
+
# Haml::Engine.new(".upcased= upcase").def_method(String, :upcased_div)
|
191
|
+
# "foobar".upcased_div #=> "<div class='upcased'>FOOBAR</div>\n"
|
192
|
+
#
|
193
|
+
# The first argument of the defined method is a hash of local variable names to values.
|
194
|
+
# However, due to an unfortunate Ruby quirk,
|
195
|
+
# the local variables which can be assigned must be pre-declared.
|
196
|
+
# This is done with the +local_names+ argument.
|
197
|
+
# For example:
|
198
|
+
#
|
199
|
+
# # This works
|
200
|
+
# obj = Object.new
|
201
|
+
# Haml::Engine.new("%p= foo").def_method(obj, :render, :foo)
|
202
|
+
# obj.render(:foo => "Hello!") #=> "<p>Hello!</p>"
|
203
|
+
#
|
204
|
+
# # This doesn't
|
205
|
+
# obj = Object.new
|
206
|
+
# Haml::Engine.new("%p= foo").def_method(obj, :render)
|
207
|
+
# obj.render(:foo => "Hello!") #=> NameError: undefined local variable or method `foo'
|
208
|
+
#
|
209
|
+
# Note that Haml modifies the evaluation context
|
210
|
+
# (either the scope object or the "self" object of the scope binding).
|
211
|
+
# It extends Haml::Helpers, and various instance variables are set
|
212
|
+
# (all prefixed with "haml").
|
213
|
+
def def_method(object, name, *local_names)
|
214
|
+
method = object.is_a?(Module) ? :module_eval : :instance_eval
|
215
|
+
|
216
|
+
object.send(method, "def #{name}(_haml_locals = {}); #{precompiled_with_ambles(local_names)}; end",
|
217
|
+
@options[:filename], 0)
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
def set_locals(locals, scope, scope_object)
|
223
|
+
scope_object.send(:instance_variable_set, '@_haml_locals', locals)
|
224
|
+
set_locals = locals.keys.map { |k| "#{k} = @_haml_locals[#{k.inspect}]" }.join("\n")
|
225
|
+
eval(set_locals, scope)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns a hash of options that Haml::Buffer cares about.
|
229
|
+
# This should remain loadable form #inspect.
|
230
|
+
def options_for_buffer
|
231
|
+
{:attr_wrapper => @options[:attr_wrapper]}
|
874
232
|
end
|
875
233
|
end
|
876
234
|
end
|