haml 2.0.10 → 2.2.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 (107) hide show
  1. data/.yardopts +5 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +347 -0
  4. data/Rakefile +124 -19
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -0
  7. data/extra/haml-mode.el +397 -78
  8. data/extra/sass-mode.el +148 -36
  9. data/extra/update_watch.rb +13 -0
  10. data/lib/haml.rb +15 -993
  11. data/lib/haml/buffer.rb +131 -84
  12. data/lib/haml/engine.rb +129 -97
  13. data/lib/haml/error.rb +7 -7
  14. data/lib/haml/exec.rb +127 -42
  15. data/lib/haml/filters.rb +107 -42
  16. data/lib/haml/helpers.rb +210 -156
  17. data/lib/haml/helpers/action_view_extensions.rb +34 -39
  18. data/lib/haml/helpers/action_view_mods.rb +132 -139
  19. data/lib/haml/html.rb +77 -65
  20. data/lib/haml/precompiler.rb +404 -213
  21. data/lib/haml/shared.rb +78 -0
  22. data/lib/haml/template.rb +14 -14
  23. data/lib/haml/template/patch.rb +2 -2
  24. data/lib/haml/template/plugin.rb +2 -3
  25. data/lib/haml/util.rb +211 -6
  26. data/lib/haml/version.rb +30 -13
  27. data/lib/sass.rb +7 -856
  28. data/lib/sass/css.rb +169 -161
  29. data/lib/sass/engine.rb +344 -328
  30. data/lib/sass/environment.rb +79 -0
  31. data/lib/sass/error.rb +33 -11
  32. data/lib/sass/files.rb +139 -0
  33. data/lib/sass/plugin.rb +160 -117
  34. data/lib/sass/plugin/merb.rb +7 -6
  35. data/lib/sass/plugin/rails.rb +5 -6
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/script.rb +59 -0
  38. data/lib/sass/script/bool.rb +17 -0
  39. data/lib/sass/script/color.rb +183 -0
  40. data/lib/sass/script/funcall.rb +50 -0
  41. data/lib/sass/script/functions.rb +198 -0
  42. data/lib/sass/script/lexer.rb +178 -0
  43. data/lib/sass/script/literal.rb +177 -0
  44. data/lib/sass/script/node.rb +14 -0
  45. data/lib/sass/script/number.rb +381 -0
  46. data/lib/sass/script/operation.rb +45 -0
  47. data/lib/sass/script/parser.rb +172 -0
  48. data/lib/sass/script/string.rb +12 -0
  49. data/lib/sass/script/unary_operation.rb +34 -0
  50. data/lib/sass/script/variable.rb +31 -0
  51. data/lib/sass/tree/comment_node.rb +73 -10
  52. data/lib/sass/tree/debug_node.rb +30 -0
  53. data/lib/sass/tree/directive_node.rb +42 -17
  54. data/lib/sass/tree/file_node.rb +41 -0
  55. data/lib/sass/tree/for_node.rb +48 -0
  56. data/lib/sass/tree/if_node.rb +54 -0
  57. data/lib/sass/tree/mixin_def_node.rb +29 -0
  58. data/lib/sass/tree/mixin_node.rb +48 -0
  59. data/lib/sass/tree/node.rb +214 -11
  60. data/lib/sass/tree/prop_node.rb +109 -0
  61. data/lib/sass/tree/rule_node.rb +178 -51
  62. data/lib/sass/tree/variable_node.rb +34 -0
  63. data/lib/sass/tree/while_node.rb +31 -0
  64. data/test/haml/engine_test.rb +331 -36
  65. data/test/haml/helper_test.rb +12 -1
  66. data/test/haml/results/content_for_layout.xhtml +0 -3
  67. data/test/haml/results/filters.xhtml +2 -0
  68. data/test/haml/results/list.xhtml +1 -1
  69. data/test/haml/template_test.rb +7 -2
  70. data/test/haml/templates/content_for_layout.haml +0 -2
  71. data/test/haml/templates/list.haml +1 -1
  72. data/test/haml/util_test.rb +92 -0
  73. data/test/sass/css2sass_test.rb +69 -24
  74. data/test/sass/engine_test.rb +586 -64
  75. data/test/sass/functions_test.rb +125 -0
  76. data/test/sass/more_results/more1.css +9 -0
  77. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  78. data/test/sass/more_results/more_import.css +29 -0
  79. data/test/sass/more_templates/_more_partial.sass +2 -0
  80. data/test/sass/more_templates/more1.sass +23 -0
  81. data/test/sass/more_templates/more_import.sass +11 -0
  82. data/test/sass/plugin_test.rb +81 -28
  83. data/test/sass/results/line_numbers.css +49 -0
  84. data/test/sass/results/{constants.css → script.css} +4 -4
  85. data/test/sass/results/subdir/subdir.css +2 -0
  86. data/test/sass/results/units.css +11 -0
  87. data/test/sass/script_test.rb +258 -0
  88. data/test/sass/templates/import.sass +1 -1
  89. data/test/sass/templates/importee.sass +7 -2
  90. data/test/sass/templates/line_numbers.sass +13 -0
  91. data/test/sass/templates/{constants.sass → script.sass} +11 -10
  92. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  93. data/test/sass/templates/subdir/subdir.sass +2 -2
  94. data/test/sass/templates/units.sass +11 -0
  95. data/test/test_helper.rb +14 -0
  96. metadata +77 -19
  97. data/FAQ +0 -138
  98. data/README.rdoc +0 -319
  99. data/lib/sass/constant.rb +0 -216
  100. data/lib/sass/constant/color.rb +0 -101
  101. data/lib/sass/constant/literal.rb +0 -54
  102. data/lib/sass/constant/nil.rb +0 -9
  103. data/lib/sass/constant/number.rb +0 -87
  104. data/lib/sass/constant/operation.rb +0 -30
  105. data/lib/sass/constant/string.rb +0 -22
  106. data/lib/sass/tree/attr_node.rb +0 -57
  107. data/lib/sass/tree/value_node.rb +0 -20
