haml 2.2.24 → 3.0.0.beta.1

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 (168) hide show
  1. data/.yardopts +0 -1
  2. data/README.md +91 -151
  3. data/REMEMBER +11 -1
  4. data/Rakefile +73 -55
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/css2sass +7 -1
  8. data/bin/sass-convert +7 -0
  9. data/extra/haml-mode.el +2 -1
  10. data/lib/haml/buffer.rb +22 -4
  11. data/lib/haml/engine.rb +5 -1
  12. data/lib/haml/exec.rb +231 -46
  13. data/lib/haml/filters.rb +19 -8
  14. data/lib/haml/helpers.rb +47 -20
  15. data/lib/haml/helpers/action_view_extensions.rb +2 -4
  16. data/lib/haml/helpers/action_view_mods.rb +11 -8
  17. data/lib/haml/helpers/xss_mods.rb +13 -2
  18. data/lib/haml/html.rb +179 -48
  19. data/lib/haml/html/erb.rb +141 -0
  20. data/lib/haml/precompiler.rb +40 -15
  21. data/lib/haml/railtie.rb +1 -5
  22. data/lib/haml/root.rb +3 -0
  23. data/lib/haml/template.rb +4 -14
  24. data/lib/haml/util.rb +120 -30
  25. data/lib/haml/version.rb +25 -2
  26. data/lib/sass.rb +5 -1
  27. data/lib/sass/callbacks.rb +50 -0
  28. data/lib/sass/css.rb +40 -191
  29. data/lib/sass/engine.rb +170 -74
  30. data/lib/sass/environment.rb +8 -2
  31. data/lib/sass/error.rb +163 -25
  32. data/lib/sass/files.rb +31 -28
  33. data/lib/sass/plugin.rb +268 -87
  34. data/lib/sass/plugin/rails.rb +9 -4
  35. data/lib/sass/repl.rb +1 -1
  36. data/lib/sass/script.rb +31 -29
  37. data/lib/sass/script/bool.rb +1 -0
  38. data/lib/sass/script/color.rb +290 -23
  39. data/lib/sass/script/css_lexer.rb +22 -0
  40. data/lib/sass/script/css_parser.rb +28 -0
  41. data/lib/sass/script/funcall.rb +22 -3
  42. data/lib/sass/script/functions.rb +523 -33
  43. data/lib/sass/script/interpolation.rb +42 -0
  44. data/lib/sass/script/lexer.rb +169 -52
  45. data/lib/sass/script/literal.rb +58 -9
  46. data/lib/sass/script/node.rb +79 -1
  47. data/lib/sass/script/number.rb +20 -5
  48. data/lib/sass/script/operation.rb +49 -3
  49. data/lib/sass/script/parser.rb +162 -28
  50. data/lib/sass/script/string.rb +50 -2
  51. data/lib/sass/script/unary_operation.rb +25 -2
  52. data/lib/sass/script/variable.rb +21 -4
  53. data/lib/sass/scss.rb +14 -0
  54. data/lib/sass/scss/css_parser.rb +39 -0
  55. data/lib/sass/scss/parser.rb +683 -0
  56. data/lib/sass/scss/rx.rb +112 -0
  57. data/lib/sass/scss/script_lexer.rb +13 -0
  58. data/lib/sass/scss/script_parser.rb +25 -0
  59. data/lib/sass/tree/comment_node.rb +69 -27
  60. data/lib/sass/tree/debug_node.rb +7 -2
  61. data/lib/sass/tree/directive_node.rb +41 -35
  62. data/lib/sass/tree/for_node.rb +6 -0
  63. data/lib/sass/tree/if_node.rb +13 -1
  64. data/lib/sass/tree/import_node.rb +52 -27
  65. data/lib/sass/tree/mixin_def_node.rb +18 -0
  66. data/lib/sass/tree/mixin_node.rb +41 -6
  67. data/lib/sass/tree/node.rb +197 -70
  68. data/lib/sass/tree/prop_node.rb +152 -57
  69. data/lib/sass/tree/root_node.rb +118 -0
  70. data/lib/sass/tree/rule_node.rb +193 -96
  71. data/lib/sass/tree/variable_node.rb +9 -5
  72. data/lib/sass/tree/while_node.rb +4 -0
  73. data/test/benchmark.rb +5 -5
  74. data/test/haml/engine_test.rb +147 -10
  75. data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
  76. data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
  77. data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
  78. data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
  79. data/test/haml/helper_test.rb +91 -24
  80. data/test/haml/html2haml/erb_tests.rb +410 -0
  81. data/test/haml/html2haml_test.rb +210 -66
  82. data/test/haml/results/filters.xhtml +1 -1
  83. data/test/haml/results/just_stuff.xhtml +2 -0
  84. data/test/haml/spec_test.rb +44 -0
  85. data/test/haml/template_test.rb +22 -2
  86. data/test/haml/templates/helpers.haml +0 -13
  87. data/test/haml/templates/just_stuff.haml +2 -0
  88. data/test/haml/util_test.rb +48 -0
  89. data/test/sass/callbacks_test.rb +61 -0
  90. data/test/sass/conversion_test.rb +884 -0
  91. data/test/sass/css2sass_test.rb +99 -18
  92. data/test/sass/data/hsl-rgb.txt +319 -0
  93. data/test/sass/engine_test.rb +1049 -131
  94. data/test/sass/functions_test.rb +398 -47
  95. data/test/sass/more_results/more_import.css +1 -1
  96. data/test/sass/more_templates/more_import.sass +3 -3
  97. data/test/sass/plugin_test.rb +184 -10
  98. data/test/sass/results/compact.css +1 -1
  99. data/test/sass/results/complex.css +5 -5
  100. data/test/sass/results/compressed.css +1 -1
  101. data/test/sass/results/expanded.css +1 -1
  102. data/test/sass/results/import.css +3 -1
  103. data/test/sass/results/mixins.css +12 -12
  104. data/test/sass/results/nested.css +1 -1
  105. data/test/sass/results/options.css +1 -0
  106. data/test/sass/results/parent_ref.css +4 -4
  107. data/test/sass/results/script.css +3 -3
  108. data/test/sass/results/scss_import.css +15 -0
  109. data/test/sass/results/scss_importee.css +2 -0
  110. data/test/sass/script_conversion_test.rb +153 -0
  111. data/test/sass/script_test.rb +137 -70
  112. data/test/sass/scss/css_test.rb +811 -0
  113. data/test/sass/scss/rx_test.rb +156 -0
  114. data/test/sass/scss/scss_test.rb +871 -0
  115. data/test/sass/scss/test_helper.rb +37 -0
  116. data/test/sass/templates/alt.sass +2 -2
  117. data/test/sass/templates/bork1.sass +2 -0
  118. data/test/sass/templates/bork3.sass +2 -0
  119. data/test/sass/templates/bork4.sass +2 -0
  120. data/test/sass/templates/import.sass +4 -4
  121. data/test/sass/templates/importee.sass +3 -3
  122. data/test/sass/templates/line_numbers.sass +1 -1
  123. data/test/sass/templates/mixin_bork.sass +5 -0
  124. data/test/sass/templates/mixins.sass +2 -2
  125. data/test/sass/templates/nested_bork1.sass +2 -0
  126. data/test/sass/templates/nested_bork2.sass +2 -0
  127. data/test/sass/templates/nested_bork3.sass +2 -0
  128. data/test/sass/templates/nested_bork4.sass +2 -0
  129. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  130. data/test/sass/templates/options.sass +2 -0
  131. data/test/sass/templates/parent_ref.sass +2 -2
  132. data/test/sass/templates/script.sass +69 -69
  133. data/test/sass/templates/scss_import.scss +10 -0
  134. data/test/sass/templates/scss_importee.scss +1 -0
  135. data/test/sass/templates/units.sass +10 -10
  136. data/test/test_helper.rb +20 -8
  137. data/vendor/fssm/LICENSE +20 -0
  138. data/vendor/fssm/README.markdown +55 -0
  139. data/vendor/fssm/Rakefile +59 -0
  140. data/vendor/fssm/VERSION.yml +5 -0
  141. data/vendor/fssm/example.rb +9 -0
  142. data/vendor/fssm/fssm.gemspec +77 -0
  143. data/vendor/fssm/lib/fssm.rb +33 -0
  144. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  145. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  146. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  147. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  148. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  149. data/vendor/fssm/lib/fssm/path.rb +91 -0
  150. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  151. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  152. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  153. data/vendor/fssm/lib/fssm/support.rb +63 -0
  154. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  155. data/vendor/fssm/profile/prof-cache.rb +40 -0
  156. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  157. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  158. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  159. data/vendor/fssm/profile/prof.html +2379 -0
  160. data/vendor/fssm/spec/path_spec.rb +75 -0
  161. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  162. data/vendor/fssm/spec/root/file.css +0 -0
  163. data/vendor/fssm/spec/root/file.rb +0 -0
  164. data/vendor/fssm/spec/root/file.yml +0 -0
  165. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  166. data/vendor/fssm/spec/spec_helper.rb +14 -0
  167. metadata +94 -14
  168. data/test/sass/templates/bork.sass +0 -2
