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
@@ -1,43 +1,13 @@
1
1
  module Haml
2
2
  # The abstract type of exception raised by Haml code.
3
- # Haml::SyntaxError includes this module,
4
- # as do all exceptions raised by Ruby code within Haml.
5
- #
6
- # Haml::Error encapsulates information about the exception,
7
- # such as the line of the Haml template it was raised on
8
- # and the Haml file that was being parsed (if applicable).
9
- # It also provides a handy way to rescue only exceptions raised
10
- # because of a faulty template.
11
- module Error
12
- # The line of the Haml template on which the exception was thrown.
13
- attr_reader :haml_line
14
-
15
- # The name of the file that was being parsed when the exception was raised.
16
- # This will be nil unless Haml is being used as an ActionView plugin.
17
- attr_reader :haml_filename
18
-
19
- # Adds a properly formatted entry to the exception's backtrace.
20
- # +lineno+ should be the line on which the error occurred.
21
- # +filename+ should be the file in which the error occurred,
22
- # if applicable (defaults to "(haml)").
23
- def add_backtrace_entry(lineno, filename = nil) # :nodoc:
24
- @haml_line = lineno
25
- @haml_filename = filename
26
- self.backtrace ||= []
27
- self.backtrace.unshift "#{filename || '(haml)'}:#{lineno}"
28
- end
29
- end
3
+ class Error < StandardError; end
30
4
 
31
5
  # SyntaxError is the type of exception raised when Haml encounters an
32
6
  # ill-formatted document.
33
7
  # It's not particularly interesting, except in that it includes Haml::Error.
34
- class SyntaxError < StandardError
35
- include Haml::Error
36
- end
8
+ class SyntaxError < Haml::Error; end
37
9
 
38
10
  # HamlError is the type of exception raised when Haml encounters an error
39
11
  # not of a syntactical nature, such as an undefined Filter.
40
- class HamlError < StandardError
41
- include Haml::Error
42
- end
12
+ class HamlError < Haml::Error; end
43
13
  end
@@ -17,7 +17,7 @@ module Haml
17
17
 
18
18
  def parse!
19
19
  begin
20
- @opts = OptionParser.new(&(method(:set_opts).to_proc))
20
+ @opts = OptionParser.new(&method(:set_opts))
21
21
  @opts.parse!(@args)
22
22
 
23
23
  process_result
@@ -26,14 +26,10 @@ module Haml
26
26
  rescue Exception => e
27
27
  raise e if e.is_a? SystemExit
28
28
 
29
- line = e.backtrace[0].scan(/:(.*)/)[0]
30
- puts "#{e.class} on line #{line}: #{e.message}"
29
+ $stderr.print "#{e.class} on line #{get_line e}: " if @options[:trace]
30
+ $stderr.puts e.message
31
31
 
32
- if @options[:trace]
33
- e.backtrace[1..-1].each { |t| puts " #{t}" }
34
- else
35
- puts " Use --trace to see traceback"
36
- end
32
+ e.backtrace[1..-1].each { |t| $stderr.puts " #{t}" } if @options[:trace]
37
33
 
38
34
  exit 1
39
35
  end
@@ -43,23 +39,20 @@ module Haml
43
39
  def to_s
44
40
  @opts.to_s
45
41
  end
42
+
43
+ protected
44
+
45
+ def get_line(exception)
46
+ exception.backtrace[0].scan(/:(\d+)/)[0]
47
+ end
46
48
 
47
49
  private
48
50
 
49
51
  def set_opts(opts)
50
- opts.on('--stdin', :NONE, 'Read input from standard input instead of an input file') do
52
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
51
53
  @options[:input] = $stdin
52
54
  end
53
55
 
54
- opts.on('--stdout', :NONE, 'Print output to standard output instead of an output file') do
55
- @options[:output] = $stdout
56
- end
57
-
58
- opts.on('-s', '--stdio', 'Read input from standard input and print output to standard output') do
59
- @options[:input] = $stdin
60
- @options[:output] = $stdout
61
- end
62
-
63
56
  opts.on('--trace', :NONE, 'Show a full traceback on error') do
64
57
  @options[:trace] = true
65
58
  end
@@ -76,49 +69,40 @@ module Haml
76
69
  end
77
70
 
78
71
  def process_result
