haml 1.8.2 → 2.0.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 (77) hide show
  1. data/FAQ +138 -0
  2. data/MIT-LICENSE +1 -1
  3. data/{README → README.rdoc} +66 -3
  4. data/Rakefile +111 -147
  5. data/VERSION +1 -1
  6. data/bin/css2sass +0 -0
  7. data/bin/haml +0 -0
  8. data/bin/html2haml +0 -0
  9. data/bin/sass +0 -0
  10. data/init.rb +6 -1
  11. data/lib/haml.rb +464 -201
  12. data/lib/haml/buffer.rb +117 -63
  13. data/lib/haml/engine.rb +63 -44
  14. data/lib/haml/error.rb +16 -6
  15. data/lib/haml/exec.rb +37 -7
  16. data/lib/haml/filters.rb +213 -68
  17. data/lib/haml/helpers.rb +95 -60
  18. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  19. data/lib/haml/helpers/action_view_mods.rb +54 -6
  20. data/lib/haml/html.rb +6 -6
  21. data/lib/haml/precompiler.rb +254 -133
  22. data/lib/haml/template.rb +3 -6
  23. data/lib/haml/template/patch.rb +9 -2
  24. data/lib/haml/template/plugin.rb +52 -23
  25. data/lib/sass.rb +157 -12
  26. data/lib/sass/constant.rb +22 -22
  27. data/lib/sass/constant/color.rb +13 -13
  28. data/lib/sass/constant/literal.rb +7 -7
  29. data/lib/sass/constant/number.rb +9 -9
  30. data/lib/sass/constant/operation.rb +4 -4
  31. data/lib/sass/constant/string.rb +3 -3
  32. data/lib/sass/css.rb +104 -31
  33. data/lib/sass/engine.rb +120 -39
  34. data/lib/sass/error.rb +1 -1
  35. data/lib/sass/plugin.rb +14 -3
  36. data/lib/sass/plugin/merb.rb +6 -2
  37. data/lib/sass/tree/attr_node.rb +5 -5
  38. data/lib/sass/tree/directive_node.rb +2 -7
  39. data/lib/sass/tree/node.rb +1 -12
  40. data/lib/sass/tree/rule_node.rb +39 -31
  41. data/lib/sass/tree/value_node.rb +1 -1
  42. data/test/benchmark.rb +67 -80
  43. data/test/haml/engine_test.rb +284 -84
  44. data/test/haml/helper_test.rb +51 -15
  45. data/test/haml/results/content_for_layout.xhtml +1 -2
  46. data/test/haml/results/eval_suppressed.xhtml +2 -4
  47. data/test/haml/results/filters.xhtml +44 -15
  48. data/test/haml/results/helpers.xhtml +2 -3
  49. data/test/haml/results/just_stuff.xhtml +2 -6
  50. data/test/haml/results/nuke_inner_whitespace.xhtml +34 -0
  51. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  52. data/test/haml/results/original_engine.xhtml +3 -7
  53. data/test/haml/results/partials.xhtml +1 -0
  54. data/test/haml/results/tag_parsing.xhtml +1 -6
  55. data/test/haml/results/very_basic.xhtml +2 -4
  56. data/test/haml/results/whitespace_handling.xhtml +13 -21
  57. data/test/haml/template_test.rb +8 -15
  58. data/test/haml/templates/_partial.haml +1 -0
  59. data/test/haml/templates/filters.haml +48 -7
  60. data/test/haml/templates/just_stuff.haml +1 -2
  61. data/test/haml/templates/nuke_inner_whitespace.haml +26 -0
  62. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  63. data/test/haml/templates/tag_parsing.haml +0 -3
  64. data/test/haml/test_helper.rb +15 -0
  65. data/test/sass/engine_test.rb +80 -34
  66. data/test/sass/plugin_test.rb +1 -1
  67. data/test/sass/results/import.css +2 -2
  68. data/test/sass/results/mixins.css +95 -0
  69. data/test/sass/results/multiline.css +24 -0
  70. data/test/sass/templates/import.sass +4 -1
  71. data/test/sass/templates/importee.sass +4 -0
  72. data/test/sass/templates/mixins.sass +76 -0
  73. data/test/sass/templates/multiline.sass +20 -0
  74. metadata +65 -51
  75. data/lib/haml/util.rb +0 -18
  76. data/test/haml/runner.rb +0 -16
  77. data/test/profile.rb +0 -65