@@ -3,24 +3,54 @@ module Sass::Tree
3
3
  #
4
4
  # @see Sass::Tree
5
5
  class PropNode < Node
6
- # The name of the property.
6
+ # The name of the property,
7
+ # interspersed with {Sass::Script::Node}s
8
+ # representing `#{}`-interpolation.
9
+ # Any adjacent strings will be merged together.
7
10
  #
8
- # @return [String]
11
+ # @return [Array<String, Sass::Script::Node>]
9
12
  attr_accessor :name
10
13
 
11
- # The value of the property,
12
- # either a plain string or a SassScript parse tree.
14
+ # The name of the property
15
+ # after any interpolated SassScript has been resolved.
16
+ # Only set once \{Tree::Node#perform} has been called.
17
+ #
18
+ # @return [String]
19
+ attr_accessor :resolved_name
20
+
21
+ # The value of the property.
13
22
  #
14
- # @return [String, Script::Node]
23
+ # @return [Sass::Script::Node]
15
24
  attr_accessor :value
16
25
 
17
- # @param name [String] See \{#name}
18
- # @param value [String] See \{#value}
26
+ # The value of the property
27
+ # after any interpolated SassScript has been resolved.
28
+ # Only set once \{Tree::Node#perform} has been called.
29
+ #
30
+ # @return [String]
31
+ attr_accessor :resolved_value
32
+
33
+ # How deep this property is indented
34
+ # relative to a normal property.
35
+ # This is only greater than 0 in the case that:
36
+ #
37
+ # * This node is in a CSS tree
38
+ # * The style is :nested
39
+ # * This is a child property of another property
40
+ # * The parent property has a value, and thus will be rendered
41
+ #
42
+ # @return [Fixnum]
43
+ attr_accessor :tabs
44
+
45
+ # @param name [Array<String, Sass::Script::Node>] See \{#name}
46
+ # @param value [Sass::Script::Node] See \{#value}
19
47
  # @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
