haml 1.5.2 → 1.7.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 (66) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/Rakefile +1 -0
  3. data/VERSION +1 -1
  4. data/bin/css2sass +7 -0
  5. data/bin/html2haml +0 -82
  6. data/lib/haml.rb +43 -6
  7. data/lib/haml/buffer.rb +81 -72
  8. data/lib/haml/engine.rb +240 -110
  9. data/lib/haml/exec.rb +120 -5
  10. data/lib/haml/helpers.rb +88 -3
  11. data/lib/haml/helpers/action_view_extensions.rb +45 -0
  12. data/lib/haml/helpers/action_view_mods.rb +30 -17
  13. data/lib/haml/html.rb +173 -0
  14. data/lib/haml/template.rb +1 -26
  15. data/lib/haml/util.rb +18 -0
  16. data/lib/sass.rb +181 -3
  17. data/lib/sass/constant.rb +38 -9
  18. data/lib/sass/constant/color.rb +25 -1
  19. data/lib/sass/constant/literal.rb +10 -8
  20. data/lib/sass/css.rb +197 -0
  21. data/lib/sass/engine.rb +239 -68
  22. data/lib/sass/error.rb +2 -2
  23. data/lib/sass/plugin.rb +11 -3
  24. data/lib/sass/tree/attr_node.rb +25 -17
  25. data/lib/sass/tree/comment_node.rb +14 -0
  26. data/lib/sass/tree/node.rb +18 -1
  27. data/lib/sass/tree/rule_node.rb +17 -5
  28. data/lib/sass/tree/value_node.rb +4 -0
  29. data/test/haml/engine_test.rb +42 -25
  30. data/test/haml/helper_test.rb +28 -3
  31. data/test/haml/results/eval_suppressed.xhtml +6 -0
  32. data/test/haml/results/helpers.xhtml +26 -2
  33. data/test/haml/results/helpful.xhtml +2 -0
  34. data/test/haml/results/just_stuff.xhtml +17 -2
  35. data/test/haml/results/standard.xhtml +1 -1
  36. data/test/haml/results/whitespace_handling.xhtml +1 -11
  37. data/test/haml/rhtml/standard.rhtml +1 -1
  38. data/test/haml/template_test.rb +7 -2
  39. data/test/haml/templates/eval_suppressed.haml +7 -2
  40. data/test/haml/templates/helpers.haml +16 -1
  41. data/test/haml/templates/helpful.haml +2 -0
  42. data/test/haml/templates/just_stuff.haml +23 -4
  43. data/test/haml/templates/standard.haml +3 -3
  44. data/test/haml/templates/whitespace_handling.haml +0 -50
  45. data/test/sass/engine_test.rb +35 -10
  46. data/test/sass/plugin_test.rb +10 -6
  47. data/test/sass/results/alt.css +4 -0
  48. data/test/sass/results/complex.css +4 -3
  49. data/test/sass/results/constants.css +3 -3
  50. data/test/sass/results/import.css +27 -0
  51. data/test/sass/results/nested.css +7 -0
  52. data/test/sass/results/parent_ref.css +13 -0
  53. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  54. data/test/sass/results/subdir/subdir.css +1 -0
  55. data/test/sass/templates/alt.sass +16 -0
  56. data/test/sass/templates/bork2.sass +2 -0
  57. data/test/sass/templates/complex.sass +19 -1
  58. data/test/sass/templates/constants.sass +8 -0
  59. data/test/sass/templates/import.sass +8 -0
  60. data/test/sass/templates/importee.sass +10 -0
  61. data/test/sass/templates/nested.sass +8 -0
  62. data/test/sass/templates/parent_ref.sass +25 -0
  63. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  64. data/test/sass/templates/subdir/subdir.sass +6 -0
  65. metadata +95 -75
  66. data/test/haml/results/semantic.cache +0 -15
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006 Hampton Catlin
1
+ Copyright (c) 2006-2007 Hampton Catlin
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -103,6 +103,7 @@ unless ARGV[0] == 'benchmark'
103
103
  rdoc.rdoc_files.include('README')
104
104
  rdoc.rdoc_files.include('lib/**/*.rb')
105
105
  rdoc.rdoc_files.exclude('lib/haml/buffer.rb')