@@ -1,13 +1,23 @@
1
1
  module Haml
2
- # The abstract type of exception raised by Haml code.
3
- class Error < StandardError; end
2
+ # An exception raised by Haml code.
3
+ class Error < Exception
4
+ # :stopdoc:
5
+
6
+ # By default, an error is taken to refer to the line of the template
7
+ # that was being processed when the exception was raised.
8
+ # However, if line_offset is non-zero, it's added to that line number
9
+ # to get the line to report for the error.
10
+ attr_reader :line_offset
11
+
12
+ def initialize(message = nil, line_offset = 0)
13
+ super(message)
14
+ @line_offset = line_offset
15
+ end
16
+ # :startdoc:
17
+ end
4
18
 
5
19
  # SyntaxError is the type of exception raised when Haml encounters an
6
20
  # ill-formatted document.
7
21
  # It's not particularly interesting, except in that it includes Haml::Error.
8
22
  class SyntaxError < Haml::Error; end
9
-
10
- # HamlError is the type of exception raised when Haml encounters an error
11
- # not of a syntactical nature, such as an undefined Filter.
12
- class HamlError < Haml::Error; end
13
23
  end
@@ -21,7 +21,7 @@ module Haml
21
21
  @opts.parse!(@args)
22
22
 
23
23
  process_result
24
-
24
+
25
25
  @options
26
26
  rescue Exception => e
27
27
  raise e if e.is_a? SystemExit
@@ -43,9 +43,13 @@ module Haml
43
43
  protected
44
44
 
45
45
  def get_line(exception)
46
+ # SyntaxErrors have weird line reporting
47
+ # when there's trailing whitespace,
48
+ # which there is for Haml documents.
49
+ return exception.message.scan(/:(\d+)/)[0] if exception.is_a?(::SyntaxError)
46
50
  exception.backtrace[0].scan(/:(\d+)/)[0]
47
51
  end
48
-
52
+
49
53
  private
50
54
 
51
55
  def set_opts(opts)
@@ -63,7 +67,7 @@ module Haml
63
67
  end
64
68
 
65
69
  opts.on_tail("-v", "--version", "Print version") do
66
- puts("Haml " + File.read(File.dirname(__FILE__) + '/../../VERSION'))
70
+ puts("Haml #{::Haml.version[:string]}")
67
71
  exit
68
72
  end
69
73
  end
@@ -110,7 +114,7 @@ Description:
110
114
 
111
115
  Options:
112
116
  END
113
-
117
+
114
118
  opts.on('--rails RAILS_DIR', "Install Haml and Sass from the Gem to a Rails project") do |dir|
115
119
  original_dir = dir
116
120
 
@@ -145,6 +149,7 @@ END
145
149
  end
146
150
 
147
151
  opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
152
+ require 'stringio'
148
153
  @options[:check_syntax] = true
149
154
  @options[:output] = StringIO.new
150
155
  end
@@ -170,7 +175,7 @@ END
170
175
  super
171
176
 
172
177
  opts.on('-t', '--style NAME',
173
- 'Output style. Can be nested (default), compact, or expanded.') do |name|
178
+ 'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
174
179
  @options[:for_engine][:style] = name.to_sym
175
180
  end
176
181
  end
@@ -206,6 +211,25 @@ END
206
211
  @name = "Haml"
207
212
  end
208
213
 
214
+ def set_opts(opts)
215
+ super
216
+
217
+ opts.on('-t', '--style NAME',
218
+ 'Output style. Can be indented (default) or ugly.') do |name|
219
+ @options[:for_engine][:ugly] = true if name.to_sym == :ugly
220
+ end
221
+
222
+ opts.on('-f', '--format NAME',
223
+ 'Output format. Can be xhtml (default), html4, or html5.') do |name|
224
+ @options[:for_engine][:format] = name.to_sym
225
+ end
226
+
227
+ opts.on('-e', '--escape-html',
228
+ 'Escape HTML characters (like ampersands and angle brackets) by default.') do
229
+ @options[:for_engine][:escape_html] = true
230
+ end
231
+ end
232
+
209
233
  def process_result