20
48
  # `:old` if it uses `:a b`-style syntax
21
49
  def initialize(name, value, prop_syntax)
22
- @name = name
50
+ @name = Haml::Util.strip_string_array(
51
+ Haml::Util.merge_adjacent_strings(name))
23
52
  @value = value
53
+ @tabs = 0
24
54
  @prop_syntax = prop_syntax
25
55
  super()
26
56
  end
@@ -34,69 +64,79 @@ module Sass::Tree
34
64
  self.class == other.class && name == other.name && value == other.value && super
35
65
  end
36
66
 
37
- # Computes the CSS for the property.
38
- #
39
- # @param tabs [Fixnum] The level of indentation for the CSS
40
- # @param parent_name [String] The name of the parent property (e.g. `text`) or nil
41
- # @return [String] The resulting CSS
42
- # @raise [Sass::SyntaxError] if the property uses invalid syntax
43
- def to_s(tabs, parent_name = nil)
44
- if @options[:property_syntax] == :old && @prop_syntax == :new
45
- raise Sass::SyntaxError.new("Illegal property syntax: can't use new syntax when :property_syntax => :old is set.", @line)
46
- elsif @options[:property_syntax] == :new && @prop_syntax == :old
47
- raise Sass::SyntaxError.new("Illegal property syntax: can't use old syntax when :property_syntax => :new is set.", @line)
48
- end
49
-
50
- if value[-1] == ?;
51
- raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no \";\" required at end-of-line).", @line)
52
- end
53
- real_name = name
54
- real_name = "#{parent_name}-#{real_name}" if parent_name
55
-
56
- if value.empty? && children.empty?
57
- message = "Invalid property: #{declaration.dump} (no value)." +
58
- pseudo_class_selector_message
59
- raise Sass::SyntaxError.new(message, @line)
60
- end
61
-
62
- join_string = case style
63
- when :compact; ' '
64
- when :compressed; ''
65
- else "\n"
66
- end
67
- spaces = ' ' * (tabs - 1)
68
- to_return = ''
69
- if !value.empty?
70
- to_return << "#{spaces}#{real_name}:#{style == :compressed ? '' : ' '}#{value};#{join_string}"
71
- end
72
-
73
- children.each do |kid|
74
- next if kid.invisible?
75
- to_return << kid.to_s(tabs, real_name) << join_string
76
- end
77
-
78
- (style == :compressed && parent_name) ? to_return : to_return[0...-1]
79
- end
80
-
81
67
  # Returns a appropriate message indicating how to escape pseudo-class selectors.
82
68
  # This only applies for old-style properties with no value,
83
69
  # so returns the empty string if this is new-style.
84
70
  #
71
+ # This should only be called once \{#perform} has been called.
72
+ #
85
73
  # @return [String] The message
86
74
  def pseudo_class_selector_message
87
- return "" if @prop_syntax == :new || !value.empty?
75
+ return "" if @prop_syntax == :new || !resolved_value.empty?
88
76
  "\nIf #{declaration.dump} should be a selector, use \"\\#{declaration}\" instead."
89
77
  end
90
78
 
91
79
  protected
92
80
 