106
+ rdoc.rdoc_files.exclude('lib/haml/util.rb')
106
107
  rdoc.rdoc_files.exclude('lib/sass/tree/*')
107
108
  end
108
109
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.2
1
+ 1.7.0
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/haml'
4
+ require 'haml/exec'
5
+
6
+ opts = Haml::Exec::CSS2Sass.new(ARGV)
7
+ opts.parse!
@@ -1,88 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require File.dirname(__FILE__) + '/../lib/haml'
4
- require 'haml/engine'
5
- require 'rubygems'
6
- require 'hpricot'
7
-
8
- def tabulate(tabs)
9
- ' ' * tabs
10
- end
11
-
12
- TEXT_REGEXP = /^(\s*).*$/
13
-
14
- def parse_text(text, tabs)
15
- text.strip!
16
- if text.empty?
17
- String.new
18
- else
19
- lines = text.split("\n")
20
-
21
- lines.map do |line|
22
- line.strip!
23
- "#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
24
- end.join
25
- end
26
- end
27
-
28
- module Hpricot::Node
29
- def to_haml(tabs)
30
- parse_text(self.to_s, tabs)
31
- end
32
- end
33
-
34
- class Hpricot::Doc
35
- def to_haml
36
- output = ''
37
- children.each { |child| output += child.to_haml(0) }
38
- output
39
- end
40
- end
41
-
42
- class Hpricot::XMLDecl
43
- def to_haml(tabs)
44
- "#{tabulate(tabs)}!!! XML\n"
45
- end
46
- end
47
-
48
- class Hpricot::DocType
49
- def to_haml(tabs)
50
- "#{tabulate(tabs)}!!!\n"
51
- end
52
- end
53
-
54
- class Hpricot::Comment
55
- def to_haml(tabs)
56
- "#{tabulate(tabs)}/\n#{parse_text(self.content, tabs + 1)}"
57
- end
58
- end
59
-
60
- class Hpricot::Elem
61
- def to_haml(tabs)
62
- output = "#{tabulate(tabs)}"
63
- output += "%#{name}" unless name == 'div'
64
-
65
- if attributes
66
- output += "##{attributes['id']}" if attributes['id']
67
- attributes['class'].split(' ').each { |c| output += ".#{c}" } if attributes['class']
68
- attributes.delete("id")
69
- attributes.delete("class")
70
- output += attributes.inspect if attributes.length > 0
71
- end
72
-
73
- output += "/" if children.length == 0
74
- output += "\n"
75
-
76
- self.children.each do |child|
77
- output += child.to_haml(tabs + 1)
78
- end
79
-
80
- output
81
- end
82
- end
83
-
84
- # Must be required after Hpricot mods,
85
- # so they're in scope
86
4
  require 'haml/exec'
87
5
 
88
6
  opts = Haml::Exec::HTML2Haml.new(ARGV)
@@ -78,7 +78,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
78
78
  #
79
79
  # engine = Haml::Engine.new("%p Haml code!")
80
80
  # engine.render #=> "<p>Haml code!</p>\n"
81
- #
81
+ #
82
82
  # == Characters with meaning to Haml
83
83
  #
84
84
  # Various characters, when placed at a certain point in a line,
@@ -181,6 +181,19 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
181
181
  #
182
182
  # <br />
183
183
  # <meta http-equiv='Content-Type' content='text/html' />
184
+ #
185
+ # Some tags are automatically closed, as long as they have no content.
186
+ # +meta+, +img+, +link+, +script+, +br+, and +hr+ tags are closed by default.
187
+ # This list can be customized by setting the <tt>:autoclose</tt> option (see below).
188
+ # For example:
189
+ #
190
+ # %br
191
+ # %meta{'http-equiv' => 'Content-Type', :content => 'text/html'}
192
+ #
193
+ # is also compiled to:
194
+ #
195
+ # <br />
196
+ # <meta http-equiv='Content-Type' content='text/html' />
184
197
  #
185
198
  # ==== . and #
186
199
  #
@@ -524,6 +537,20 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
524
537
  # hi there reader!
525
538
  # yo
526
539
  # </p>
540
+ #
541
+ # You can also use two equal signs, <tt>==</tt>,
542
+ # along with conventional Ruby string-embedding syntax
543
+ # to easily embed Ruby code in otherwise static text.
544
+ # For example:
545
+ #
546
+ # %p
547
+ # == 1 + 1 = #{1 + 1}
548
+ #
549
+ # is compiled to:
550
+ #
551
+ # <p>
552
+ # 1 + 1 = 2
553
+ # </p>
527
554
  #
