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.

Files changed (71) hide show
  1. data/README +17 -9
  2. data/Rakefile +12 -4
  3. data/VERSION +1 -1
  4. data/init.rb +1 -6
  5. data/lib/haml.rb +65 -7
  6. data/lib/haml/buffer.rb +49 -84
  7. data/lib/haml/engine.rb +155 -797
  8. data/lib/haml/error.rb +3 -33
  9. data/lib/haml/exec.rb +86 -65
  10. data/lib/haml/filters.rb +57 -27
  11. data/lib/haml/helpers.rb +52 -9
  12. data/lib/haml/helpers/action_view_mods.rb +1 -1
  13. data/lib/haml/html.rb +20 -5
  14. data/lib/haml/precompiler.rb +671 -0
  15. data/lib/haml/template.rb +20 -73
  16. data/lib/haml/template/patch.rb +51 -0
  17. data/lib/haml/template/plugin.rb +21 -0
  18. data/lib/sass.rb +78 -3
  19. data/lib/sass/constant.rb +45 -19
  20. data/lib/sass/constant.rb.rej +42 -0
  21. data/lib/sass/constant/string.rb +4 -0
  22. data/lib/sass/css.rb +162 -39
  23. data/lib/sass/engine.rb +38 -14
  24. data/lib/sass/plugin.rb +79 -44
  25. data/lib/sass/tree/attr_node.rb +12 -11
  26. data/lib/sass/tree/comment_node.rb +9 -3
  27. data/lib/sass/tree/directive_node.rb +51 -0
  28. data/lib/sass/tree/node.rb +13 -6
  29. data/lib/sass/tree/rule_node.rb +34 -12
  30. data/test/benchmark.rb +85 -52
  31. data/test/haml/engine_test.rb +172 -84
  32. data/test/haml/helper_test.rb +31 -3
  33. data/test/haml/html2haml_test.rb +60 -0
  34. data/test/haml/markaby/standard.mab +52 -0
  35. data/test/haml/results/eval_suppressed.xhtml +4 -1
  36. data/test/haml/results/helpers.xhtml +15 -4
  37. data/test/haml/results/just_stuff.xhtml +9 -1
  38. data/test/haml/results/standard.xhtml +0 -1
  39. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  40. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  41. data/test/haml/rhtml/action_view.rhtml +62 -0
  42. data/test/haml/rhtml/standard.rhtml +0 -1
  43. data/test/haml/template_test.rb +41 -21
  44. data/test/haml/templates/_av_partial_1.haml +9 -0
  45. data/test/haml/templates/_av_partial_2.haml +5 -0
  46. data/test/haml/templates/action_view.haml +47 -0
  47. data/test/haml/templates/eval_suppressed.haml +1 -0
  48. data/test/haml/templates/helpers.haml +9 -3
  49. data/test/haml/templates/just_stuff.haml +10 -1
  50. data/test/haml/templates/partials.haml +1 -1
  51. data/test/haml/templates/standard.haml +0 -1
  52. data/test/profile.rb +2 -2
  53. data/test/sass/engine_test.rb +113 -3
  54. data/test/sass/engine_test.rb.rej +18 -0
  55. data/test/sass/plugin_test.rb +34 -11
  56. data/test/sass/results/compact.css +1 -1
  57. data/test/sass/results/complex.css +1 -1
  58. data/test/sass/results/compressed.css +1 -0
  59. data/test/sass/results/constants.css +3 -1
  60. data/test/sass/results/expanded.css +2 -1
  61. data/test/sass/results/import.css +2 -0
  62. data/test/sass/results/nested.css +2 -1
  63. data/test/sass/templates/_partial.sass +2 -0
  64. data/test/sass/templates/compact.sass +2 -0
  65. data/test/sass/templates/complex.sass +1 -0
  66. data/test/sass/templates/compressed.sass +15 -0
  67. data/test/sass/templates/constants.sass +9 -0
  68. data/test/sass/templates/expanded.sass +2 -0
  69. data/test/sass/templates/import.sass +1 -1
  70. data/test/sass/templates/nested.sass +2 -0
  71. 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
- Help with the Ruby On Rails implementation and much of the documentation
238
- by Jeff Hardy (packagethief).
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 (Nex3) contributed the buffered-engine code to Haml,
241
- along with many other enhancements
242
- (including the silent-line syntax: "-").
243
- He continues to actively work on both Haml and Sass.
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 '-'*51, "Benchmark: Haml vs. ERb", '-'*51
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
- benchmarker = Haml::Benchmarker.new
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.7.2
1
+ 1.8.0
data/init.rb CHANGED
@@ -1,7 +1,2 @@
1
1
  require 'haml'