@@ -0,0 +1,41 @@
1
+ module Sass
2
+ module Tree
3
+ # A static node that wraps the {Sass::Tree} for an `@import`ed file.
4
+ # It doesn't have a functional purpose other than to add the `@import`ed file
5
+ # to the backtrace if an error occurs.
6
+ class FileNode < Node
7
+ # @param filename [String] The name of the imported file
8
+ def initialize(filename)
9
+ @filename = filename
10
+ super()
11
+ end
12
+
13
+ # Computes the CSS for the imported file.
14
+ #
15
+ # @param args [Array] Ignored
16
+ def to_s(*args)
17
+ @to_s ||= (style == :compressed ? super().strip : super())
18
+ rescue Sass::SyntaxError => e
19
+ e.add_backtrace_entry(@filename)
20
+ raise e
21
+ end
22
+
23
+ def invisible?; to_s.empty?; end
24
+
25
+ protected
26
+
27
+ # Parses the imported file
28
+ # and runs the dynamic Sass for it.
29
+ #
30
+ # @param environment [Sass::Environment] The lexical environment containing
31
+ # variable and mixin values
32
+ def perform!(environment)
33
+ self.children = Sass::Files.tree_for(filename, @options).children
34
+ self.children = perform_children(environment)
35
+ rescue Sass::SyntaxError => e
36
+ e.add_backtrace_entry(@filename)
37
+ raise e
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A dynamic node representing a Sass `@for` loop.
5
+ #
6
+ # @see Sass::Tree
7
+ class ForNode < Node
8
+ # @param var [String] The name of the loop variable
9
+ # @param from [Script::Node] The parse tree for the initial expression
10
+ # @param to [Script::Node] The parse tree for the final expression
11
+ # @param exclusive [Boolean] Whether to include `to` in the loop
12
+ # or stop just before
13
+ def initialize(var, from, to, exclusive)
14
+ @var = var
15
+ @from = from
16
+ @to = to
17
+ @exclusive = exclusive
18
+ super()
19
+ end
20
+
21
+ protected
22
+
23
+ # Runs the child nodes once for each time through the loop,
24
+ # varying the variable each time.
25
+ #
26
+ # @param environment [Sass::Environment] The lexical environment containing
27
+ # variable and mixin values
28
+ # @return [Array<Tree::Node>] The resulting static nodes
29
+ # @see Sass::Tree
30
+ def _perform(environment)
31
+ from = @from.perform(environment)
32
+ to = @to.perform(environment)
33
+ from.assert_int!
34
+ to.assert_int!
35
+
36
+ to = to.coerce(from.numerator_units, from.denominator_units)
37
+ range = Range.new(from.to_i, to.to_i, @exclusive)
38
+
39
+ children = []
40
+ environment = Sass::Environment.new(environment)
41
+ range.each do |i|
42
+ environment.set_local_var(@var, Sass::Script::Number.new(i, from.numerator_units, from.denominator_units))
43
+ children += perform_children(environment)
44
+ end
45
+ children
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A dynamic node representing a Sass `@if` statement.
5
+ #
6
+ # {IfNode}s are a little odd, in that they also represent `@else` and `@else if`s.
7
+ # This is done as a linked list:
8
+ # each {IfNode} has a link (\{#else}) to the next {IfNode}.
9
+ #
10
+ # @see Sass::Tree
11
+ class IfNode < Node
12
+ # The next {IfNode} in the if-else list, or `nil`.
13
+ #
14
+ # @return [IfNode]
15
+ attr_accessor :else
16
+
17
+ # @param expr [Script::Expr] The conditional expression.
18
+ # If this is nil, this is an `@else` node, not an `@else if`
19
+ def initialize(expr)
20
+ @expr = expr
21
+ @last_else = self
22
+ super()
23
+ end
24
+
25
+ # Append an `@else` node to the end of the list.
26
+ #
27
+ # @param node [IfNode] The `@else` node to append
28
+ def add_else(node)
29
+ @last_else.else = node
30
+ @last_else = node
31
+ end
32
+
33
+ def options=(options)
34
+ super
35
+ self.else.options = options if self.else
36
+ end
37
+
38
+ protected
39
+
40
+ # Runs the child nodes if the conditional expression is true;
41
+ # otherwise, tries the \{#else} nodes.
42
+ #
43
+ # @param environment [Sass::Environment] The lexical environment containing
44
+ # variable and mixin values
45
+ # @return [Array<Tree::Node>] The resulting static nodes
46
+ # @see Sass::Tree
47
+ def _perform(environment)
48
+ environment = Sass::Environment.new(environment)
49
+ return perform_children(environment) if @expr.nil? || @expr.perform(environment).to_bool
50
+ return @else.perform(environment) if @else
51
+ []
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing a mixin definition.
4
+ #
5
+ # @see Sass::Tree
6
+ class MixinDefNode < Node
7
+ # @param name [String] The mixin name
8
+ # @param args [Array<(String, Script::Node)>] The arguments for the mixin.
9
+ # Each element is a tuple containing the name of the argument
10
+ # and the parse tree for the default value of the argument
11
+ def initialize(name, args)
12
+ @name = name
13
+ @args = args
14
+ super()
15
+ end
16
+
17
+ protected
18
+
19
+ # Loads the mixin into the environment.
20
+ #
21
+ # @param environment [Sass::Environment] The lexical environment containing
22
+ # variable and mixin values
23
+ def _perform(environment)
24
+ environment.set_mixin(@name, Sass::Mixin.new(@name, @args, environment, children))
25
+ []
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A dynamic node representing a mixin include.
5
+ #
6
+ # @see Sass::Tree
7
+ class MixinNode < Node
8
+ # @param name [String] The name of the mixin
9
+ # @param args [Array<Script::Node>] The arguments to the mixin
10
+ def initialize(name, args)
11
+ @name = name
12
+ @args = args
13
+ super()
14
+ end
15
+
16
+ protected
17
+
18
+ # Runs the mixin.
19
+ #
20
+ # @param environment [Sass::Environment] The lexical environment containing
21
+ # variable and mixin values
22
+ # @return [Array<Tree::Node>] The resulting static nodes
23
+ # @raise [Sass::SyntaxError] if there is no mixin with the given name
24
+ # @raise [Sass::SyntaxError] if an incorrect number of arguments was passed
25
+ # @see Sass::Tree
26
+ def _perform(environment)
27
+ raise Sass::SyntaxError.new("Undefined mixin '#{@name}'.", @line) unless mixin = environment.mixin(@name)
28
+
29
+ raise Sass::SyntaxError.new(<<END.gsub("\n", "")) if mixin.args.size < @args.size
30
+ Mixin #{@name} takes #{mixin.args.size} argument#{'s' if mixin.args.size != 1}
31
+ but #{@args.size} #{@args.size == 1 ? 'was' : 'were'} passed.
32
+ END
33
+
34
+ environment = mixin.args.zip(@args).
35
+ inject(Sass::Environment.new(mixin.environment)) do |env, ((name, default), value)|
36
+ env.set_local_var(name,
37
+ if value
38
+ value.perform(environment)
39
+ elsif default
40
+ default.perform(env)
41
+ end)
42
+ raise Sass::SyntaxError.new("Mixin #{@name} is missing parameter !#{name}.") unless env.var(name)
43
+ env
44
+ end
45
+ mixin.tree.map {|c| c.perform(environment)}.flatten
46
+ end
47
+ end
48
+ end
@@ -1,15 +1,69 @@
1
1
  module Sass