528
555
  # ==== -
529
556
  #
@@ -595,8 +622,18 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
595
622
  # <p>
596
623
  # 2?
597
624
  # </p>
625
+ #
626
+ # == Other Useful Things
627
+ #
628
+ # === Helpers
629
+ #
630
+ # Haml offers a bunch of helpers that are useful
631
+ # for doing stuff like preserving whitespace,
632
+ # creating nicely indented output for user-defined helpers,
633
+ # and other useful things.
634
+ # The helpers are all documented in the Haml::Helpers and Haml::Helpers::ActionViewExtensions modules.
598
635
  #
599
- # == Haml Options
636
+ # === Haml Options
600
637
  #
601
638
  # Options can be set by setting the hash <tt>Haml::Template.options</tt>
602
639
  # from <tt>environment.rb</tt> in Rails,
@@ -608,10 +645,6 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
608
645
  # evaluated. If this is true, said scripts are
609
646
  # rendered as empty strings. Defaults to false.
610
647
  #
611
- # [<tt>:precompiled</tt>] A string containing a precompiled Haml template.
612
- # If this is passed, <tt>template</tt> is ignored
613
- # and no precompilation is done.
614
- #
615
648
  # [<tt>:attr_wrapper</tt>] The character that should wrap element attributes.
616
649
  # This defaults to <tt>'</tt> (an apostrophe). Characters
617
650
  # of this type within the attributes will be escaped
@@ -638,6 +671,10 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
638
671
  # <tt>{ :foo => "bar" }</tt>, then within the template,
639
672
  # <tt>= foo</tt> will produce <tt>bar</tt>.
640
673
  #
674
+ # [<tt>:autoclose</tt>] A list of tag names that should be automatically self-closed
675
+ # if they have no content.
676
+ # Defaults to <tt>['meta', 'img', 'link', 'script', 'br', 'hr']</tt>.
677
+ #
641
678
  module Haml; end
642
679
 
643
680
  require 'haml/engine'
@@ -15,9 +15,16 @@ module Haml
15
15
  # _erbout for compatibility with ERB-specific code.
16
16
  attr_accessor :buffer
17
17
 
18
- # The number of tabs that are added or subtracted from the
19
- # tabulation proscribed by the precompiled template.
20
- attr_accessor :tabulation
18
+ # Gets the current tabulation of the document.
19
+ def tabulation
20
+ @real_tabs + @tabulation
21
+ end
22
+
23
+ # Sets the current tabulation of the document.
24
+ def tabulation=(val)
25
+ val = val - @real_tabs
26
+ @tabulation = val > -1 ? val : 0
27
+ end
21
28
 
22
29
  # Creates a new buffer.
23
30
  def initialize(options = {})
@@ -27,17 +34,16 @@ module Haml
27
34
  @buffer = ""
28
35
  @one_liner_pending = false
29
36
  @tabulation = 0
37
+
38
+ # The number of tabs that Engine thinks we should have
39
+ # @real_tabs + @tabulation is the number of tabs actually output
40
+ @real_tabs = 0
30
41
  end
31
42
 
32
43
  # Renders +text+ with the proper tabulation. This also deals with
33
44
  # making a possible one-line tag one line or not.
34
- def push_text(text, tabulation, flattened = false)
35
- if flattened
36
- # In this case, tabulation is the number of spaces, rather
37
- # than the number of tabs.
38
- @buffer << "#{' ' * tabulation}#{flatten(text + "\n")}"
39
- @one_liner_pending = true
40
- elsif @one_liner_pending && one_liner?(text)
45
+ def push_text(text, tabulation)
46
+ if @one_liner_pending && Buffer.one_liner?(text)
41
47
  @buffer << text
42
48
  else
43
49
  if @one_liner_pending
@@ -66,14 +72,21 @@ module Haml
66
72
  end
67
73
  nil
68
74
  end
75
+
76
+ def open_prerendered_tag(tag, tabulation)
77
+ @buffer << "#{tabs(tabulation)}#{tag}"
78
+ @real_tabs += 1
79
+ end
69
80
 