93
- # Runs any SassScript that may be embedded in the property.
81
+ def to_src(tabs, opts, fmt)
82
+ name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass}}"}.join
83
+ old = opts[:old] && fmt == :sass
84
+ initial = old ? ':' : ''
85
+ mid = old ? '' : ':'
86
+ res = "#{' ' * tabs}#{initial}#{name}#{mid} #{self.class.val_to_sass(value)}"
87
+ return res + "#{semi fmt}\n" if children.empty?
88
+ res.rstrip + children_to_src(tabs, opts, fmt)
89
+ end
90
+
91
+ # Computes the CSS for the property.
92
+ #
93
+ # @param tabs [Fixnum] The level of indentation for the CSS
94
+ # @return [String] The resulting CSS
95
+ def _to_s(tabs)
96
+ to_return = ' ' * (tabs - 1 + self.tabs) + resolved_name + ":" +
97
+ (style == :compressed ? '' : ' ') + resolved_value + (style == :compressed ? "" : ";")
98
+ end
99
+
100
+ # Converts nested properties into flat properties.
101
+ #
102
+ # @param parent [PropNode, nil] The parent node of this node,
103
+ # or nil if the parent isn't a {PropNode}
104
+ # @raise [Sass::SyntaxError] if the property uses invalid syntax
105
+ def _cssize(parent)
106
+ node = super
107
+ result = node.children.dup
108
+ if !node.resolved_value.empty? || node.children.empty?
109
+ node.send(:check!)
110
+ result.unshift(node)
111
+ end
112
+ result
113
+ end
114
+
115
+ # Updates the name and indentation of this node based on the parent name
116
+ # and nesting level.
117
+ #
118
+ # @param parent [PropNode, nil] The parent node of this node,
119
+ # or nil if the parent isn't a {PropNode}
120
+ def cssize!(parent)
121
+ self.resolved_name = "#{parent.resolved_name}-#{resolved_name}" if parent
122
+ self.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if parent && style == :nested
123
+ super
124
+ end
125
+
126
+ # Runs any SassScript that may be embedded in the property,
127
+ # and invludes the parent property, if any.
94
128
  #
95
129
  # @param environment [Sass::Environment] The lexical environment containing
96
130
  # variable and mixin values
97
131
  def perform!(environment)
98
- @name = interpolate(@name, environment)
99
- @value = @value.is_a?(String) ? interpolate(@value, environment) : @value.perform(environment).to_s
132
+ @resolved_name = run_interp(@name, environment)
133
+ val = @value.perform(environment)
134
+ @resolved_value =
135
+ if @value.context == :equals && val.is_a?(Sass::Script::String)
136
+ val.value
137
+ else
138
+ val.to_s
139
+ end
100
140
  super
101
141
  end
102
142
 
@@ -114,8 +154,63 @@ module Sass::Tree
114
154
 
115
155
  private
116
156
 
157
+ def check!
158
+ if @options[:property_syntax] == :old && @prop_syntax == :new
159
+ raise Sass::SyntaxError.new("Illegal property syntax: can't use new syntax when :property_syntax => :old is set.")
160
+ elsif @options[:property_syntax] == :new && @prop_syntax == :old
161
+ raise Sass::SyntaxError.new("Illegal property syntax: can't use old syntax when :property_syntax => :new is set.")
162
+ elsif resolved_value.empty?
163
+ raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value)." +
164
+ pseudo_class_selector_message)
165
+ end
166
+ end
167
+
117
168
  def declaration
118
- (@prop_syntax == :new ? "#{name}: #{value}" : ":#{name} #{value}").strip
169
+ if @prop_syntax == :new
170
+ "#{resolved_name}: #{resolved_value}"
171
+ else
172
+ ":#{resolved_name} #{resolved_value}"
173
+ end.strip
174
+ end
175
+
176
+ class << self
177
+ # @private
178
+ def val_to_sass(value)
179
+ return value.to_sass unless value.context == :equals
180
+ val_to_sass_comma(value).to_sass
181
+ end
182
+
183
+ private
184
+
185
+ def val_to_sass_comma(node)
186
+ return node unless node.is_a?(Sass::Script::Operation)
187
+ return val_to_sass_concat(node) unless node.operator == :comma
188
+
189
+ Sass::Script::Operation.new(
190
+ val_to_sass_concat(node.operand1),
191
+ val_to_sass_comma(node.operand2),
192
+ node.operator)
193
+ end
194
+
195
+ def val_to_sass_concat(node)
196
+ return node unless node.is_a?(Sass::Script::Operation)
197
+ return val_to_sass_div(node) unless node.operator == :concat
198
+
199
+ Sass::Script::Operation.new(
200
+ val_to_sass_div(node.operand1),
201
+ val_to_sass_concat(node.operand2),
202
+ node.operator)
203
+ end
204
+
205
+ def val_to_sass_div(node)
206
+ unless node.is_a?(Sass::Script::Operation) && node.operator == :div &&
207
+ node.operand1.is_a?(Sass::Script::Number) &&
208
+ node.operand2.is_a?(Sass::Script::Number)
209
+ return node
210
+ end
211
+
212
+ Sass::Script::String.new("(#{node.to_sass})")
213
+ end
119
214
  end
120
215
  end
121
216
  end