2
+ # A namespace for nodes in the Sass parse tree.
3
+ #
4
+ # The Sass parse tree has two states.
5
+ # When it's first parsed, it has nodes for mixin definitions
6
+ # and for loops and so forth,
7
+ # in addition to nodes for CSS rules and properties.
8
+ #
9
+ # However, {Tree::Node#perform} returns a different sort of tree.
10
+ # This tree maps more closely to the resulting CSS document
11
+ # than it does to the original Sass document.
12
+ # It still has nodes for CSS rules and properties,
13
+ # but it doesn't have any dynamic-generation-related nodes.
14
+ #
15
+ # Nodes that only appear in the pre-perform state are called **dynamic nodes**;
16
+ # those that appear in both states are called **static nodes**.
2
17
  module Tree
18
+ # This class doubles as the root node of the parse tree
19
+ # and the superclass of all other parse-tree nodes.
3
20
  class Node
21
+ # The child nodes of this node.
22
+ #
23
+ # @return [Array<Tree::Node>]
4
24
  attr_accessor :children
25
+
26
+ # The line of the document on which this node appeared.
27
+ #
28
+ # @return [Fixnum]
5
29
  attr_accessor :line
6
- attr_accessor :filename
7
30
 
8
- def initialize(style)
9
- @style = style
31
+ # The name of the document on which this node appeared.
32
+ #
33
+ # @return [String]
34
+ attr_writer :filename
35
+
36
+ # The options hash for the node.
37
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
38
+ #
39
+ # @return [Hash<Symbol, Object>]
40
+ attr_reader :options
41
+
42
+ def initialize
10
43
  @children = []