70
81
  # Takes the various information about the opening tag for an
71
82
  # element, formats it, and adds it to the buffer.
72
- def open_tag(name, tabulation, atomic, try_one_line, class_id, attributes_hash, obj_ref, flattened)
73
- attributes = {}
74
- attributes.merge!(parse_class_and_id(class_id)) unless class_id.nil? || class_id.empty?
75
- attributes.merge!(parse_object_ref(obj_ref, attributes[:id], attributes[:class])) if obj_ref
76
- attributes.merge!(attributes_hash) if attributes_hash
83
+ def open_tag(name, tabulation, atomic, try_one_line, class_id, obj_ref, attributes_hash)
84
+ attributes = class_id
85
+ if attributes_hash
86
+ attributes_hash.keys.each { |key| attributes_hash[key.to_s] = attributes_hash.delete(key) }
87
+ self.class.merge_attrs(attributes, attributes_hash)
88
+ end
89
+ self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
77
90
 
78
91
  @one_liner_pending = false
79
92
  if atomic
@@ -81,12 +94,24 @@ module Haml
81
94
  elsif try_one_line
82
95
  @one_liner_pending = true
83
96
  str = ">"
84
- elsif flattened
85
- str = ">&#x000A;"
86
97
  else
87
98
  str = ">\n"
88
99
  end
89
100
  @buffer << "#{tabs(tabulation)}<#{name}#{build_attributes(attributes)}#{str}"
101
+ @real_tabs += 1
102
+ end
103
+
104
+ def self.merge_attrs(to, from)
105
+ if to['id'] && from['id']
106
+ to['id'] << '_' << from.delete('id')
107
+ end
108
+
109
+ if to['class'] && from['class']
110
+ # Make sure we don't duplicate class names
111
+ from['class'] = (from['class'].split(' ') | to['class'].split(' ')).join(' ')
112
+ end
113
+
114
+ to.merge!(from)
90
115
  end
91
116
 
92
117
  # Creates a closing tag with the given name.
@@ -107,6 +132,7 @@ module Haml
107
132
  @one_liner_pending = true
108
133
  else
109
134
  @buffer << "\n"
135
+ @real_tabs += 1
110
136
  end
111
137
  end
112
138
 
@@ -120,60 +146,9 @@ module Haml
120
146
  push_text(close_tag, tabulation)
121
147
  end
122
148
  end
123
-
124
- # Stops parsing a flat section.
125
- def stop_flat
126
- buffer.concat("\n")
127
- @one_liner_pending = false
128
- end
129
-
130
- private
131
-
132
- # Gets <tt>count</tt> tabs. Mostly for internal use.
133
- def tabs(count)
134
- ' ' * (count + @tabulation)
135
- end
136
-
137
- # Iterates through the classes and ids supplied through <tt>.</tt>
138
- # and <tt>#</tt> syntax, and returns a hash with them as attributes,
139
- # that can then be merged with another attributes hash.
140
- def parse_class_and_id(list)
141
- attributes = {}
142
- list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
143
- case type
144
- when '.'
145
- if attributes[:class]
146
- attributes[:class] += " "
147
- else
148
- attributes[:class] = ""
149
- end
150
- attributes[:class] += property
151
- when '#'
152
- attributes[:id] = property
153
- end
154
- end
155
- attributes
156
- end
157
-
158
- # Takes an array of objects and uses the class and id of the first
159
- # one to create an attributes hash.
160
- def parse_object_ref(ref, old_id, old_class)
161
- ref = ref[0]
162
- # Let's make sure the value isn't nil. If it is, return the default Hash.
163
- return {} if ref.nil?
164
- class_name = ref.class.to_s.underscore
165
- id = "#{class_name}_#{ref.id}"
166
-
167
- if old_class
168
- class_name += " #{old_class}"
169
- end
170
149
 
171
- if old_id
172
- id = "#{old_id}_#{id}"
173
- end
174
-
175
- {:id => id, :class => class_name}
176
- end
150
+ # Some of these methods are exposed as public class methods
151
+ # so they can be re-used in helpers.
177
152
 
178
153
  # Takes a hash and builds a list of XHTML attributes from it, returning
179
154
  # the result.
@@ -194,12 +169,46 @@ module Haml
194
169
  end
195
170
  result.sort.join
196
171
  end