2
- require 'haml/template'
3
- require 'sass'
4
- require 'sass/plugin'
5
-
6
- ActionView::Base.register_template_handler('haml', Haml::Template)
7
- Sass::Plugin.update_stylesheets
2
+ Haml.init_rails(binding)
@@ -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; end
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'
@@ -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 = options
32
- @quote_escape = options[:attr_wrapper] == '"' ? "&quot;" : "&apos;"
33
- @other_quote_char = options[:attr_wrapper] == '"' ? "'" : '"'
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, tabulation)
46
- if @one_liner_pending && Buffer.one_liner?(text)
47
- @buffer << text
48
- else
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, tabulation, flattened)
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] == 10 # \n
67
+ while result[-1] == ?\n
66
68
  # String#chomp is slow
67
69
  result = result[0...-1]
68
70
  end
69
71
 
70
- result = result.gsub("\n", "\n#{tabs(tabulation)}")
71
- push_text result, tabulation
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, tabulation, atomic, try_one_line, class_id, obj_ref, attributes_hash)
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
- if attributes_hash
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
- @real_tabs += 1
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
@@ -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, l_options = {})
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
- end
140
- if !NOT_LOADED.include? 'bluecloth'
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! l_options[:filters] if l_options[:filters]
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
- begin
165
- # Only do the first round of pre-compiling if we really need to.
166
- # They might be passing in the precompiled string.
167
- requires_precompile = true
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
- # This method doesn't return anything; it simply processes the line and
303
- # adds the appropriate code to <tt>@precompiled</tt>.
304
- def process_line(line, index, block_opened)
305
- @index = index + 1
306
- @block_opened = block_opened
307
-
308
- case line[0]
309
- when DIV_CLASS, DIV_ID
310
- render_div(line)
311
- when ELEMENT
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
- # This returns whether or not the line should be
361
- # rendered normally.
362
- def handle_multiline(count, line, index)
363
- suppress_render = false
364
- # Multilines are denoting by ending with a `|` (124)
365
- if is_multiline?(line) && @multiline_buffer
366
- # A multiline string is active, and is being continued
367
- @multiline_buffer += line[0...-1]
368
- suppress_render = true
369
- elsif is_multiline?(line) && (MULTILINE_STARTERS.include? line[0])
370
- # A multiline string has just been activated, start adding the lines
371
- @multiline_buffer = line[0...-1]
372
- @multiline_count = count
373
- @multiline_index = index
374
- process_indent(count, line)
375
- suppress_render = true
376
- elsif @multiline_buffer
377
- # A multiline string has just ended, make line into the result
378
- unless line.empty?
379
- process_line(@multiline_buffer, @multiline_index, count > @multiline_count)
380
- @multiline_buffer = nil
381
- end
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
- return suppress_render
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
- # Takes <tt>@precompiled</tt>, a string buffer of Ruby code, and
410
- # evaluates it in the context of <tt>@scope_object</tt>, after preparing
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
- begin
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
- @scope_object.instance_eval do
136
+ scope_object.instance_eval do
463
137
  @haml_stack.pop
138
+ @haml_is_haml = false
464
139
  end
465
- end
466
140
 
467
- # Evaluates <tt>text</tt> in the context of <tt>@scope_object</tt>, but
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
- # Causes <tt>text</tt> to be evaluated in the context of
503
- # <tt>@scope_object</tt> and the result to be added to <tt>@buffer</tt>.
145
+ # Returns a proc that, when called,
146
+ # renders the template and returns the result as a string.
504
147
  #
505
- # If <tt>flattened</tt> is true, Haml::Helpers#find_and_flatten is run on
506
- # the result before it is added to <tt>@buffer</tt>
507
- def push_script(text, flattened)
508
- unless options[:suppress_eval]
509
- push_silent("haml_temp = #{text}", true)
510
- out = "haml_temp = _hamlout.push_script(haml_temp, #{@output_tabs}, #{flattened})\n"
511
- if @block_opened
512
- push_and_tabulate([:loud, out])
513
- else
514
- @precompiled << out
515
- end
516
- end
517
- end
518
-
519
- # Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
520
- # to be run on it afterwards.
521
- def push_flat_script(text)
522
- if text.empty?
523
- raise SyntaxError.new("Tag has no content.")
524
- else
525
- push_script(text, true)
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] == '"' ? "&quot;" : "&apos;"
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
- version, type = line.scan(DOCTYPE_REGEX)[0]
826
- if version == "1.1"
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
- # Starts a filtered block.
843
- def start_filtered(filter)
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
- def unescape_interpolation(str)
853
- str.dump.gsub('\\#', '#').gsub(/\#\{[^\}]+\}/) do |substr|
854
- substr.gsub('\\"', '"')
855
- end
856
- end
857
-
858
- # Counts the tabulation of a line.
859
- def count_soft_tabs(line)
860
- spaces = line.index(/[^ ]/)
861
- if line[spaces] == ?\t
862
- return nil if line.strip.empty?
863
-
864
- raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
865
- end
866
- [spaces, spaces/2]
867
- end
868
-
869
- # Pushes value onto <tt>@to_close_stack</tt> and increases
870
- # <tt>@template_tabs</tt>.
871
- def push_and_tabulate(value)
872
- @to_close_stack.push(value)
873
- @template_tabs += 1
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