11
44
  end
12
45
 
46
+ # Sets the options hash for the node and all its children.
47
+ #
48
+ # @param options [Hash<Symbol, Object>] The options
49
+ # @see #options
50
+ def options=(options)
51
+ children.each {|c| c.options = options}
52
+ @options = options
53
+ end
54
+
55
+ # The name of the document on which this node appeared.
56
+ #
57
+ # @return [String]
58
+ def filename
59
+ @filename || @options[:filename]
60
+ end
61
+
62
+ # Appends a child to the node.
63
+ #
64
+ # @param child [Tree::Node] The child node
65
+ # @raise [Sass::SyntaxError] if `child` is invalid
66
+ # @see #invalid_child?
13
67
  def <<(child)
14
68
  if msg = invalid_child?(child)
15
69
  raise Sass::SyntaxError.new(msg, child.line)
@@ -17,27 +71,176 @@ module Sass
17
71
  @children << child
18
72
  end
19
73
 
74
+ # Return the last child node.
75
+ #
76
+ # We need this because {Tree::Node} duck types as an Array for {Sass::Engine}.
77
+ #
78
+ # @return [Tree::Node] The last child node
79
+ def last
80
+ children.last
81
+ end
82
+
83
+ # Compares this node and another object (only other {Tree::Node}s will be equal).
84
+ # This does a structural comparison;
85
+ # if the contents of the nodes and all the child nodes are equivalent,
86
+ # then the nodes are as well.
87
+ #
88
+ # Only static nodes need to override this.
89
+ #
90
+ # @param other [Object] The object to compare with
91
+ # @return [Boolean] Whether or not this node and the other object
92
+ # are the same
93
+ # @see Sass::Tree
20
94
  def ==(other)
