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
@@ -4,24 +4,7 @@ require 'active_support'
4
4
  require 'action_view'
5
5
 
6
6
  module Haml
7
- # This class interfaces with ActionView
8
- # to make Haml usable as a Ruby on Rails plugin.
9
- # It usually shouldn't need to be used by end users.
10
- # Just in case, though, here's what you might do to render
11
- # <tt>templates/index.haml</tt>:
12
- #
13
- # ActionView::Base.register_template_handler("haml", Haml::Template)
14
- # base = ActionView::Base.new("templates")
15
- # base.render("index")
16
- #
17
- # Or, if you want to really get into the nitty-gritty:
18
- #
19
- # base = ActionView::Base.new
20
- # template = Haml::Template.new(base)
21
- # template.render("templates/index.haml")
22
- #
23
7
  class Template
24
-
25
8
  class << self
26
9
  @@options = {}
27
10
 
@@ -35,65 +18,29 @@ module Haml
35
18
  @@options = value
36
19
  end
37
20
  end
38
-
39
- # Creates a new Haml::Template object that uses <tt>view</tt>
40
- # to render its templates.
41
- def initialize(view)
42
- @view = view
43
- end
44
-
45
- # Renders the file at the location <tt>template</tt>,
46
- # with <tt>local_assigns</tt> available as local variables within the template.
47
- # Returns the result as a string.
48
- def render(template, local_assigns={})
49
- @view.instance_eval do
50
- evaluate_assigns
51
- end
52
-
53
- options = @@options.dup
54
- locals = options[:locals] || {}
55
- locals.merge! local_assigns
56
- options[:locals] = locals
57
-
58
- if @view.haml_inline
59
- engine = Haml::Engine.new(template, options)
60
- else
61
- options[:filename] ||= template
62
- engine = Haml::Engine.new(File.read(template), options)
63
- end
64
-
65
- yield_proc = @view.instance_eval do
66
- proc { |*name| instance_variable_get("@content_for_#{name.first || 'layout'}") }
67
- end
68
-
69
- engine.to_html(@view) { |*args| yield_proc.call(*args) }
70
-
71
- end
72
21
  end
73
22
  end
74
23
 
75
- # This module refers to the ActionView module that's part of Ruby on Rails.
76
- # Haml can be used as an alternate templating engine for it,
77
- # and includes several modifications to make it more Haml-friendly.
78
- # The documentation can be found
79
- # here[http://rubyonrails.org/api/classes/ActionView/Base.html].
80
- module ActionView
81
- class Base # :nodoc:
82
- attr :haml_inline
83
-
84
- alias_method :read_template_file_old, :read_template_file
85
- def read_template_file(template_path, extension)
86
- if extension =~ /haml/i
87
- template_path
88
- else
89
- read_template_file_old(template_path, extension)
90
- end
91
- end
24
+ # Decide how we want to load Haml into Rails.
25
+ # Patching was necessary for versions <= 2.0.1,
26
+ # but we can make it a normal handler for higher versions.
27
+ if defined?(ActionView::TemplateHandler)
28
+ require 'haml/template/plugin'
29
+ else
30
+ require 'haml/template/patch'
31
+ end
92
32
 
93
- alias_method :render_template_old, :render_template
94
- def render_template(template_extension, template, file_path = nil, local_assigns = {})
95
- @haml_inline = !template.nil?
96
- render_template_old(template_extension, template, file_path, local_assigns)
97
- end
33
+ if defined?(RAILS_ROOT)
34
+ # Update init.rb to the current version
35
+ # if it's out of date.
36
+ #
37
+ # We can probably remove this as of v1.9,
38
+ # because the new init file is sufficiently flexible
39
+ # to not need updating.
40
+ rails_init_file = File.join(RAILS_ROOT, 'vendor', 'plugins', 'haml', 'init.rb')
41
+ haml_init_file = File.join(File.dirname(__FILE__), '..', '..', 'init.rb')
42
+ if File.exists?(rails_init_file)
43
+ require 'fileutils'
44
+ FileUtils.cp(haml_init_file, rails_init_file) unless FileUtils.cmp(rails_init_file, haml_init_file)
98
45
  end
99
46
  end