79
- input = @options[:input]
80
- output = @options[:output]
81
-
82
- if input
83
- output ||= ARGV[0]
84
- else
85
- input ||= ARGV[0]
86
- output ||= ARGV[1]
87
- end
88
-
89
- unless input && output
90
- puts @opts
91
- exit 1
92
- end
93
-
94
- if input.is_a?(String) && !File.exists?(input)
95
- puts "File #{input} doesn't exist!"
96
- exit 1
97
- end
98
-
99
- unless input.is_a? IO
100
- input = File.open(input)
101
- input_file = true
102
- end
103
-
104
- unless output.is_a? IO
105
- output = File.open(output, "w")
106
- output_file = true
107
- end
72
+ input, output = @options[:input], @options[:output]
73
+ input_file, output_file = if input
74
+ [nil, open_file(ARGV[0], 'w')]
75
+ else
76
+ [open_file(ARGV[0]), open_file(ARGV[1], 'w')]
77
+ end
78
+
79
+ input ||= input_file
80
+ output ||= output_file
81
+ input ||= $stdin
82
+ output ||= $stdout
83
+
84
+ @options[:input], @options[:output] = input, output
85
+ end
108
86
 
109
- @options[:input] = input
110
- @options[:output] = output
87
+ def open_file(filename, flag = 'r')
88
+ return if filename.nil?
89
+ File.open(filename, flag)
111
90
  end
112
91
  end
113
92
 
114
93
  # A class encapsulating the executable functionality
115
94
  # specific to Haml and Sass.
116
95
  class HamlSass < Generic # :nodoc:
96
+ def initialize(args)
97
+ super
98
+ @options[:for_engine] = {}
99
+ end
100
+
117
101
  private
118
102
 
119
103
  def set_opts(opts)
120
104
  opts.banner = <<END
121
- Usage: #{@name.downcase} [options] (#{@name.downcase} file) (output file)
105
+ Usage: #{@name.downcase} [options] [INPUT] [OUTPUT]
122
106
 
123
107
  Description:
124
108
  Uses the #{@name} engine to parse the specified template
@@ -127,7 +111,7 @@ Description:
127
111
  Options:
128
112
  END
129
113
 
130
- opts.on('--rails RAILS_DIR', "Install Haml from the Gem to a Rails project") do |dir|
114
+ opts.on('--rails RAILS_DIR', "Install Haml and Sass from the Gem to a Rails project") do |dir|
131
115
  original_dir = dir
132
116
 
133
117
  dir = File.join(dir, 'vendor', 'plugins')
@@ -152,22 +136,19 @@ END
152
136
  end
153
137
 
154
138
  File.open(File.join(dir, 'init.rb'), 'w') do |file|
155
- file.puts <<END
156
- require 'rubygems'
157
- require 'haml'
158
- require 'haml/template'
159
- require 'sass'
160
- require 'sass/plugin'
161
-
162
- ActionView::Base.register_template_handler('haml', Haml::Template)
163
- Sass::Plugin.update_stylesheets
164
- END
139
+ file.puts "require 'rubygems'"
140
+ file << File.read(File.dirname(__FILE__) + "/../../init.rb")
165
141
  end
166
142
 
167
143
  puts "Haml plugin added to #{original_dir}"
168
144
  exit
169
145
  end
170
146
 
147
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
148
+ @options[:check_syntax] = true
149
+ @options[:output] = StringIO.new
150
+ end
151
+
171
152
  super
172
153
  end
173
154
 
@@ -185,6 +166,15 @@ END
185
166
  @name = "Sass"
186
167
  end
187
168
 
169
+ def set_opts(opts)
170
+ super
171
+
172
+ opts.on('-t', '--style NAME',
173
+ 'Output style. Can be nested (default), compact, or expanded.') do |name|
174
+ @options[:for_engine][:style] = name.to_sym
175
+ end
176
+ end
177
+
188
178
  def process_result
189
179
  super
190
180
  input = @options[:input]
@@ -192,7 +182,17 @@ END
192
182
 
193
183
  template = input.read()
194
184
  input.close() if input.is_a? File
195
- result = ::Sass::Engine.new(template).render
185
+
186
+ begin
187
+ # We don't need to do any special handling of @options[:check_syntax] here,
188
+ # because the Sass syntax checking happens alongside evaluation
189
+ # and evaluation doesn't actually evaluate any code anyway.
190
+ result = ::Sass::Engine.new(template, @options[:for_engine]).render
191
+ rescue ::Sass::SyntaxError => e
192
+ raise e if @options[:trace]
193
+ raise "Syntax error on line #{get_line e}: #{e.message}"
194
+ end
195
+
196
196
  output.write(result)
197
197
  output.close() if output.is_a? File
198
198
  end