@@ -0,0 +1,118 @@
1
+ module Sass
2
+ module Tree
3
+ # A static node that is the root node of the Sass document.
4
+ class RootNode < Node
5
+ # The Sass template from which this node was created
6
+ #
7
+ # @param template [String]
8
+ attr_reader :template
9
+
10
+ # @param template [String] The Sass template from which this node was created
11
+ def initialize(template)
12
+ super()
13
+ @template = template
14
+ end
15
+
16
+ # @see Node#to_s
17
+ def to_s(*args)
18
+ super
19
+ rescue Sass::SyntaxError => e
20
+ e.sass_template = @template
21
+ raise e
22
+ end
23
+
24
+ # @see Node#perform
25
+ def perform(environment)
26
+ environment.options = @options if environment.options.nil? || environment.options.empty?
27
+ super
28
+ rescue Sass::SyntaxError => e
29
+ e.sass_template = @template
30
+ raise e
31
+ end
32
+
33
+ # @see Node#cssize
34
+ def cssize(*args)
35
+ super
36
+ rescue Sass::SyntaxError => e
37
+ e.sass_template = @template
38
+ raise e
39
+ end
40
+
41
+ # @see \{Node#perform!}
42
+ def perform!(environment)
43
+ environment.options = @options if environment.options.nil? || environment.options.empty?
44
+ super
45
+ end
46
+
47
+ # Converts a node to Sass code that will generate it.
48
+ #
49
+ # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
50
+ # @return [String] The Sass code corresponding to the node
51
+ def to_sass(opts = {})
52
+ to_src(opts, :sass)
53
+ end
54
+
55
+ # Converts a node to SCSS code that will generate it.
56
+ #
57
+ # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
58
+ # @return [String] The SCSS code corresponding to the node
59
+ def to_scss(opts = {})
60
+ to_src(opts, :scss)
61
+ end
62
+
63
+ protected
64
+
65
+ def to_src(opts, fmt)
66
+ Haml::Util.enum_cons(children + [nil], 2).map do |child, nxt|
67
+ child.send("to_#{fmt}", 0, opts) +
68
+ if nxt &&
69
+ (child.is_a?(CommentNode) && child.line + child.value.count("\n") + 1 == nxt.line) ||
70
+ (child.is_a?(ImportNode) && nxt.is_a?(ImportNode) && child.line + 1 == nxt.line)
71
+ ""
72
+ else
73
+ "\n"
74
+ end
75
+ end.join.rstrip + "\n"
76
+ end
77
+
78
+ # Destructively converts this static Sass node into a static CSS node,
79
+ # and checks that there are no properties at root level.
80
+ #
81
+ # @param parent [Node, nil] The parent node of this node.
82
+ # This should only be non-nil if the parent is the same class as this node
83
+ # @see Node#cssize!
84
+ def cssize!(parent)
85
+ super
86
+ return unless child = children.find {|c| c.is_a?(PropNode)}
87
+ message = "Properties aren't allowed at the root of a document." +
88
+ child.pseudo_class_selector_message
89
+ raise Sass::SyntaxError.new(message, :line => child.line)
90
+ end
91
+
92
+ # Computes the CSS corresponding to this Sass tree.
93
+ #
94
+ # @param args [Array] ignored
95
+ # @return [String] The resulting CSS
96
+ # @see Sass::Tree
97
+ def _to_s(*args)
98
+ result = String.new
99
+ children.each do |child|
100
+ next if child.invisible?
101
+ child_str = child.to_s(1)
102
+ result << child_str + (style == :compressed ? '' : "\n")
103
+ end
104
+ result.rstrip!
105
+ return "" if result.empty?
106
+ return result + "\n"
107
+ end
108
+
109
+ # Returns false, because all nodes are allowed at the root of the document
110
+ # (properties are detected elsewhere post-mixin-resolution).
111
+ #
112
+ # @see Node#invalid_child?
113
+ def invalid_child?(child)
114
+ false
115
+ end
116
+ end
117
+ end
118
+ end
@@ -1,4 +1,5 @@
1
1
  require 'pathname'
2
+ require 'uri'
2
3
 
3
4
  module Sass::Tree
4
5
  # A static node reprenting a CSS rule.
@@ -9,30 +10,23 @@ module Sass::Tree
9
10
  # @private
10
11
  PARENT = '&'
11
12
 
12
- # The CSS selectors for this rule.
13
- # Each string is a selector line, and the lines are meant to be separated by commas.
14
- # For example,
15
- #
16
- # foo, bar, baz,
17
- # bip, bop, bup
18
- #
19
- # would be
20
- #
21
- # ["foo, bar, baz",
22
- # "bip, bop, bup"]
13
+ # The CSS selector for this rule,
14
+ # interspersed with {Sass::Script::Node}s
15
+ # representing `#{}`-interpolation.
16
+ # Any adjacent strings will be merged together.
23
17
  #
24
- # @return [Array<String>]
25
- attr_accessor :rules
18
+ # @return [Array<String, Sass::Script::Node>]
19
+ attr_accessor :rule
26
20
 
27
21
  # The CSS selectors for this rule,
28
22
  # parsed for commas and parent-references.
29
23
  # It's only set once {Tree::Node#perform} has been called.
30
24
  #