197
-
172
+
198
173
  # Returns whether or not the given value is short enough to be rendered
199
174
  # on one line.
200
- def one_liner?(value)
175
+ def self.one_liner?(value)
201
176
  value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
202
177
  end
178
+
179
+ private
180
+
181
+ @@tab_cache = {}
182
+ # Gets <tt>count</tt> tabs. Mostly for internal use.
183
+ def tabs(count)
184
+ @real_tabs = count
185
+ tabs = count + @tabulation
186
+ ' ' * tabs
187
+ @@tab_cache[tabs] ||= ' ' * tabs
188
+ end
189
+
190
+ # Takes an array of objects and uses the class and id of the first
191
+ # one to create an attributes hash.
192
+ def parse_object_ref(ref)
193
+ ref = ref[0]
194
+ # Let's make sure the value isn't nil. If it is, return the default Hash.
195
+ return {} if ref.nil?
196
+ class_name = underscore(ref.class)
197
+ id = "#{class_name}_#{ref.id || 'new'}"
198
+
199
+ {'id' => id, 'class' => class_name}
200
+ end
201
+
202
+ # Changes a word from camel case to underscores.
203
+ # Based on the method of the same name in Rails' Inflector,
204
+ # but copied here so it'll run properly without Rails.
205
+ def underscore(camel_cased_word)
206
+ camel_cased_word.to_s.gsub(/::/, '_').
207
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
208
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
209
+ tr("-", "_").
210
+ downcase
211
+ end
203
212
  end
204
213
  end
205
214
 
@@ -2,6 +2,7 @@ require 'haml/helpers'
2
2
  require 'haml/buffer'
3
3
  require 'haml/filters'
4
4
  require 'haml/error'
5
+ require 'haml/util'
5
6
 
6
7
  module Haml
7
8
  # This is the class where all the parsing and processing of the Haml
@@ -13,9 +14,6 @@ module Haml
13
14
  # output = haml_engine.to_html
14
15
  # puts output
15
16
  class Engine
16
- # Allow access to the precompiled template
17
- attr_reader :precompiled
18
-
19
17
  # Allow reading and writing of the options hash
20
18
  attr :options, true
21
19
 
@@ -93,13 +91,16 @@ module Haml
93
91
  MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
94
92
 
95
93
  # The Regex that matches an HTML comment command.
96
- COMMENT_REGEX = /\/(\[[a-zA-Z0-9 \.]*\])?(.*)/
94
+ COMMENT_REGEX = /\/(\[[\w\s\.]*\])?(.*)/
97
95
 
98
96
  # The Regex that matches a Doctype command.
99
- DOCTYPE_REGEX = /([0-9]\.[0-9])?[\s]*([a-zA-Z]*)/
97
+ DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i
100
98
 
101
99
  # The Regex that matches an HTML tag command.