@@ -0,0 +1,51 @@
1
+ # This file makes Haml work with Rails
2
+ # by monkeypatching the core template-compilation methods.
3
+ # This is necessary for versions <= 2.0.1 because the template handler API
4
+ # wasn't sufficiently powerful to deal with caching and so forth.
5
+
6
+ # This module refers to the ActionView module that's part of Ruby on Rails.
7
+ # Haml can be used as an alternate templating engine for it,
8
+ # and includes several modifications to make it more Haml-friendly.
9
+ # The documentation can be found
10
+ # here[http://rubyonrails.org/api/classes/ActionView/Base.html].
11
+ module ActionView
12
+ class Base # :nodoc:
13
+ def delegate_template_exists_with_haml(template_path)
14
+ template_exists?(template_path, :haml) && [:haml]
15
+ end
16
+ alias_method :delegate_template_exists_without_haml, :delegate_template_exists?
17
+ alias_method :delegate_template_exists?, :delegate_template_exists_with_haml
18
+
19
+ def compile_template_with_haml(extension, template, file_name, local_assigns)
20
+ return compile_haml(template, file_name, local_assigns) if extension.to_s == "haml"
21
+ compile_template_without_haml(extension, template, file_name, local_assigns)
22
+ end
23
+ alias_method :compile_template_without_haml, :compile_template
24
+ alias_method :compile_template, :compile_template_with_haml
25
+
26
+ def compile_haml(template, file_name, local_assigns)
27
+ render_symbol = assign_method_name(:haml, template, file_name)
28
+ locals = local_assigns.keys
29
+
30
+ @@template_args[render_symbol] ||= {}
31
+ locals_keys = @@template_args[render_symbol].keys | locals
32
+ @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
33
+
34
+ options = Haml::Template.options.dup
35
+ options[:filename] = file_name || 'compiled-template'
36
+
37
+ begin
38
+ Haml::Engine.new(template, options).def_method(CompiledTemplates, render_symbol, *locals)
39
+ rescue Exception => e
40
+ if logger
41
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
42
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
43
+ end
44
+
45
+ raise TemplateError.new(@base_path, file_name || template, @assigns, template, e)
46
+ end
47
+
48
+ @@compile_time[render_symbol] = Time.now
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ # This file makes Haml work with Rails
2
+ # using the > 2.0.1 template handler API.
3
+
4
+ module Haml
5
+ class Template
6
+ def self.line_offset
7
+ 1
8
+ end
9
+
10
+ def initialize(view)
11
+ @view = view
12
+ end
13
+
14
+ def compile(template)
15
+ options = Haml::Template.options.dup
16
+ Haml::Engine.new(template, options).send(:precompiled_with_ambles, [])
17
+ end
18
+ end
19
+ end
20
+
21
+ ActionView::Base.register_template_handler(:haml, Haml::Template)
@@ -82,6 +82,15 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
82
82
  # <attribute>
83
83
  # ...
84
84
  #
85
+ # Like CSS, you can stretch rules over multiple lines.
86
+ # However, unlike CSS, you can only do this if each line but the last
87
+ # ends with a comma.
88
+ # For example:
89
+ #
90
+ # .users #userTab,
91
+ # .posts #postsTab
92
+ # <attributes>
93
+ #
85
94
  # === Attributes
86
95
  #
87
96
  # There are two different ways to write CSS attrbibutes.
@@ -117,6 +126,10 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
117
126
  # color: #00ff00;
118
127
  # width: 97% }
119
128
  #
129
+ # By default, either attribute syntax may be used.
130
+ # If you want to force one or the other,
131
+ # see the :attribute_syntax option below.
132
+ #
120
133
  # === Nested Rules
121
134
  #
122
135
  # Rules can also be nested within each other.
@@ -461,6 +474,21 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
461
474
  # #awesome.rule {
462
475
  # awesomeness: very; }
463
476
  #
477
+ # You can also nest text beneath a comment to comment out a whole block.
478
+ # For example:
479
+ #
480
+ # // A very awesome rule
481
+ # #awesome.rule
482
+ # // Don't use these attributes
483
+ # color: green
484
+ # font-size: 10em
485
+ # color: red
486
+ #
487
+ # becomes
488
+ #
489
+ # #awesome.rule {
490
+ # color: red; }
491
+ #
464
492
  # === Loud Comments
465
493
  #
466
494
  # "Loud" comments are just as easy as silent ones.
@@ -480,6 +508,26 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
480
508
  # /* An equally awesome attribute. */
481
509
  # awesomeness: very; }
482
510
  #