210
234
  super
211
235
  input = @options[:input]
@@ -226,7 +250,7 @@ END
226
250
 
227
251
  case e
228
252
  when ::Haml::SyntaxError; raise "Syntax error on line #{get_line e}: #{e.message}"
229
- when ::Haml::HamlError; raise "Haml error on line #{get_line e}: #{e.message}"
253
+ when ::Haml::Error; raise "Haml error on line #{get_line e}: #{e.message}"
230
254
  else raise "Exception on line #{get_line e}: #{e.message}\n Use --trace for backtrace."
231
255
  end
232
256
  end
@@ -289,6 +313,8 @@ END
289
313
  def initialize(args)
290
314
  super
291
315
 
316
+ @module_opts = {}
317
+
292
318
  require 'sass/css'
293
319
  end
294
320
 
@@ -301,6 +327,10 @@ Description: Transforms a CSS file into corresponding Sass code.
301
327
  Options:
302
328
  END
303
329
 
330
+ opts.on('-a', '--alternate', 'Output using alternative Sass syntax (margin: 1px)') do
331
+ @module_opts[:alternate] = true
332
+ end
333
+
304
334
  super
305
335
  end
306
336
 
@@ -310,7 +340,7 @@ END
310
340
  input = @options[:input]
311
341
  output = @options[:output]
312
342
 
313
- output.write(::Sass::CSS.new(input).render)
343
+ output.write(::Sass::CSS.new(input, @module_opts).render)
314
344
  end
315
345
  end
316
346
  end
@@ -1,115 +1,260 @@
1
1
  # This file contains redefinitions of and wrappers around various text
2
2
  # filters so they can be used as Haml filters.
3
3
 
4
- # :stopdoc:
5
-
6
- require 'erb'
7
- require 'sass/engine'
8
- require 'stringio'
9
-
10
- begin
11
- require 'rubygems'
12
- rescue LoadError; end
13
-
14
- class ERB; alias_method :render, :result; end
15
-
16
4
  module Haml
5
+ # The module containing the default filters,
6
+ # as well as the base module,
7
+ # Haml::Filters::Base.
17
8
  module Filters
18
- class Plain
19
- def initialize(text)
20
- @text = text
21
- end
9
+ # Returns a hash of defined filters.
10
+ def self.defined
11
+ @defined ||= {}
12
+ end
22
13
 
23
- def render
24
- @text
14
+ # The base module for Haml filters.
15
+ # User-defined filters should be modules including this module.
16
+ #
17
+ # A user-defined filter should override either Base#render or Base #compile.
18
+ # Base#render is the most common.
19
+ # It takes a string, the filter source,
20
+ # and returns another string,
21
+ # the result of the filter.
22
+ # For example:
23
+ #
24
+ # module Haml::Filters::Sass
25
+ # include Haml::Filters::Base
26
+ #
27
+ # def render(text)
28
+ # ::Sass::Engine.new(text).render
29
+ # end
30
+ # end
31
+ #
32
+ # For details on overriding #compile, see its documentation.
33
+ #
34
+ module Base
35
+ def self.included(base) # :nodoc:
36
+ Filters.defined[base.name.split("::").last.downcase] = base
37
+ base.extend(base)
25
38
  end
26
- end
27
39
 
28
- class Ruby
29
- def initialize(text)
30
- @text = text
40
+ # Takes a string, the source text that should be passed to the filter,
41
+ # and returns the string resulting from running the filter on <tt>text</tt>.
42
+ #
43
+ # This should be overridden in most individual filter modules
44
+ # to render text with the given filter.
45
+ # If compile is overridden, however, render doesn't need to be.
46
+ def render(text)
47
+ raise Error.new("#{self.inspect}#render not defined!")
31
48
  end
32
49
 