31
- # It's an array of arrays of arrays.
32
- # The first level of arrays represents distinct lines in the Sass file;
33
- # the second level represents comma-separated selectors;
34
- # the third represents structure within those selectors,
25
+ # It's an array of arrays.
26
+ # The first level of arrays comma-separated selectors;
27
+ # the second represents structure within those selectors,
35
28
  # currently only parent-refs (represented by `:parent`).
29
+ # Newlines are represented as literal `\n` characters in the strings.
36
30
  # For example,
37
31
  #
38
32
  # &.foo, bar, baz,
@@ -40,15 +34,54 @@ module Sass::Tree
40
34
  #
41
35
  # would be
42
36
  #
43
- # [[[:parent, "foo"], ["bar"], ["baz"]],
44
- # [["bip"], [:parent, "bop"], ["bup"]]]
37
+ # [[:parent, ".foo"], ["bar"], ["baz"],
38
+ # ["\nbip"], [:parent, ".bop"], ["bup"]]
45
39
  #
46
- # @return [Array<Array<Array<String|Symbol>>>]
40
+ # @return [Array<Array<String, Symbol>>]
47
41
  attr_accessor :parsed_rules
48
42
 
49
- # @param rule [String] The first CSS rule. See \{#rules}
43
+ # The CSS selectors for this rule,
44
+ # with all nesting and parent references resolved.
45
+ # It's only set once {Tree::Node#cssize} has been called.
46
+ #
47
+ # Each element is a distinct selector, separated by commas.
48
+ # Newlines are represented as literal `\n` characters in the strings.
49
+ # For example,
50
+ #
51
+ # foo bar, baz,
52
+ # bang, bip bop, blip
53
+ #
54
+ # would be
55
+ #
56
+ # ["foo bar", "baz", "\nbang", "bip bop", "blip"]
57
+ #
58
+ # @return [Array<String>]
59
+ attr_accessor :resolved_rules
60
+
61
+ # How deep this rule is indented
62
+ # relative to a base-level rule.
63
+ # This is only greater than 0 in the case that:
64
+ #
65
+ # * This node is in a CSS tree
66
+ # * The style is :nested
67
+ # * This is a child rule of another rule
68
+ # * The parent rule has properties, and thus will be rendered
69
+ #
70
+ # @return [Fixnum]
71
+ attr_accessor :tabs
72
+
73
+ # Whether or not this rule is the last rule in a nested group.
74
+ # This is only set in a CSS tree.
75
+ #
76
+ # @return [Boolean]
77
+ attr_accessor :group_end
78
+
79
+ # @param rule [Array<String, Sass::Script::Node>]
80
+ # The CSS rule. See \{#rule}
50
81
  def initialize(rule)
51
- @rules = [rule]
82
+ @rule = Haml::Util.strip_string_array(
83
+ Haml::Util.merge_adjacent_strings(rule))
84
+ @tabs = 0
52
85
  super()
53
86
  end
54
87
 
@@ -58,63 +91,86 @@ module Sass::Tree
58
91
  # @return [Boolean] Whether or not this node and the other object
59
92
  # are the same
60
93
  def ==(other)
61
- self.class == other.class && rules == other.rules && super
94
+ self.class == other.class && rule == other.rule && super
62
95
  end
63
96
 
64
97
  # Adds another {RuleNode}'s rules to this one's.
65
98
  #
66
99
  # @param node [RuleNode] The other node
67
100
  def add_rules(node)
68
- @rules += node.rules
101
+ @rule = Haml::Util.strip_string_array(
102
+ Haml::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule))
69
103
  end
70
104
 
71
105
  # @return [Boolean] Whether or not this rule is continued on the next line
72
106
  def continued?
73
- @rules.last[-1] == ?,
107
+ last = @rule.last
108
+ last.is_a?(String) && last[-1] == ?,
74
109
  end
75
110
 
111
+ # @see Node#to_sass
112
+ def to_sass(tabs, opts = {})
113
+ name = rule.map do |r|
114
+ if r.is_a?(String)
115
+ r.gsub(/(,[ \t]*)?\n\s*/) {$1 ? $1 + "\n" : " "}
116
+ else
117
+ "\#{#{r.to_sass}}"
118
+ end
119
+ end.join
120
+ name = "\\" + name if name[0] == ?:
121
+ name.gsub(/^/, ' ' * tabs) + children_to_src(tabs, opts, :sass)
122
+ end
123
+
124
+ def to_scss(tabs, opts = {})
125
+ name = rule.map {|r| r.is_a?(String) ? r : "\#{#{r.to_sass}}"}.
126
+ join.gsub(/^[ \t]*/, ' ' * tabs)
127
+
128
+ res = name + children_to_src(tabs, opts, :scss)
129
+
130
+ if children.last.is_a?(CommentNode) && children.last.silent
131
+ res.slice!(-3..-1)
132
+ res << "\n" << (' ' * tabs) << "}\n"
133
+ end
134
+
135
+ res
136
+ end
137
+
138
+ protected
139
+
76
140
  # Computes the CSS for the rule.
77
141
  #
78
142
  # @param tabs [Fixnum] The level of indentation for the CSS