@@ -213,7 +213,24 @@ END
213
213
 
214
214
  template = input.read()
215
215
  input.close() if input.is_a? File
216
- result = ::Haml::Engine.new(template).to_html
216
+
217
+ begin
218
+ engine = ::Haml::Engine.new(template, @options[:for_engine])
219
+ if @options[:check_syntax]
220
+ puts "Syntax OK"
221
+ return
222
+ end
223
+ result = engine.to_html
224
+ rescue Exception => e
225
+ raise e if @options[:trace]
226
+
227
+ case e
228
+ 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}"
230
+ else raise "Exception on line #{get_line e}: #{e.message}\n Use --trace for backtrace."
231
+ end
232
+ end
233
+
217
234
  output.write(result)
218
235
  output.close() if output.is_a? File
219
236
  end
@@ -238,7 +255,7 @@ END
238
255
 
239
256
  def set_opts(opts)
240
257
  opts.banner = <<END
241
- Usage: html2haml [options] (html file) (output file)
258
+ Usage: html2haml [options] [INPUT] [OUTPUT]
242
259
 
243
260
  Description: Transforms an HTML file into corresponding Haml code.
244
261
 
@@ -249,6 +266,10 @@ END
249
266
  @module_opts[:rhtml] = true
250
267
  end
251
268
 
269
+ opts.on('-x', '--xhtml', 'Parse the input using the more strict XHTML parser.') do
270
+ @module_opts[:xhtml] = true
271
+ end
272
+
252
273
  super
253
274
  end
254
275
 
@@ -273,7 +294,7 @@ END
273
294
 
274
295
  def set_opts(opts)
275
296
  opts.banner = <<END
276
- Usage: css2sass [options] (css file) (output file)
297
+ Usage: css2sass [options] [INPUT] [OUTPUT]
277
298
 
278
299
  Description: Transforms a CSS file into corresponding Sass code.
279
300
 
@@ -7,22 +7,12 @@ require 'erb'
7
7
  require 'sass/engine'
8
8
  require 'stringio'
9
9
 
10
- volatile_requires = ['rubygems', 'redcloth', 'bluecloth']
11
- NOT_LOADED = [] unless defined?(NOT_LOADED)
12
- volatile_requires.each do |file|
13
- begin
14
- require file
15
- rescue LoadError
16
- NOT_LOADED.push file
17
- end
18
- end
10
+ begin
11
+ require 'rubygems'
12
+ rescue LoadError; end
19
13
 
20
14
  class ERB; alias_method :render, :result; end
21
15
 
22
- unless NOT_LOADED.include? 'bluecloth'
23
- class BlueCloth; alias_method :render, :to_html; end
24
- end
25
-
26
16
  module Haml
27
17
  module Filters
28
18
  class Plain
@@ -60,26 +50,66 @@ module Haml
60
50
  end
61
51
  end
62
52
 
63
- unless NOT_LOADED.include? 'bluecloth'
64
- Markdown = BlueCloth unless defined?(Markdown)
53
+ class LazyLoaded
54
+ def initialize(*reqs)
55
+ reqs[0...-1].each do |req|
56
+ begin
57
+ @required = req
58
+ require @required
59
+ return
60
+ rescue LoadError; end # RCov doesn't see this, but it is run
61
+ end
62
+
63
+ begin
64
+ @required = reqs[-1]
65
+ require @required
66
+ rescue LoadError => e
67
+ classname = self.class.to_s.gsub(/\w+::/, '')
68
+
69
+ if reqs.size == 1
70
+ raise HamlError.new("Can't run #{classname} filter; required file '#{reqs.first}' not found")
71
+ else
72
+ raise HamlError.new("Can't run #{classname} filter; required #{reqs.map { |r| "'#{r}'" }.join(' or ')}, but none were found")
73
+ end
74
+ end
75
+ end
65
76
  end
77
+
78
+ class RedCloth < LazyLoaded
79
+ def initialize(text)
80
+ super('redcloth')
81
+ @engine = ::RedCloth.new(text)
82
+ end
66
83
 
67
- unless NOT_LOADED.include? 'redcloth'
68
- class ::RedCloth; alias_method :render, :to_html; end
84
+ def render
85
+ @engine.to_html
86
+ end
87
+ end
69
88
 