21
95
  self.class == other.class && other.children == children
22
96
  end
23
97
 
98
+ # Runs the dynamic Sass code *and* computes the CSS for the tree.
99
+ #
100
+ # @see #perform
101
+ # @see #to_s
102
+ def render
103
+ perform(Environment.new).to_s
104
+ end
105
+
106
+ # True if \{#to\_s} will return `nil`;
107
+ # that is, if the node shouldn't be rendered.
108
+ # Should only be called in a static tree.
109
+ #
110
+ # @return [Boolean]
111
+ def invisible?; false; end
112
+
113
+ # Computes the CSS corresponding to this Sass tree.
114
+ #
115
+ # Only static-node subclasses need to implement \{#to\_s}.
116
+ #
117
+ # This may return `nil`, but it will only do so if \{#invisible?} is true.
118
+ #
119
+ # @return [String, nil] The resulting CSS
120
+ # @raise [Sass::SyntaxError] if some element of the tree is invalid
121
+ # @see Sass::Tree
24
122
  def to_s
25
123
  result = String.new
26
124
  children.each do |child|
27
- if child.is_a? AttrNode
28
- raise SyntaxError.new('Attributes aren\'t allowed at the root of a document.', child.line)
125
+ if child.is_a? PropNode
126
+ raise Sass::SyntaxError.new('Properties aren\'t allowed at the root of a document.', child.line)
29
127
  else
30
- result << "#{child.to_s(1)}" + (@style == :compressed ? '' : "\n")
128
+ next if child.invisible?
129
+ child_str = child.to_s(1)
130
+ result << child_str + (style == :compressed ? '' : "\n")
31
131
  end
32
132
  end
33
- @style == :compressed ? result+"\n" : result[0...-1]
133
+ result.rstrip!
134
+ return "" if result.empty?
135
+ return result + "\n"
136
+ rescue Sass::SyntaxError => e; e.add_metadata(filename, line)
34
137
  end
35
138
 