511
+ # You can also nest content beneath loud comments. For example:
512
+ #
513
+ # #pbj
514
+ # /* This rule describes
515
+ # the styling of the element
516
+ # that represents
517
+ # a peanut butter and jelly sandwich.
518
+ # :background-image url(/images/pbj.png)
519
+ # :color red
520
+ #
521
+ # becomes
522
+ #
523
+ # #pbj {
524
+ # /* This rule describes
525
+ # * the styling of the element
526
+ # * that represents
527
+ # * a peanut butter and jelly sandwich. */
528
+ # background-image: url(/images/pbj.png);
529
+ # color: red; }
530
+ #
483
531
  # == Output Style
484
532
  #
485
533
  # Although the default CSS style that Sass outputs is very nice,
@@ -555,6 +603,16 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
555
603
  #
556
604
  # .huge { font-size: 10em; font-weight: bold; text-decoration: underline; }
557
605
  #
606
+ # === <tt>:compressed</tt>
607
+ #
608
+ # Compressed style takes up the minimum amount of space possible,
609
+ # having no whitespace except that necessary to separate selectors
610
+ # and a newline at the end of the file.
611
+ # It's not meant to be human-readable.
612
+ # For example:
613
+ #
614
+ # #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline}
615
+ #
558
616
  # == Sass Options
559
617
  #
560
618
  # Options can be set by setting the hash <tt>Sass::Plugin.options</tt>
@@ -565,6 +623,24 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
565
623
  # [<tt>:style</tt>] Sets the style of the CSS output.
566
624
  # See the section on Output Style, above.
567
625
  #
626
+ # [<tt>:attribute_syntax</tt>] Forces the document to use one syntax for attributes.
627
+ # If the correct syntax isn't used, an error is thrown.
628
+ # <tt>:normal</tt> forces the use of a colon
629
+ # before the attribute name.
630
+ # For example: <tt>:color #0f3</tt>
631
+ # or <tt>:width = !main_width</tt>.
632
+ # <tt>:alternate</tt> forces the use of a colon or equals sign
633
+ # after the attribute name.
634
+ # For example: <tt>color: #0f3</tt>
635
+ # or <tt>width = !main_width</tt>.
636
+ # By default, either syntax is valid.
637
+ #
638
+ # [<tt>:never_update</tt>] Whether the CSS files should never be updated,
639
+ # even if the template file changes.
640
+ # Setting this to true may give small performance gains.
641
+ # It always defaults to false.
642
+ # Only has meaning within Ruby on Rails or Merb.
643
+ #
568
644
  # [<tt>:always_update</tt>] Whether the CSS files should be updated every
569
645
  # time a controller is accessed,
570
646
  # as opposed to only when the template has been modified.
@@ -576,9 +652,8 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
576
652
  # as opposed to only when the Rails server starts.
577
653
  # If a Sass template has been updated,
578
654
  # it will be recompiled and will overwrite the corresponding CSS file.
579
- # Defaults to false if Rails is running in production mode,
580
- # true otherwise.
581
- # Only has meaning within Ruby on Rails.
655
+ # Defaults to false in production mode, true otherwise.
656
+ # Only has meaning within Ruby on Rails or Merb.
582
657
  #
583
658
  # [<tt>:full_exception</tt>] Whether an error in the Sass code
584
659
  # should cause Sass to provide a detailed description.
@@ -25,12 +25,13 @@ module Sass
25
25
  ?* => :times,
26
26
  ?/ => :div,
27
27
  ?% => :mod,
28
+ CONSTANT_CHAR => :const,
28
29
  STRING_CHAR => :str,
29
30
  ESCAPE_CHAR => :esc
30
31
  }
31
32
 
32
33
  # The regular expression used to parse constants
33
- MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }}]+)\s*=\s*(.+)/
34
+ MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }.join}]+)\s*=\s*(.+)/
34
35
 
35
36
  # First-order operations
36
37
  FIRST_ORDER = [:times, :div, :mod]
@@ -58,7 +59,7 @@ module Sass
58
59
  def tokenize(value)
59
60
  escaped = false
60
61
  is_string = false
61
- negative_okay = true
62
+ beginning_of_token = true
62
63
  str = ''
63
64
  to_return = []
64
65
 
@@ -100,22 +101,35 @@ module Sass
100
101
  symbol = SYMBOLS[byte]
101
102
 
102
103
  # Adjacent values without an operator should be concatenated
103
- if (symbol.nil? || symbol == :open) &&
104
+ if (symbol.nil? || symbol == :open || symbol == :const) &&
104
105
  last && (!last.is_a?(Symbol) || last == :close)
105
106
  to_return << :concat
106
107
  end
107
108
 