79
- # @param super_rules [Array<Array<String>>] The rules for the parent node
80
- # (see \{#rules}), or `nil` if there are no parents
81
143
  # @return [String] The resulting CSS
82
- # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
83
- def to_s(tabs, super_rules = nil)
84
- resolved_rules = resolve_parent_refs(super_rules)
85
-
86
- properties = []
87
- sub_rules = []
144
+ def _to_s(tabs)
145
+ tabs = tabs + self.tabs
88
146
 
89
147
  rule_separator = style == :compressed ? ',' : ', '
90
- line_separator = [:nested, :expanded].include?(style) ? ",\n" : rule_separator
148
+ line_separator =
149
+ case style
150
+ when :nested, :expanded; "\n"
151
+ when :compressed; ""
152
+ else; " "
153
+ end
91
154
  rule_indent = ' ' * (tabs - 1)
92
155
  per_rule_indent, total_indent = [:nested, :expanded].include?(style) ? [rule_indent, ''] : ['', rule_indent]
93
156
 
94
- total_rule = total_indent + resolved_rules.map do |line|
95
- per_rule_indent + line.join(rule_separator)
157
+ total_rule = total_indent + resolved_rules.join(rule_separator).split("\n").map do |line|
158
+ per_rule_indent + line.strip
96
159
  end.join(line_separator)
97
160
 
98
- children.each do |child|
99
- next if child.invisible?
100
- if child.is_a? RuleNode
101
- sub_rules << child
102
- else
103
- properties << child
104
- end
105
- end
106
-
107
161
  to_return = ''
108
- if !properties.empty?
109
- old_spaces = ' ' * (tabs - 1)
110
- spaces = ' ' * tabs
111
- if @options[:line_comments] && style != :compressed
162
+ old_spaces = ' ' * (tabs - 1)
163
+ spaces = ' ' * tabs
164
+ if style != :compressed
165
+ if @options[:debug_info]
166
+ to_return << debug_info_rule.to_s(tabs) << "\n"
167
+ elsif @options[:line_comments]
112
168
  to_return << "#{old_spaces}/* line #{line}"
113
169
 
114
170
  if filename
115
171
  relative_filename = if @options[:css_filename]
116
172
  begin