70
- # Uses RedCloth to provide only Textile (not Markdown) parsing
71
- class Textile < RedCloth
72
- def render
73
- self.to_html(:textile)
89
+ # Uses RedCloth to provide only Textile (not Markdown) parsing
90
+ class Textile < RedCloth
91
+ def render
92
+ @engine.to_html(:textile)
93
+ end
94
+ end
95
+
96
+ # Uses BlueCloth or RedCloth to provide only Markdown (not Textile) parsing
97
+ class Markdown < LazyLoaded
98
+ def initialize(text)
99
+ super('bluecloth', 'redcloth')
100
+
101
+ if @required == 'bluecloth'
102
+ @engine = ::BlueCloth.new(text)
103
+ else
104
+ @engine = ::RedCloth.new(text)
74
105
  end
75
106
  end
76
107
 
77
- unless defined?(Markdown)
78
- # Uses RedCloth to provide only Markdown (not Textile) parsing
79
- class Markdown < RedCloth
80
- def render
81
- self.to_html(:markdown)
82
- end
108
+ def render
109
+ if @engine.is_a?(::BlueCloth)
110
+ @engine.to_html
111
+ else
112
+ @engine.to_html(:markdown)
83
113
  end
84
114
  end
85
115
  end
@@ -17,20 +17,56 @@ module Haml
17
17
  @@action_view_defined
18
18
  end
19
19
 
20
+ # Note: this does *not* need to be called
21
+ # when using Haml helpers normally
22
+ # in Rails.
23
+ #
24
+ # Initializes the current object
25
+ # as though it were in the same context
26
+ # as a normal ActionView rendering
27
+ # using Haml.
28
+ # This is useful if you want to use the helpers in a context
29
+ # other than the normal setup with ActionView.
30
+ # For example:
31
+ #
32
+ # context = Object.new
33
+ # class << context
34
+ # include Haml::Helpers
35
+ # end
36
+ # context.init_haml_helpers
37
+ # context.open :p, "Stuff"
38
+ #
39
+ def init_haml_helpers
40
+ @haml_is_haml = true
41
+ @haml_stack = [Haml::Buffer.new]
42
+ nil
43
+ end
44
+
45
+ # call-seq:
46
+ # find_and_preserve(input)
47
+ # find_and_preserve {...}
48
+ #
20
49
  # Isolates the whitespace-sensitive tags in the string and uses preserve
21
50
  # to convert any endlines inside them into HTML entities for endlines.
22
- def find_and_preserve(input)
51
+ def find_and_preserve(input = '', &block)
52
+ return find_and_preserve(capture_haml(&block)) if block
53
+
23
54
  input = input.to_s
24
- input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im) do |tag, contents|
25
- input = input.gsub(contents, preserve(contents))
55
+ input.gsub(/<(textarea|code|pre)([^>]*)>(.*?)(<\/\1>)/im) do
56
+ "<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
26
57
  end
27
- input
28
58
  end
29
59
 
60
+ # call-seq:
61
+ # preserve(input)
62
+ # preserve {...}
63
+ #
30
64
  # Takes any string, finds all the endlines and converts them to
31
65
  # HTML entities for endlines so they'll render correctly in
32
66
  # whitespace-sensitive tags without screwing up the indentation.
33
- def preserve(input)
67
+ def preserve(input = '', &block)
68
+ return preserve(capture_haml(&block)) if block
69
+
34
70
  input.gsub(/\n/, '&#x000A;').gsub(/\r/, '')
35
71
  end
36
72
 
@@ -256,15 +292,22 @@ module Haml
256
292
  attributes = alt_atts
257
293
  end
258
294
 
259
- puts "<#{name}#{buffer.build_attributes(attributes)}>"
260
- tab_up
295
+ if text.nil? && block.nil?
296
+ puts "<#{name}#{Haml::Precompiler.build_attributes(buffer.options[:attr_wrapper], attributes)} />"
297
+ return nil
298
+ end
299
+
300
+ puts "<#{name}#{Haml::Precompiler.build_attributes(buffer.options[:attr_wrapper], attributes)}>"
301
+ unless text && text.empty?
302
+ tab_up
261
303
  # Print out either the text (using push_text) or call the block and add an endline
262
304
  if text
263
305
  puts(text)
264
306
  elsif block
265
307
  block.call
266
308
  end
267
- tab_down
309
+ tab_down
310
+ end
268
311
  puts "</#{name}>"
269
312
  nil
270
313
  end
@@ -314,7 +357,7 @@ module Haml
314
357
  def is_haml?
315
358
  @haml_is_haml
316
359
  end
317
-
360
+
318
361
  include ActionViewExtensions if self.const_defined? "ActionViewExtensions"
319
362
  end
320
363
  end