102
- TAG_REGEX = /[%]([-:_a-zA-Z0-9]+)([-_a-zA-Z0-9\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/
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*$/
103
104
 
104
105
  FLAT_WARNING = <<END
105
106
  Haml deprecation warning:
@@ -109,7 +110,7 @@ helper instead.
109
110
  END
110
111
 
111
112
  # Creates a new instace of Haml::Engine that will compile the given
112
- # template string when <tt>to_html</tt> is called.
113
+ # template string when <tt>render</tt> is called.
113
114
  # See README for available options.
114
115
  #
115
116
  #--
@@ -117,16 +118,16 @@ END
117
118
  # to README!
118
119
  #++
119
120
  #
120
- def initialize(template, options = {})
121
+ def initialize(template, l_options = {})
121
122
  @options = {
122
123
  :suppress_eval => false,
123
124
  :attr_wrapper => "'",
124
125
  :locals => {},
126
+ :autoclose => ['meta', 'img', 'link', 'script', 'br', 'hr'],
125
127
  :filters => {
126
128
  'sass' => Sass::Engine,
127
129
  'plain' => Haml::Filters::Plain,
128
- 'preserve' => Haml::Filters::Preserve
129
- }
130
+ 'preserve' => Haml::Filters::Preserve }
130
131
  }
131
132
 
132
133
  if !NOT_LOADED.include? 'redcloth'
@@ -140,7 +141,7 @@ END
140
141
  @options[:filters]['markdown'] = Haml::Filters::Markdown
141
142
  end
142
143
 
143
- @options.rec_merge! options
144
+ @options.rec_merge! l_options
144
145
 
145
146
  unless @options[:suppress_eval]
146
147
  @options[:filters].merge!({
@@ -148,9 +149,7 @@ END
148
149
  'ruby' => Haml::Filters::Ruby
149
150
  })
150
151
  end
151
- @options[:filters].rec_merge! options[:filters] if options[:filters]
152
-
153
- @precompiled = @options[:precompiled]
152
+ @options[:filters].rec_merge! l_options[:filters] if l_options[:filters]
154
153
 
155
154
  @template = template.strip #String
156
155
  @to_close_stack = []
@@ -165,7 +164,13 @@ END
165
164
  begin
166
165
  # Only do the first round of pre-compiling if we really need to.
167
166
  # They might be passing in the precompiled string.
168
- do_precompile if @precompiled.nil? && (@precompiled = String.new)
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
169
174
  rescue Haml::Error => e
170
175
  e.add_backtrace_entry(@index, @options[:filename])
171
176
  raise e
@@ -177,17 +182,7 @@ END
177
182
  @scope_object = scope
178
183
  @buffer = Haml::Buffer.new(@options)
179
184
 
180
- local_assigns = @options[:locals]
181
-
182
- # Get inside the view object's world
183
- @scope_object.instance_eval do
184
- # Set all the local assigns
185
- local_assigns.each do |key,val|
186
- self.class.send(:define_method, key) { val }
187
- end
188
- end
189
-
190
- # Compile the @precompiled buffer
185
+ # Run the compiled evaluator function
191
186
  compile &block
192
187
 
193
188
  # Return the result string
@@ -196,17 +191,36 @@ END
196
191
 
197
192
  alias_method :to_html, :render
198
193
 
199
- private
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
200
203
 
204
+ private
205
+
201
206
  #Precompile each line
202
207
  def do_precompile
208
+ @precompiled = ''
209
+ method_name = assign_method_name(@template, options[:filename])
203
210
  push_silent <<-END
204
- def _haml_render
205
- @haml_is_haml = true
206
- _hamlout = @haml_stack[-1]
207
- _erbout = _hamlout.buffer
211
+ def #{method_name}(_haml_local_assigns)
212
+ @haml_is_haml = true
213
+ _hamlout = @haml_stack[-1]
214
+ _erbout = _hamlout.buffer
208
215
  END
209
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
+
210
224
  old_line = nil
211
225
  old_index = nil
212
226
  old_spaces = nil
@@ -236,7 +250,7 @@ END
236
250
 
237
251
  if flat
238
252
  push_flat(old_uline, old_spaces)
239
- elsif !line_empty
253
+ elsif !line_empty && !@haml_comment
240
254
  process_line(old_line, old_index, block_opened)
241
255
  end
242
256
 
@@ -266,8 +280,7 @@ END
266
280
  # Close all the open tags
267
281
  @template_tabs.times { close }
268
282
 
269
- push_silent "@haml_is_haml = false"
270
- push_silent "end"
283
+ push_silent "@haml_is_haml = false\nend\n"
271
284
  end
272
285
 
273
286
  # Processes and deals with lowering indentation.
@@ -302,12 +315,11 @@ END
302
315
  when SCRIPT
303
316
  sub_line = line[1..-1]
304
317
  if sub_line[0] == SCRIPT
305
- push_script(sub_line[1..-1].strip.dump.gsub('\\#', '#'), false)
318
+ push_script(unescape_interpolation(sub_line[1..-1].strip), false)
306
319
  else
307
320
  push_script(sub_line, false)
308
321
  end
309
322
  when FLAT_SCRIPT
310
- warn(FLAT_WARNING) unless defined?(Test::Unit)
311
323
  push_flat_script(line[1..-1])
312
324
  when SILENT_SCRIPT
313
325
  sub_line = line[1..-1]
@@ -317,6 +329,8 @@ END
317
329
  if (@block_opened && !mbk) || line[1..-1].split(' ', 2)[0] == "case"
318
330
  push_and_tabulate([:script])
319
331
  end
332
+ else
333
+ start_haml_comment
320
334
  end
321
335
  when FILTER
322
336
  name = line[1..-1].downcase
@@ -374,6 +388,23 @@ END
374
388
  def is_multiline?(line) # ' '[0] == 32
375
389
  line && line.length > 1 && line[-1] == MULTILINE_CHAR_VALUE && line[-2] == 32
376
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
377
408
 
378
409
  # Takes <tt>@precompiled</tt>, a string buffer of Ruby code, and
379
410
  # evaluates it in the context of <tt>@scope_object</tt>, after preparing
@@ -391,11 +422,17 @@ END
391
422
  attr :haml_lineno # :nodoc:
392
423
  end
393
424
  end
425
+ @scope_object.class.instance_eval do
426
+ include CompiledTemplates
427
+ end
394
428
 
395
429
  begin
396
- # Evaluate the buffer in the context of the scope object
397
- @scope_object.instance_eval @precompiled
398
- @scope_object._haml_render &block
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)
399
436
  rescue Exception => e