36
- private
139
+ # Runs the dynamic Sass code:
140
+ # mixins, variables, control directives, and so forth.
141
+ # This doesn't modify this node or any of its children.
142
+ #
143
+ # \{#perform} shouldn't be overridden directly;
144
+ # if you want to return a new node (or list of nodes),
145
+ # override \{#\_perform};
146
+ # if you want to destructively modify this node,
147
+ # override \{#perform!}.
148
+ #
149
+ # @param environment [Sass::Environment] The lexical environment containing
150
+ # variable and mixin values
151
+ # @return [Tree::Node] The resulting tree of static nodes
152
+ # @raise [Sass::SyntaxError] if some element of the tree is invalid
153
+ # @see Sass::Tree
154
+ def perform(environment)
155
+ environment.options = @options if self.class == Tree::Node
156
+ _perform(environment)
157
+ rescue Sass::SyntaxError => e; e.add_metadata(filename, line)
158
+ end
159
+
160
+ # The output style. See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
161
+ #
162
+ # @return [Symbol]
163
+ def style
164
+ @options[:style]
165
+ end
166
+
167
+ protected
168
+
169
+ # Runs any dynamic Sass code in this particular node.
170
+ # This doesn't modify this node or any of its children.
171
+ #
172
+ # @param environment [Sass::Environment] The lexical environment containing
173
+ # variable and mixin values
174
+ # @return [Tree::Node, Array<Tree::Node>] The resulting static nodes
175
+ # @see #perform
176
+ # @see Sass::Tree
177
+ def _perform(environment)
178
+ node = dup
179
+ node.perform!(environment)
180
+ node
181
+ end
182
+
183
+ # Destructively runs dynamic Sass code in this particular node.
184
+ # This *does* modify this node,
185
+ # but will be run non-destructively by \{#\_perform\}.
186
+ #
187
+ # @param environment [Sass::Environment] The lexical environment containing
188
+ # variable and mixin values
189
+ # @see #perform
190
+ def perform!(environment)
191
+ self.children = perform_children(Environment.new(environment))
192
+ end
193
+
194
+ # Non-destructively runs \{#perform} on all children of the current node.
195
+ #
196
+ # @param environment [Sass::Environment] The lexical environment containing
197
+ # variable and mixin values
198
+ # @return [Array<Tree::Node>] The resulting static nodes
199
+ def perform_children(environment)
200
+ children.map {|c| c.perform(environment)}.flatten
201
+ end
202
+
203
+ # Replaces SassScript in a chunk of text (via `#{}`)
204
+ # with the resulting value.
205
+ #
206
+ # @param text [String] The text to interpolate
207
+ # @param environment [Sass::Environment] The lexical environment containing
208
+ # variable and mixin values
209
+ # @return [String] The interpolated text
210
+ def interpolate(text, environment)
211
+ res = ''
212
+ rest = Haml::Shared.handle_interpolation text do |scan|
213
+ escapes = scan[2].size
214
+ res << scan.matched[0...-2 - escapes]
215
+ if escapes % 2 == 1
216
+ res << "\\" * (escapes - 1) << '#{'
217
+ else
218
+ res << "\\" * [0, escapes - 1].max
219
+ res << Script::Parser.new(scan, line, scan.pos - scan.matched_size, filename).
220
+ parse_interpolated.perform(environment).to_s
221
+ end
222
+ end
223
+ res + rest
224
+ end
225
+
226
+ # @see Haml::Shared.balance
227
+ # @raise [Sass::SyntaxError] if the brackets aren't balanced
228
+ def balance(*args)
229
+ res = Haml::Shared.balance(*args)
230
+ return res if res
231
+ raise Sass::SyntaxError.new("Unbalanced brackets.", line)
232
+ end
37
233
 
38
- # This method should be overridden by subclasses to return an error message
39
- # if the given child node is invalid,
40
- # and false or nil otherwise.
234
+ # Returns an error message if the given child node is invalid,
235
+ # and false otherwise.
236
+ #
237
+ # By default, all child nodes are valid.
238
+ # This is expected to be overriden by subclasses
239
+ # for which some children are invalid.
240
+ #
241
+ # @param child [Tree::Node] A potential child node
242
+ # @return [Boolean, String] Whether or not the child node is valid,
243
+ # as well as the error message to display if it is invalid
41
244
  def invalid_child?(child)
42
245
  false
43
246
  end