109
+ # String then open with no whitespace means funcall
110
+ if symbol == :open && !str.empty?
111
+ str = reset_str.call
112
+ to_return << :funcall
113
+ end
114
+
108
115
  # Time for a unary minus!
109
- if negative_okay && symbol == :minus
110
- negative_okay = true
116
+ if beginning_of_token && symbol == :minus
117
+ beginning_of_token = true
111
118
  to_return << :neg
112
119
  next
113
120
  end
114
121
 
122
+ # Is this a constant?
123
+ if beginning_of_token && symbol == :const
124
+ beginning_of_token = true
125
+ to_return << :const
126
+ next
127
+ end
128
+
115
129
  # Are we looking at an operator?
116
- if symbol && (str.empty? || symbol != :mod)
130
+ if symbol && (symbol != :mod || str.empty?)
117
131
  str = reset_str.call
118
- negative_okay = true
132
+ beginning_of_token = true
119
133
  to_return << symbol
120
134
  next
121
135
  end
@@ -123,7 +137,7 @@ module Sass
123
137
  end
124
138
 
125
139
  escaped = false
126
- negative_okay = false
140
+ beginning_of_token = false
127
141
  str << byte.chr
128
142
  end
129
143
 
@@ -138,7 +152,7 @@ module Sass
138
152
  parenthesize_helper(0, value, value.length)[0]
139
153
  end
140
154
 
141
- def parenthesize_helper(i, value, value_len)
155
+ def parenthesize_helper(i, value, value_len, return_after_expr = false)
142
156
  to_return = []
143
157
  beginning = i
144
158
  token = value[i]
@@ -151,17 +165,32 @@ module Sass
151
165
  to_return << sub
152
166
  elsif token == :neg
153
167
  if value[i + 1].nil?
154
- raise Sass::SyntaxError("Unterminated unary minus.")
168
+ # This is never actually reached, but we'll leave it in just in case.
169
+ raise Sass::SyntaxError.new("Unterminated unary minus.")
155
170
  elsif value[i + 1] == :open
156
171
  to_return.push(*value[beginning...i])
157
172
  sub, i = parenthesize_helper(i + 2, value, value_len)
158
173
  beginning = i
159
174
  to_return << [:neg, sub]
175
+ elsif value[i + 1].is_a?(::Symbol)
176
+ to_return.push(*value[beginning...i])
177
+ sub, i = parenthesize_helper(i + 1, value, value_len, true)
178
+ beginning = i
179
+ to_return << [:neg, sub]
160
180
  else
161
181
  to_return.push(*value[beginning...i])
162
182
  to_return << [:neg, value[i + 1]]
163
183
  beginning = i = i + 2
164
184
  end
185
+ return to_return[0], i if return_after_expr
186
+ elsif token == :const
187
+ raise Sass::SyntaxError.new("Unterminated constant.") if value[i + 1].nil?
188
+ raise Sass::SyntaxError.new("Invalid constant.") unless value[i + 1].is_a?(::String)
189
+
190
+ to_return.push(*value[beginning...i])
191
+ to_return << [:const, value[i + 1]]
192
+ beginning = i = i + 2
193
+ return to_return[0], i if return_after_expr
165
194
  else
166
195
  i += 1
167
196
  end
@@ -185,11 +214,13 @@ module Sass
185
214
  elsif value.is_a? Operation
186
215
  value
187
216
  else
188
- Literal.parse(insert_constant(value, constants))
217
+ Literal.parse(value)
189
218
  end
190
219
  elsif value.length == 2
191
220
  if value[0] == :neg
192
221
  Operation.new(Sass::Constant::Number.new('0'), operationalize(value[1], constants), :minus)
222
+ elsif value[0] == :const
223
+ Literal.parse(get_constant(value[1], constants))
193
224
  else
194
225
  raise SyntaxError.new("Constant arithmetic error")
195
226
  end
@@ -204,14 +235,9 @@ module Sass
204
235
  end
205
236
  end
206
237
 
207
- def insert_constant(value, constants)
208
- to_return = value
209
- if value[0] == CONSTANT_CHAR
210
- to_return = constants[value[1..-1]]
211
- unless to_return
212
- raise SyntaxError.new("Undefined constant: \"#{value}\"")
213
- end
214
- end
238
+ def get_constant(value, constants)
239
+ to_return = constants[value]
240
+ raise SyntaxError.new("Undefined constant: \"!#{value}\"") unless to_return
215
241
  to_return
216
242
  end
217
243
  end