400
437
  class << e
401
438
  include Haml::Error
@@ -408,9 +445,13 @@ END
408
445
  compile_error = e.message.scan(/\(eval\):([0-9]*):in `[-_a-zA-Z]*': compile error/)[0]
409
446
 
410
447
  if compile_error
411
- eval_line = compile_error[0].to_i
412
- line_marker = @precompiled.split("\n")[0...eval_line].grep(/@haml_lineno = [0-9]*/)[-1]
413
- lineno = line_marker.scan(/[0-9]+/)[0].to_i if line_marker
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
414
455
  end
415
456
 
416
457
  e.add_backtrace_entry(lineno, @options[:filename])
@@ -426,7 +467,7 @@ END
426
467
  # Evaluates <tt>text</tt> in the context of <tt>@scope_object</tt>, but
427
468
  # does not output the result.
428
469
  def push_silent(text, add_index = false, can_suppress = false)
429
- unless can_suppress && @options[:suppress_eval]
470
+ unless (can_suppress && options[:suppress_eval])
430
471
  if add_index
431
472
  @precompiled << "@haml_lineno = #{@index}\n#{text}\n"
432
473
  else
@@ -455,11 +496,7 @@ END
455
496
  def push_flat(text, spaces)
456
497
  tabulation = spaces - @flat_spaces
457
498
  tabulation = tabulation > -1 ? tabulation : 0
458
- if @filter_buffer
459
- @filter_buffer << "#{' ' * tabulation}#{text}\n"
460
- else
461
- @precompiled << "_hamlout.push_text(#{text.dump}, #{tabulation}, true)\n"
462
- end
499
+ @filter_buffer << "#{' ' * tabulation}#{text}\n"
463
500
  end
464
501
 
465
502
  # Causes <tt>text</tt> to be evaluated in the context of
@@ -482,13 +519,17 @@ END
482
519
  # Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
483
520
  # to be run on it afterwards.
484
521
  def push_flat_script(text)
485
- unless text.empty?
486
- push_script(text, true)
522
+ if text.empty?
523
+ raise SyntaxError.new("Tag has no content.")
487
524
  else
488
- unless @block_opened
489
- raise SyntaxError.new('Filters must have nested text.')
490
- end
491
- start_flat(false)
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])
492
533
  end
493
534
  end
494
535
 
@@ -502,12 +543,12 @@ END
502
543
  close_comment value
503
544
  when :element
504
545
  close_tag value
505
- when :flat
506
- close_flat value
507
546
  when :loud
508
547
  close_loud value
509
548
  when :filtered
510
549
  close_filtered value
550
+ when :haml_comment
551
+ close_haml_comment
511
552
  end
512
553
  end
513
554
 
@@ -532,17 +573,6 @@ END
532
573
  push_silent "_hamlout.close_comment(#{has_conditional}, #{@output_tabs})"
533
574
  end
534
575
 
535
- # Closes a flattened section.
536
- def close_flat(in_tag)
537
- @flat_spaces = -1
538
- if in_tag
539
- close
540
- else
541
- push_silent('_hamlout.stop_flat')
542
- @template_tabs -= 1
543
- end
544
- end
545
-
546
576
  # Closes a loud Ruby block.
547
577
  def close_loud(command)
548
578
  push_silent 'end', false, true
@@ -573,43 +603,167 @@ END
573
603
  @template_tabs -= 1
574
604
  end
575
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
+
576
698
  # Parses a line that will render as an XHTML tag, and adds the code that will
577
699
  # render that tag to <tt>@precompiled</tt>.
578
700
  def render_tag(line)
579
701
  matched = false
580
702
  line.scan(TAG_REGEX) do |tag_name, attributes, attributes_hash, object_ref, action, value|
581
703
  matched = true
582
- value = value.to_s
704
+ value = value.to_s.strip
583
705
 
584
706
  case action
585
707
  when '/'
586
708
  atomic = true
587
709
  when '=', '~'
588
710
  parse = true
589
- else
590
- value = value.strip
711
+
712
+ if value.first == '='
713
+ value = value[1..-1].strip.dump.gsub('\\#', '#')
714
+ end
591
715
  end
592
716
 
593
717
  flattened = (action == '~')
594
718
 
595
- warn(FLAT_WARNING) if flattened && !defined?(Test::Unit)
596
-
597
719
  value_exists = !value.empty?
598
- attributes_hash = "nil" if attributes_hash.nil? || @options[:suppress_eval]
720
+ literal_attributes = parse_literal_hash(attributes_hash)
721
+ attributes_hash = "{nil}" if attributes_hash.nil? || literal_attributes || @options[:suppress_eval]
599
722
  object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
600
723
 
601
- if @block_opened
724
+ if !attributes.empty? && '.#'.include?(attributes)
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
602
733
  if atomic
603
734
  raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.")
604
735
  elsif action == '=' || value_exists
605
736
  raise SyntaxError.new("Illegal Nesting: Nesting within a tag that already has content is illegal.")
606
737
  end
738
+ elsif atomic && value_exists
739
+ raise SyntaxError.new("Atomic tags can't have content.")
607
740
  elsif parse && !value_exists
608
- raise SyntaxError.new("No tag content to parse.")
741
+ raise SyntaxError.new("Tag has no content.")
609
742
  end
610
743
 
611
- push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{@output_tabs}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{attributes_hash}, #{object_ref}, #{flattened.inspect})", true
612
-
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
+
613
767
  unless atomic
614
768
  push_and_tabulate([:element, tag_name])
615
769
  @output_tabs += 1
@@ -622,7 +776,7 @@ END
622
776
  end
623
777
  close
624
778
  elsif flattened
625
- start_flat(true)
779
+ raise SyntaxError.new("Tag has no content.")
626
780
  end
627
781
  end
628
782
  end
@@ -684,18 +838,6 @@ END
684
838
  end
685
839
  push_text doctype
686
840
  end
687
-
688
- # Starts a flattened block.
689
- def start_flat(in_tag)
690
- # @flat_spaces is the number of indentations in the template
691
- # that forms the base of the flattened area
692
- if in_tag
693
- @to_close_stack.push([:flat, true])
694
- else
695
- push_and_tabulate([:flat])
696
- end
697
- @flat_spaces = @template_tabs * 2
698
- end
699
841
 
700
842
  # Starts a filtered block.
701
843
  def start_filtered(filter)
@@ -707,10 +849,18 @@ END
707
849
  @filter_buffer = String.new
708
850
  end
709
851
 
852
+ def unescape_interpolation(str)
853
+ str.dump.gsub('\\#', '#').gsub(/\#\{[^\}]+\}/) do |substr|
854
+ substr.gsub('\\"', '"')
855
+ end
856
+ end
857
+
710
858
  # Counts the tabulation of a line.
711
859
  def count_soft_tabs(line)
712
860
  spaces = line.index(/[^ ]/)
713
861
  if line[spaces] == ?\t
862
+ return nil if line.strip.empty?
863
+
714
864
  raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
715
865
  end
716
866
  [spaces, spaces/2]
@@ -724,23 +874,3 @@ END
724
874
  end
725
875
  end
726
876
  end
727
-
728
- class Hash # :nodoc:
729
- # Same as Hash#merge!, but recursively merges sub-hashes.
730
- def rec_merge!(other)
731
- other.each do |key, value|
732
- myval = self[key]
733
- if value.is_a?(Hash) && myval.is_a?(Hash)
734
- myval.rec_merge!(value)
735
- else
736
- self[key] = value
737
- end
738
- end
739
- self
740
- end
741
-
742
- def rec_merge(other)
743
- toret = self.clone
744
- toret.rec_merge! other
745
- end
746
- end