33
- def render
34
- old_stdout = $stdout
35
- $stdout = StringIO.new
36
- Object.new.instance_eval(@text)
37
- old_stdout, $stdout = $stdout, old_stdout
38
- old_stdout.pos = 0
39
- old_stdout.read
50
+ def internal_compile(*args) # :nodoc:
51
+ resolve_lazy_requires
52
+ compile(*args)
40
53
  end
41
- end
42
54
 
43
- class Preserve
44
- def initialize(text)
45
- @text = text
55
+ # compile should be overridden when a filter needs to have access
56
+ # to the Haml evaluation context.
57
+ # Rather than applying a filter to a string at compile-time,
58
+ # compile uses the Haml::Precompiler instance to compile the string to Ruby code
59
+ # that will be executed in the context of the active Haml template.
60
+ #
61
+ # Warning: the Haml::Precompiler interface is neither well-documented
62
+ # nor guaranteed to be stable.
63
+ # If you want to make use of it,
64
+ # you'll probably need to look at the source code
65
+ # and should test your filter when upgrading to new Haml versions.
66
+ def compile(precompiler, text)
67
+ resolve_lazy_requires
68
+ filter = self
69
+ precompiler.instance_eval do
70
+ if contains_interpolation?(text)
71
+ return if options[:suppress_eval]
72
+
73
+ push_script(<<RUBY, false)
74
+ find_and_preserve(#{filter.inspect}.render(#{unescape_interpolation(text)}))
75
+ RUBY
76
+ return
77
+ end
78
+
79
+ rendered = Haml::Helpers::find_and_preserve(filter.render(text), precompiler.options[:preserve])
80
+
81
+ if !options[:ugly]
82
+ push_text(rendered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
83
+ else
84
+ push_text(rendered.rstrip)
85
+ end
86
+ end
46
87
  end
47
88
 
48
- def render
49
- Haml::Helpers.preserve(@text)
89
+ # This becomes a class method of modules that include Base.
90
+ # It allows the module to specify one or more Ruby files
91
+ # that Haml should try to require when compiling the filter.
92
+ #
93
+ # The first file specified is tried first,
94
+ # then the second, etc.
95
+ # If none are found, the compilation throws an exception.
96
+ #
97
+ # For example:
98
+ #
99
+ # module Haml::Filters::Markdown
100
+ # lazy_require 'bluecloth', 'redcloth'
101
+ #
102
+ # ...
103
+ # end
104
+ #
105
+ def lazy_require(*reqs)
106
+ @lazy_requires = reqs
50
107
  end
51
- end
52
108
 
53
- class LazyLoaded
54
- def initialize(*reqs)
55
- reqs[0...-1].each do |req|
109
+ private
110
+
111
+ def resolve_lazy_requires
112
+ return unless @lazy_requires
113
+
114
+ @lazy_requires[0...-1].each do |req|
56
115
  begin
57
116
  @required = req
58
117
  require @required
59
118
  return
60
119
  rescue LoadError; end # RCov doesn't see this, but it is run
61
120
  end
62
-
121
+
63
122
  begin
64
- @required = reqs[-1]
123
+ @required = @lazy_requires[-1]
65
124
  require @required
66
125
  rescue LoadError => e
67
126
  classname = self.class.to_s.gsub(/\w+::/, '')
68
127
 
69
- if reqs.size == 1
70
- raise HamlError.new("Can't run #{classname} filter; required file '#{reqs.first}' not found")
128
+ if @lazy_requires.size == 1
129
+ raise Error.new("Can't run #{classname} filter; required file '#{@lazy_requires.first}' not found")
71
130
  else
72
- raise HamlError.new("Can't run #{classname} filter; required #{reqs.map { |r| "'#{r}'" }.join(' or ')}, but none were found")
131
+ raise Error.new("Can't run #{classname} filter; required #{@lazy_requires.map { |r| "'#{r}'" }.join(' or ')}, but none were found")
73
132
  end
74
133
  end
75
134
  end
76
135
  end
77
-
78
- class RedCloth < LazyLoaded
79
- def initialize(text)
80
- super('redcloth')
81
- @engine = ::RedCloth.new(text)
136
+ end
137
+ end
138
+
139
+ # :stopdoc:
140
+
141
+ begin
142
+ require 'rubygems'
143
+ rescue LoadError; end
144
+
145
+ module Haml
146
+ module Filters
147
+ module Plain
148
+ include Base
149
+
150
+ def render(text); text; end
151
+ end
152
+
153
+ module Javascript
154
+ include Base
155
+
156
+ def render(text)
157
+ <<END
158
+ <script type='text/javascript'>
159
+ //<![CDATA[
160
+ #{text.rstrip.gsub("\n", "\n ")}
161
+ //]]>
162
+ </script>
163
+ END
164
+ end
165
+ end
166
+
167
+ module Cdata
168
+ include Base
169
+
170
+ def render(text)
171
+ "<![CDATA[#{("\n" + text).rstrip.gsub("\n", "\n ")}\n]]>"
172
+ end
173
+ end
174
+
175
+ module Escaped
176
+ include Base
177
+
178
+ def render(text)
179
+ Haml::Helpers.html_escape text
180
+ end
181
+ end
182
+
183
+ module Ruby
184
+ include Base
185
+ lazy_require 'stringio'
186
+
187
+ def compile(precompiler, text)
188
+ return if precompiler.options[:suppress_eval]
189
+ precompiler.instance_eval do
190
+ push_silent <<-END.gsub("\n", ';')
191
+ _haml_old_stdout = $stdout
192
+ $stdout = StringIO.new(_hamlout.buffer, 'a')
193
+ #{text}
194
+ _haml_old_stdout, $stdout = $stdout, _haml_old_stdout
195
+ _haml_old_stdout.close
196
+ END
197
+ end
198
+ end
199
+ end
200
+
201
+ module Preserve
202
+ include Base
203
+
204
+ def render(text)
205
+ Haml::Helpers.preserve text
206
+ end
207
+ end
208
+
209
+ module Sass
210
+ include Base
211
+ lazy_require 'sass/engine'
212
+
213
+ def render(text)
214
+ ::Sass::Engine.new(text).render
215
+ end
216
+ end
217
+
218
+ module ERB
219
+ include Base
220
+ lazy_require 'erb'
221
+
222
+ def compile(precompiler, text)
223
+ return if precompiler.options[:suppress_eval]
224
+ src = ::ERB.new(text).src.sub(/^_erbout = '';/, "").gsub("\n", ';')
225
+ precompiler.send(:push_silent, src)
82
226
  end
227
+ end
228
+
229
+ module RedCloth
230
+ include Base
231
+ lazy_require 'redcloth'
83
232
 
84
- def render
85
- @engine.to_html
233
+ def render(text)
234
+ ::RedCloth.new(text).to_html
86
235
  end
87
236
  end
88
-
237
+
89
238
  # Uses RedCloth to provide only Textile (not Markdown) parsing
90
- class Textile < RedCloth
91
- def render
92
- @engine.to_html(:textile)
239
+ module Textile
240
+ include Base
241
+ lazy_require 'redcloth'
242
+
243
+ def render(text)
244
+ ::RedCloth.new(text).to_html(:textile)
93
245
  end
94
246
  end
95
247
 
96
248
  # Uses BlueCloth or RedCloth to provide only Markdown (not Textile) parsing
97
- class Markdown < LazyLoaded
98
- def initialize(text)
99
- super('bluecloth', 'redcloth')
249
+ module Markdown
250
+ include Base
251
+ lazy_require 'bluecloth', 'redcloth'
100
252
 
253
+ def render(text)
101
254
  if @required == 'bluecloth'
102
- @engine = ::BlueCloth.new(text)
103
- else
104
- @engine = ::RedCloth.new(text)
105
- end
106
- end
107
-
108
- def render
109
- if @engine.is_a?(::BlueCloth)
110
- @engine.to_html
255
+ ::BlueCloth.new(text).to_html
111
256
  else
112
- @engine.to_html(:markdown)
257
+ ::RedCloth.new(text).to_html(:markdown)
113
258
  end
114
259
  end
115
260
  end