117
- Pathname.new(filename).relative_path_from(
173
+ Pathname.new(filename).relative_path_from(
118
174
  Pathname.new(File.dirname(@options[:css_filename]))).to_s
119
175
  rescue ArgumentError
120
176
  nil
@@ -126,70 +182,101 @@ module Sass::Tree
126
182
 
127
183
  to_return << " */\n"
128
184
  end
129
-
130
- if style == :compact
131
- properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(' ')
132
- to_return << "#{total_rule} { #{properties} }\n"
133
- elsif style == :compressed
134
- properties = properties.map { |a| a.to_s(1) }.select{|a| a && a.length > 0}.join(';')
135
- to_return << "#{total_rule}{#{properties}}"
136
- else
137
- properties = properties.map { |a| a.to_s(tabs + 1) }.select{|a| a && a.length > 0}.join("\n")
138
- end_props = (style == :expanded ? "\n" + old_spaces : ' ')
139
- to_return << "#{total_rule} {\n#{properties}#{end_props}}\n"
140
- end
141
185
  end
142
186
 
143
- tabs += 1 unless properties.empty? || style != :nested
144
- sub_rules.each do |sub|
145
- to_return << sub.to_s(tabs, resolved_rules)
187
+ if style == :compact
188
+ properties = children.map { |a| a.to_s(1) }.join(' ')
189
+ to_return << "#{total_rule} { #{properties} }#{"\n" if group_end}"
190
+ elsif style == :compressed
191
+ properties = children.map { |a| a.to_s(1) }.join(';')
192
+ to_return << "#{total_rule}{#{properties}}"
193
+ else
194
+ properties = children.map { |a| a.to_s(tabs + 1) }.join("\n")
195
+ end_props = (style == :expanded ? "\n" + old_spaces : ' ')
196
+ to_return << "#{total_rule} {\n#{properties}#{end_props}}#{"\n" if group_end}"
146
197
  end
147
198
 
148
199
  to_return
149
200
  end
150
201
 
151
- protected
152
-
153
202
  # Runs any SassScript that may be embedded in the rule,
154
203
  # and parses the selectors for commas.
155
204
  #
156
205
  # @param environment [Sass::Environment] The lexical environment containing
157
206
  # variable and mixin values
158
207
  def perform!(environment)
159
- @parsed_rules = @rules.map {|r| parse_selector(interpolate(r, environment))}
208
+ @parsed_rules = parse_selector(run_interp(@rule, environment))
209
+ super
210
+ end
211
+
212
+ # Converts nested rules into a flat list of rules.
213
+ #
214
+ # @param parent [RuleNode, nil] The parent node of this node,
215
+ # or nil if the parent isn't a {RuleNode}
216
+ def _cssize(parent)
217
+ node = super
218
+ rules = node.children.select {|c| c.is_a?(RuleNode)}
219
+ props = node.children.reject {|c| c.is_a?(RuleNode) || c.invisible?}
220
+
221
+ unless props.empty?
222
+ node.children = props
223
+ rules.each {|r| r.tabs += 1} if style == :nested
224
+ rules.unshift(node)
225
+ end
226
+
227
+ rules.last.group_end = true unless parent || rules.empty?
228
+
229
+ rules
230
+ end
231
+
232
+ # Resolves parent references and nested selectors,
233
+ # and updates the indentation based on the parent's indentation.
234
+ #
235
+ # @param parent [RuleNode, nil] The parent node of this node,
236
+ # or nil if the parent isn't a {RuleNode}
237
+ # @raise [Sass::SyntaxError] if the rule has no parents but uses `&`
238
+ def cssize!(parent)
239
+ self.resolved_rules = resolve_parent_refs(parent && parent.resolved_rules)
160
240
  super
161
241
  end
162
242
 
243
+ # A hash that will be associated with this rule in the CSS document
244
+ # if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled.
245
+ # This data is used by e.g. [the FireSass Firebug extension](https://addons.mozilla.org/en-US/firefox/addon/103988).
246
+ #
247
+ # @return [{#to_s => #to_s}]
248
+ def debug_info
249
+ {:filename => filename && ("file://" + URI.escape(File.expand_path(filename))),
250
+ :line => self.line}
251
+ end
252
+
163
253
  private
164
254
 
165
255
  def resolve_parent_refs(super_rules)
166
256
  if super_rules.nil?
167
- return @parsed_rules.map do |line|
168
- line.map do |rule|
169
- if rule.include?(:parent)
170
- raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.", self.line)
171
- end
257
+ return @parsed_rules.map do |rule|
258
+ if rule.include?(:parent)
259
+ raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'.")
260
+ end
172
261
 
173
- rule.join
174
- end.compact
262
+ rule.join
175
263
  end
176
264
  end
177
265
 
178
266
  new_rules = []
179
- super_rules.each do |super_line|
180
- @parsed_rules.each do |line|
267
+ super_rules.each do |super_rule|
268
+ @parsed_rules.each do |rule|
181
269
  new_rules << []
182
270
 
183
- super_line.each do |super_rule|
184
- line.each do |rule|
185
- rule = [:parent, " ", *rule] unless rule.include?(:parent)
271
+ # An initial newline of the child rule
272
+ # should be moved to the beginning of the entire rule
273
+ rule.first.slice!(0) if nl = (rule.first.is_a?(String) && rule.first[0] == ?\n)
274
+ rule = [nl ? "\n" : "", :parent, " ", *rule] unless rule.include?(:parent)
186
275
 
187
- new_rules.last << rule.map do |segment|
188
- next segment unless segment == :parent
189
- super_rule
190
- end.join
191
- end
192
- end
276
+ new_rules.last << rule.map do |segment|
277
+ next segment unless segment == :parent
278
+ super_rule
279
+ end.join
193
280
  end
194
281
  end
195
282
  new_rules
@@ -202,18 +289,13 @@ module Sass::Tree
202
289
  while scanner.rest?
203
290
  rules.last << scanner.scan(/[^",&]*/)
204
291
  case scanner.scan(/./)
205
- when '&'
206
- warn <<END unless rules.last.empty? || rules.last.last =~ /(^|\s)$/
207
- DEPRECATION WARNING:
208
- On line #{@line}#{" of '#{@filename}'" if @filename}
209
- In Sass 3, parent selectors will only be able to appear
210
- at the beginning of simple selector sequences.
211
- For example, ".foo &.bar" is allowed but ".bar&" is not.
212
- END
213
- rules.last << :parent
292
+ when '&'; rules.last << :parent
214
293
  when ','
215
294
  scanner.scan(/\s*/)
216
- rules << [] if scanner.rest?
295
+ if scanner.rest?
296
+ rules << []
297
+ rules.last << "\n" if scanner.matched.include?("\n")
298
+ end
217
299
  when '"'
218
300
  rules.last << '"' << scanner.scan(/([^"\\]|\\.)*/)
219
301
  # We don't want to enforce that strings are closed,
@@ -228,5 +310,20 @@ END
228
310
 
229
311
  rules
230
312
  end
313
+
314
+ def debug_info_rule
315
+ node = DirectiveNode.new("@media -sass-debug-info")
316
+ debug_info.map {|k, v| [k.to_s, v.to_s]}.sort.each do |k, v|
317
+ rule = RuleNode.new([""])
318
+ rule.resolved_rules = [[k.to_s.gsub(/[^\w-]/, "\\\\\\0")]]
319
+ prop = PropNode.new("", "", :new)
320
+ prop.resolved_name = "font-family"
321
+ prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
322
+ rule << prop
323
+ node << rule
324
+ end
325
+ node.options = @options.merge(:debug_info => false, :line_comments => false, :style => :compressed)
326
+ node
327
+ end
231
328
  end
232
329
  end