haml 3.0.21 → 3.0.22

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 (195) hide show
  1. data/Rakefile +1 -41
  2. data/VERSION +1 -1
  3. data/lib/haml/precompiler.rb +8 -6
  4. data/lib/haml/template/plugin.rb +16 -6
  5. data/lib/sass/less.rb +31 -12
  6. data/lib/sass/script/funcall.rb +2 -1
  7. data/lib/sass/script/operation.rb +10 -5
  8. data/lib/sass/script/parser.rb +9 -0
  9. data/test/haml/engine_test.rb +25 -0
  10. data/test/sass/less_conversion_test.rb +24 -3
  11. data/test/sass/script_conversion_test.rb +60 -0
  12. metadata +243 -426
  13. data/vendor/sass/CONTRIBUTING +0 -3
  14. data/vendor/sass/MIT-LICENSE +0 -20
  15. data/vendor/sass/README.md +0 -201
  16. data/vendor/sass/Rakefile +0 -363
  17. data/vendor/sass/TODO +0 -39
  18. data/vendor/sass/VERSION +0 -1
  19. data/vendor/sass/VERSION_NAME +0 -1
  20. data/vendor/sass/bin/css2sass +0 -13
  21. data/vendor/sass/bin/sass +0 -8
  22. data/vendor/sass/bin/sass-convert +0 -7
  23. data/vendor/sass/doc-src/FAQ.md +0 -35
  24. data/vendor/sass/doc-src/INDENTED_SYNTAX.md +0 -210
  25. data/vendor/sass/doc-src/SASS_CHANGELOG.md +0 -1870
  26. data/vendor/sass/doc-src/SASS_REFERENCE.md +0 -1713
  27. data/vendor/sass/doc-src/SCSS_FOR_SASS_USERS.md +0 -155
  28. data/vendor/sass/ext/extconf.rb +0 -10
  29. data/vendor/sass/extra/update_watch.rb +0 -13
  30. data/vendor/sass/init.rb +0 -18
  31. data/vendor/sass/lib/sass.rb +0 -71
  32. data/vendor/sass/lib/sass/cache_store.rb +0 -208
  33. data/vendor/sass/lib/sass/callbacks.rb +0 -66
  34. data/vendor/sass/lib/sass/css.rb +0 -294
  35. data/vendor/sass/lib/sass/engine.rb +0 -792
  36. data/vendor/sass/lib/sass/environment.rb +0 -143
  37. data/vendor/sass/lib/sass/error.rb +0 -201
  38. data/vendor/sass/lib/sass/exec.rb +0 -619
  39. data/vendor/sass/lib/sass/importers.rb +0 -22
  40. data/vendor/sass/lib/sass/importers/base.rb +0 -138
  41. data/vendor/sass/lib/sass/importers/filesystem.rb +0 -121
  42. data/vendor/sass/lib/sass/less.rb +0 -363
  43. data/vendor/sass/lib/sass/plugin.rb +0 -126
  44. data/vendor/sass/lib/sass/plugin/compiler.rb +0 -346
  45. data/vendor/sass/lib/sass/plugin/configuration.rb +0 -123
  46. data/vendor/sass/lib/sass/plugin/generic.rb +0 -15
  47. data/vendor/sass/lib/sass/plugin/merb.rb +0 -48
  48. data/vendor/sass/lib/sass/plugin/rack.rb +0 -47
  49. data/vendor/sass/lib/sass/plugin/rails.rb +0 -41
  50. data/vendor/sass/lib/sass/plugin/staleness_checker.rb +0 -145
  51. data/vendor/sass/lib/sass/railtie.rb +0 -8
  52. data/vendor/sass/lib/sass/repl.rb +0 -58
  53. data/vendor/sass/lib/sass/root.rb +0 -7
  54. data/vendor/sass/lib/sass/script.rb +0 -63
  55. data/vendor/sass/lib/sass/script/bool.rb +0 -18
  56. data/vendor/sass/lib/sass/script/color.rb +0 -491
  57. data/vendor/sass/lib/sass/script/css_lexer.rb +0 -29
  58. data/vendor/sass/lib/sass/script/css_parser.rb +0 -31
  59. data/vendor/sass/lib/sass/script/funcall.rb +0 -79
  60. data/vendor/sass/lib/sass/script/functions.rb +0 -852
  61. data/vendor/sass/lib/sass/script/interpolation.rb +0 -70
  62. data/vendor/sass/lib/sass/script/lexer.rb +0 -337
  63. data/vendor/sass/lib/sass/script/literal.rb +0 -236
  64. data/vendor/sass/lib/sass/script/node.rb +0 -101
  65. data/vendor/sass/lib/sass/script/number.rb +0 -423
  66. data/vendor/sass/lib/sass/script/operation.rb +0 -92
  67. data/vendor/sass/lib/sass/script/parser.rb +0 -392
  68. data/vendor/sass/lib/sass/script/string.rb +0 -67
  69. data/vendor/sass/lib/sass/script/string_interpolation.rb +0 -93
  70. data/vendor/sass/lib/sass/script/unary_operation.rb +0 -57
  71. data/vendor/sass/lib/sass/script/variable.rb +0 -48
  72. data/vendor/sass/lib/sass/scss.rb +0 -17
  73. data/vendor/sass/lib/sass/scss/css_parser.rb +0 -51
  74. data/vendor/sass/lib/sass/scss/parser.rb +0 -838
  75. data/vendor/sass/lib/sass/scss/rx.rb +0 -126
  76. data/vendor/sass/lib/sass/scss/sass_parser.rb +0 -11
  77. data/vendor/sass/lib/sass/scss/script_lexer.rb +0 -15
  78. data/vendor/sass/lib/sass/scss/script_parser.rb +0 -25
  79. data/vendor/sass/lib/sass/scss/static_parser.rb +0 -40
  80. data/vendor/sass/lib/sass/selector.rb +0 -361
  81. data/vendor/sass/lib/sass/selector/abstract_sequence.rb +0 -62
  82. data/vendor/sass/lib/sass/selector/comma_sequence.rb +0 -82
  83. data/vendor/sass/lib/sass/selector/sequence.rb +0 -236
  84. data/vendor/sass/lib/sass/selector/simple.rb +0 -113
  85. data/vendor/sass/lib/sass/selector/simple_sequence.rb +0 -135
  86. data/vendor/sass/lib/sass/shared.rb +0 -78
  87. data/vendor/sass/lib/sass/tree/comment_node.rb +0 -128
  88. data/vendor/sass/lib/sass/tree/debug_node.rb +0 -36
  89. data/vendor/sass/lib/sass/tree/directive_node.rb +0 -75
  90. data/vendor/sass/lib/sass/tree/extend_node.rb +0 -65
  91. data/vendor/sass/lib/sass/tree/for_node.rb +0 -67
  92. data/vendor/sass/lib/sass/tree/if_node.rb +0 -81
  93. data/vendor/sass/lib/sass/tree/import_node.rb +0 -124
  94. data/vendor/sass/lib/sass/tree/mixin_def_node.rb +0 -60
  95. data/vendor/sass/lib/sass/tree/mixin_node.rb +0 -123
  96. data/vendor/sass/lib/sass/tree/node.rb +0 -490
  97. data/vendor/sass/lib/sass/tree/prop_node.rb +0 -220
  98. data/vendor/sass/lib/sass/tree/root_node.rb +0 -125
  99. data/vendor/sass/lib/sass/tree/rule_node.rb +0 -273
  100. data/vendor/sass/lib/sass/tree/variable_node.rb +0 -39
  101. data/vendor/sass/lib/sass/tree/warn_node.rb +0 -42
  102. data/vendor/sass/lib/sass/tree/while_node.rb +0 -48
  103. data/vendor/sass/lib/sass/util.rb +0 -700
  104. data/vendor/sass/lib/sass/util/subset_map.rb +0 -101
  105. data/vendor/sass/lib/sass/version.rb +0 -109
  106. data/vendor/sass/rails/init.rb +0 -1
  107. data/vendor/sass/sass.gemspec +0 -32
  108. data/vendor/sass/test/sass/cache_test.rb +0 -74
  109. data/vendor/sass/test/sass/callbacks_test.rb +0 -61
  110. data/vendor/sass/test/sass/conversion_test.rb +0 -1210
  111. data/vendor/sass/test/sass/css2sass_test.rb +0 -364
  112. data/vendor/sass/test/sass/data/hsl-rgb.txt +0 -319
  113. data/vendor/sass/test/sass/engine_test.rb +0 -2283
  114. data/vendor/sass/test/sass/extend_test.rb +0 -1348
  115. data/vendor/sass/test/sass/functions_test.rb +0 -565
  116. data/vendor/sass/test/sass/importer_test.rb +0 -104
  117. data/vendor/sass/test/sass/less_conversion_test.rb +0 -632
  118. data/vendor/sass/test/sass/mock_importer.rb +0 -49
  119. data/vendor/sass/test/sass/more_results/more1.css +0 -9
  120. data/vendor/sass/test/sass/more_results/more1_with_line_comments.css +0 -26
  121. data/vendor/sass/test/sass/more_results/more_import.css +0 -29
  122. data/vendor/sass/test/sass/more_templates/_more_partial.sass +0 -2
  123. data/vendor/sass/test/sass/more_templates/more1.sass +0 -23
  124. data/vendor/sass/test/sass/more_templates/more_import.sass +0 -11
  125. data/vendor/sass/test/sass/plugin_test.rb +0 -430
  126. data/vendor/sass/test/sass/results/alt.css +0 -4
  127. data/vendor/sass/test/sass/results/basic.css +0 -9
  128. data/vendor/sass/test/sass/results/compact.css +0 -5
  129. data/vendor/sass/test/sass/results/complex.css +0 -86
  130. data/vendor/sass/test/sass/results/compressed.css +0 -1
  131. data/vendor/sass/test/sass/results/expanded.css +0 -19
  132. data/vendor/sass/test/sass/results/import.css +0 -31
  133. data/vendor/sass/test/sass/results/line_numbers.css +0 -49
  134. data/vendor/sass/test/sass/results/mixins.css +0 -95
  135. data/vendor/sass/test/sass/results/multiline.css +0 -24
  136. data/vendor/sass/test/sass/results/nested.css +0 -22
  137. data/vendor/sass/test/sass/results/options.css +0 -1
  138. data/vendor/sass/test/sass/results/parent_ref.css +0 -13
  139. data/vendor/sass/test/sass/results/script.css +0 -16
  140. data/vendor/sass/test/sass/results/scss_import.css +0 -31
  141. data/vendor/sass/test/sass/results/scss_importee.css +0 -2
  142. data/vendor/sass/test/sass/results/subdir/nested_subdir/nested_subdir.css +0 -1
  143. data/vendor/sass/test/sass/results/subdir/subdir.css +0 -3
  144. data/vendor/sass/test/sass/results/units.css +0 -11
  145. data/vendor/sass/test/sass/results/warn.css +0 -0
  146. data/vendor/sass/test/sass/results/warn_imported.css +0 -0
  147. data/vendor/sass/test/sass/script_conversion_test.rb +0 -254
  148. data/vendor/sass/test/sass/script_test.rb +0 -470
  149. data/vendor/sass/test/sass/scss/css_test.rb +0 -897
  150. data/vendor/sass/test/sass/scss/rx_test.rb +0 -156
  151. data/vendor/sass/test/sass/scss/scss_test.rb +0 -1088
  152. data/vendor/sass/test/sass/scss/test_helper.rb +0 -37
  153. data/vendor/sass/test/sass/templates/_partial.sass +0 -2
  154. data/vendor/sass/test/sass/templates/alt.sass +0 -16
  155. data/vendor/sass/test/sass/templates/basic.sass +0 -23
  156. data/vendor/sass/test/sass/templates/bork1.sass +0 -2
  157. data/vendor/sass/test/sass/templates/bork2.sass +0 -2
  158. data/vendor/sass/test/sass/templates/bork3.sass +0 -2
  159. data/vendor/sass/test/sass/templates/bork4.sass +0 -2
  160. data/vendor/sass/test/sass/templates/compact.sass +0 -17
  161. data/vendor/sass/test/sass/templates/complex.sass +0 -305
  162. data/vendor/sass/test/sass/templates/compressed.sass +0 -15
  163. data/vendor/sass/test/sass/templates/expanded.sass +0 -17
  164. data/vendor/sass/test/sass/templates/import.sass +0 -12
  165. data/vendor/sass/test/sass/templates/importee.less +0 -2
  166. data/vendor/sass/test/sass/templates/importee.sass +0 -19
  167. data/vendor/sass/test/sass/templates/line_numbers.sass +0 -13
  168. data/vendor/sass/test/sass/templates/mixin_bork.sass +0 -5
  169. data/vendor/sass/test/sass/templates/mixins.sass +0 -76
  170. data/vendor/sass/test/sass/templates/multiline.sass +0 -20
  171. data/vendor/sass/test/sass/templates/nested.sass +0 -25
  172. data/vendor/sass/test/sass/templates/nested_bork1.sass +0 -2
  173. data/vendor/sass/test/sass/templates/nested_bork2.sass +0 -2
  174. data/vendor/sass/test/sass/templates/nested_bork3.sass +0 -2
  175. data/vendor/sass/test/sass/templates/nested_bork4.sass +0 -2
  176. data/vendor/sass/test/sass/templates/nested_mixin_bork.sass +0 -6
  177. data/vendor/sass/test/sass/templates/options.sass +0 -2
  178. data/vendor/sass/test/sass/templates/parent_ref.sass +0 -25
  179. data/vendor/sass/test/sass/templates/script.sass +0 -101
  180. data/vendor/sass/test/sass/templates/scss_import.scss +0 -11
  181. data/vendor/sass/test/sass/templates/scss_importee.scss +0 -1
  182. data/vendor/sass/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +0 -2
  183. data/vendor/sass/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +0 -3
  184. data/vendor/sass/test/sass/templates/subdir/subdir.sass +0 -6
  185. data/vendor/sass/test/sass/templates/units.sass +0 -11
  186. data/vendor/sass/test/sass/templates/warn.sass +0 -3
  187. data/vendor/sass/test/sass/templates/warn_imported.sass +0 -4
  188. data/vendor/sass/test/sass/test_helper.rb +0 -8
  189. data/vendor/sass/test/sass/util/subset_map_test.rb +0 -91
  190. data/vendor/sass/test/sass/util_test.rb +0 -275
  191. data/vendor/sass/test/test_helper.rb +0 -64
  192. data/vendor/sass/yard/callbacks.rb +0 -29
  193. data/vendor/sass/yard/default/fulldoc/html/css/common.sass +0 -26
  194. data/vendor/sass/yard/default/layout/html/footer.erb +0 -12
  195. data/vendor/sass/yard/inherited_hash.rb +0 -41
@@ -1,66 +0,0 @@
1
- module Sass
2
- # A lightweight infrastructure for defining and running callbacks.
3
- # Callbacks are defined using \{#define\_callback\} at the class level,
4
- # and called using `run_#{name}` at the instance level.
5
- #
6
- # Clients can add callbacks by calling the generated `on_#{name}` method,
7
- # and passing in a block that's run when the callback is activated.
8
- #
9
- # @example Define a callback
10
- # class Munger
11
- # extend Sass::Callbacks
12
- # define_callback :string_munged
13
- #
14
- # def munge(str)
15
- # res = str.gsub(/[a-z]/, '\1\1')
16
- # run_string_munged str, res
17
- # res
18
- # end
19
- # end
20
- #
21
- # @example Use a callback
22
- # m = Munger.new
23
- # m.on_string_munged {|str, res| puts "#{str} was munged into #{res}!"}
24
- # m.munge "bar" #=> bar was munged into bbaarr!
25
- module Callbacks
26
- # Automatically includes {InstanceMethods}
27
- # when something extends this module.
28
- #
29
- # @param base [Module]
30
- def self.extended(base)
31
- base.send(:include, InstanceMethods)
32
- end
33
- protected
34
-
35
- module InstanceMethods
36
- # Removes all callbacks registered against this object.
37
- def clear_callbacks!
38
- @_sass_callbacks = {}
39
- end
40
- end
41
-
42
- # Define a callback with the given name.
43
- # This will define an `on_#{name}` method
44
- # that registers a block,
45
- # and a `run_#{name}` method that runs that block
46
- # (optionall with some arguments).
47
- #
48
- # @param name [Symbol] The name of the callback
49
- # @return [void]
50
- def define_callback(name)
51
- class_eval <<RUBY
52
- def on_#{name}(&block)
53
- @_sass_callbacks ||= {}
54
- (@_sass_callbacks[#{name.inspect}] ||= []) << block
55
- end
56
-
57
- def run_#{name}(*args)
58
- return unless @_sass_callbacks
59
- return unless @_sass_callbacks[#{name.inspect}]
60
- @_sass_callbacks[#{name.inspect}].each {|c| c[*args]}
61
- end
62
- private :run_#{name}
63
- RUBY
64
- end
65
- end
66
- end
@@ -1,294 +0,0 @@
1
- require File.dirname(__FILE__) + '/../sass'
2
- require 'sass/tree/node'
3
- require 'sass/scss/css_parser'
4
- require 'strscan'
5
-
6
- module Sass
7
- # This class converts CSS documents into Sass or SCSS templates.
8
- # It works by parsing the CSS document into a {Sass::Tree} structure,
9
- # and then applying various transformations to the structure
10
- # to produce more concise and idiomatic Sass/SCSS.
11
- #
12
- # Example usage:
13
- #
14
- # Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
15
- # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
16
- class CSS
17
- # @param template [String] The CSS stylesheet.
18
- # This stylesheet can be encoded using any encoding
19
- # that can be converted to Unicode.
20
- # If the stylesheet contains an `@charset` declaration,
21
- # that overrides the Ruby encoding
22
- # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
23
- # @option options :old [Boolean] (false)
24
- # Whether or not to output old property syntax
25
- # (`:color blue` as opposed to `color: blue`).
26
- # This is only meaningful when generating Sass code,
27
- # rather than SCSS.
28
- def initialize(template, options = {})
29
- if template.is_a? IO
30
- template = template.read
31
- end
32
-
33
- @options = options.dup
34
- # Backwards compatibility
35
- @options[:old] = true if @options[:alternate] == false
36
- @template = template
37
- end
38
-
39
- # Converts the CSS template into Sass or SCSS code.
40
- #
41
- # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
42
- # @return [String] The resulting Sass or SCSS code
43
- # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
44
- def render(fmt = :sass)
45
- check_encoding!
46
- build_tree.send("to_#{fmt}", @options).strip + "\n"
47
- rescue Sass::SyntaxError => err
48
- err.modify_backtrace(:filename => @options[:filename] || '(css)')
49
- raise err
50
- end
51
-
52
- # Returns the original encoding of the document,
53
- # or `nil` under Ruby 1.8.
54
- #
55
- # @return [Encoding, nil]
56
- # @raise [Encoding::UndefinedConversionError] if the source encoding
57
- # cannot be converted to UTF-8
58
- # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
59
- def source_encoding
60
- check_encoding!
61
- @original_encoding
62
- end
63
-
64
- private
65
-
66
- def check_encoding!
67
- return if @checked_encoding
68
- @checked_encoding = true
69
- @template, @original_encoding = Sass::Util.check_sass_encoding(@template) do |msg, line|
70
- raise Sass::SyntaxError.new(msg, :line => line)
71
- end
72
- end
73
-
74
- # Parses the CSS template and applies various transformations
75
- #
76
- # @return [Tree::Node] The root node of the parsed tree
77
- def build_tree
78
- root = Sass::SCSS::CssParser.new(@template).parse
79
- expand_commas root
80
- parent_ref_rules root
81
- remove_parent_refs root
82
- flatten_rules root
83
- fold_commas root
84
- root
85
- end
86
-
87
- # Transform
88
- #
89
- # foo, bar, baz
90
- # color: blue
91
- #
92
- # into
93
- #
94
- # foo
95
- # color: blue
96
- # bar
97
- # color: blue
98
- # baz
99
- # color: blue
100
- #
101
- # @param root [Tree::Node] The parent node
102
- def expand_commas(root)
103
- root.children.map! do |child|
104
- unless child.is_a?(Tree::RuleNode) && child.rule.first.include?(',')
105
- expand_commas(child) if child.is_a?(Tree::DirectiveNode)
106
- next child
107
- end
108
- child.rule.first.split(',').map do |rule|
109
- node = Tree::RuleNode.new([rule.strip])
110
- node.children = child.children
111
- node
112
- end
113
- end
114
- root.children.flatten!
115
- end
116
-
117
- # Make rules use parent refs so that
118
- #
119
- # foo
120
- # color: green
121
- # foo.bar
122
- # color: blue
123
- #
124
- # becomes
125
- #
126
- # foo
127
- # color: green
128
- # &.bar
129
- # color: blue
130
- #
131
- # This has the side effect of nesting rules,
132
- # so that
133
- #
134
- # foo
135
- # color: green
136
- # foo bar
137
- # color: red
138
- # foo baz
139
- # color: blue
140
- #
141
- # becomes
142
- #
143
- # foo
144
- # color: green
145
- # & bar
146
- # color: red
147
- # & baz
148
- # color: blue
149
- #
150
- # @param root [Tree::Node] The parent node
151
- def parent_ref_rules(root)
152
- current_rule = nil
153
- root.children.map! do |child|
154
- unless child.is_a?(Tree::RuleNode)
155
- parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode)
156
- next child
157
- end
158
-
159
- first, rest = child.rule.first.scan(/\A(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?\Z/m).first
160
-
161
- if current_rule.nil? || current_rule.rule.first != first
162
- current_rule = Tree::RuleNode.new([first])
163
- end
164
-
165
- if rest
166
- child.rule = ["&" + rest]
167
- current_rule << child
168
- else
169
- current_rule.children += child.children
170
- end
171
-
172
- current_rule
173
- end
174
- root.children.compact!
175
- root.children.uniq!
176
-
177
- root.children.each { |v| parent_ref_rules(v) }
178
- end
179
-
180
- # Remove useless parent refs so that
181
- #
182
- # foo
183
- # & bar
184
- # color: blue
185
- #
186
- # becomes
187
- #
188
- # foo
189
- # bar
190
- # color: blue
191
- #
192
- # @param root [Tree::Node] The parent node
193
- def remove_parent_refs(root)
194
- root.children.each do |child|
195
- case child
196
- when Tree::RuleNode
197
- child.rule.first.gsub! /^& +/, ''
198
- remove_parent_refs child
199
- when Tree::DirectiveNode
200
- remove_parent_refs child
201
- end
202
- end
203
- end
204
-
205
- # Flatten rules so that
206
- #
207
- # foo
208
- # bar
209
- # color: red
210
- #
211
- # becomes
212
- #
213
- # foo bar
214
- # color: red
215
- #
216
- # and
217
- #
218
- # foo
219
- # &.bar
220
- # color: blue
221
- #
222
- # becomes
223
- #
224
- # foo.bar
225
- # color: blue
226
- #
227
- # @param root [Tree::Node] The parent node
228
- def flatten_rules(root)
229
- root.children.each do |child|
230
- case child
231
- when Tree::RuleNode
232
- flatten_rule(child)
233
- when Tree::DirectiveNode
234
- flatten_rules(child)
235
- end
236
- end
237
- end
238
-
239
- # Flattens a single rule
240
- #
241
- # @param rule [Tree::RuleNode] The candidate for flattening
242
- # @see #flatten_rules
243
- def flatten_rule(rule)
244
- while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
245
- child = rule.children.first
246
-
247
- if child.rule.first[0] == ?&
248
- rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
249
- else
250
- rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
251
- end
252
-
253
- rule.children = child.children
254
- end
255
-
256
- flatten_rules(rule)
257
- end
258
-
259
- # Transform
260
- #
261
- # foo
262
- # bar
263
- # color: blue
264
- # baz
265
- # color: blue
266
- #
267
- # into
268
- #
269
- # foo
270
- # bar, baz
271
- # color: blue
272
- #
273
- # @param rule [Tree::RuleNode] The candidate for flattening
274
- def fold_commas(root)
275
- prev_rule = nil
276
- root.children.map! do |child|
277
- unless child.is_a?(Tree::RuleNode)
278
- fold_commas(child) if child.is_a?(Tree::DirectiveNode)
279
- next child
280
- end
281
-
282
- if prev_rule && prev_rule.children == child.children
283
- prev_rule.rule.first << ", #{child.rule.first}"
284
- next nil
285
- end
286
-
287
- fold_commas(child)
288
- prev_rule = child
289
- child
290
- end
291
- root.children.compact!
292
- end
293
- end
294
- end
@@ -1,792 +0,0 @@
1
- require 'strscan'
2
- require 'digest/sha1'
3
- require 'sass/cache_store'
4
- require 'sass/tree/node'
5
- require 'sass/tree/root_node'
6
- require 'sass/tree/rule_node'
7
- require 'sass/tree/comment_node'
8
- require 'sass/tree/prop_node'
9
- require 'sass/tree/directive_node'
10
- require 'sass/tree/variable_node'
11
- require 'sass/tree/mixin_def_node'
12
- require 'sass/tree/mixin_node'
13
- require 'sass/tree/extend_node'
14
- require 'sass/tree/if_node'
15
- require 'sass/tree/while_node'
16
- require 'sass/tree/for_node'
17
- require 'sass/tree/debug_node'
18
- require 'sass/tree/warn_node'
19
- require 'sass/tree/import_node'
20
- require 'sass/selector'
21
- require 'sass/environment'
22
- require 'sass/script'
23
- require 'sass/scss'
24
- require 'sass/error'
25
- require 'sass/importers'
26
- require 'sass/shared'
27
-
28
- module Sass
29
-
30
- # A Sass mixin.
31
- #
32
- # `name`: `String`
33
- # : The name of the mixin.
34
- #
35
- # `args`: `Array<(String, Script::Node)>`
36
- # : The arguments for the mixin.
37
- # Each element is a tuple containing the name of the argument
38
- # and the parse tree for the default value of the argument.
39
- #
40
- # `environment`: {Sass::Environment}
41
- # : The environment in which the mixin was defined.
42
- # This is captured so that the mixin can have access
43
- # to local variables defined in its scope.
44
- #
45
- # `tree`: {Sass::Tree::Node}
46
- # : The parse tree for the mixin.
47
- Mixin = Struct.new(:name, :args, :environment, :tree)
48
-
49
- # This class handles the parsing and compilation of the Sass template.
50
- # Example usage:
51
- #
52
- # template = File.load('stylesheets/sassy.sass')
53
- # sass_engine = Sass::Engine.new(template)
54
- # output = sass_engine.render
55
- # puts output
56
- class Engine
57
- include Sass::Util
58
-
59
- # A line of Sass code.
60
- #
61
- # `text`: `String`
62
- # : The text in the line, without any whitespace at the beginning or end.
63
- #
64
- # `tabs`: `Fixnum`
65
- # : The level of indentation of the line.
66
- #
67
- # `index`: `Fixnum`
68
- # : The line number in the original document.
69
- #
70
- # `offset`: `Fixnum`
71
- # : The number of bytes in on the line that the text begins.
72
- # This ends up being the number of bytes of leading whitespace.
73
- #
74
- # `filename`: `String`
75
- # : The name of the file in which this line appeared.
76
- #
77
- # `children`: `Array<Line>`
78
- # : The lines nested below this one.
79
- class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children)
80
- def comment?
81
- text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
82
- end
83
- end
84
-
85
- # The character that begins a CSS property.
86
- PROPERTY_CHAR = ?:
87
-
88
- # The character that designates that
89
- # a property should be assigned to a SassScript expression.
90
- SCRIPT_CHAR = ?=
91
-
92
- # The character that designates the beginning of a comment,
93
- # either Sass or CSS.
94
- COMMENT_CHAR = ?/
95
-
96
- # The character that follows the general COMMENT_CHAR and designates a Sass comment,
97
- # which is not output as a CSS comment.
98
- SASS_COMMENT_CHAR = ?/
99
-
100
- # The character that follows the general COMMENT_CHAR and designates a CSS comment,
101
- # which is embedded in the CSS document.
102
- CSS_COMMENT_CHAR = ?*
103
-
104
- # The character used to denote a compiler directive.
105
- DIRECTIVE_CHAR = ?@
106
-
107
- # Designates a non-parsed rule.
108
- ESCAPE_CHAR = ?\\
109
-
110
- # Designates block as mixin definition rather than CSS rules to output
111
- MIXIN_DEFINITION_CHAR = ?=
112
-
113
- # Includes named mixin declared using MIXIN_DEFINITION_CHAR
114
- MIXIN_INCLUDE_CHAR = ?+
115
-
116
- # The regex that matches properties of the form `name: prop`.
117
- PROPERTY_NEW_MATCHER = /^[^\s:"\[]+\s*[=:](\s|$)/
118
-
119
- # The regex that matches and extracts data from
120
- # properties of the form `name: prop`.
121
- PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
122
-
123
- # The regex that matches and extracts data from
124
- # properties of the form `:name prop`.
125
- PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/
126
-
127
- # The default options for Sass::Engine.
128
- # @api public
129
- DEFAULT_OPTIONS = {
130
- :style => :nested,
131
- :load_paths => ['.'],
132
- :cache => true,
133
- :cache_location => './.sass-cache',
134
- :syntax => :sass,
135
- :filesystem_importer => Sass::Importers::Filesystem
136
- }.freeze
137
-
138
- # Converts a Sass options hash into a standard form, filling in
139
- # default values and resolving aliases.
140
- #
141
- # @param options [{Symbol => Object}] The options hash;
142
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
143
- # @return [{Symbol => Object}] The normalized options hash.
144
- # @private
145
- def self.normalize_options(options)
146
- options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
147
-
148
- # If the `:filename` option is passed in without an importer,
149
- # assume it's using the default filesystem importer.
150
- options[:importer] ||= options[:filesystem_importer].new(".") if options[:filename]
151
-
152
- # Tracks the original filename of the top-level Sass file
153
- options[:original_filename] = options[:original_filename] || options[:filename]
154
-
155
- options[:cache_store] ||= Sass::FileCacheStore.new(options[:cache_location])
156
- # Support both, because the docs said one and the other actually worked
157
- # for quite a long time.
158
- options[:line_comments] ||= options[:line_numbers]
159
-
160
- options[:load_paths] = options[:load_paths].map do |p|
161
- next p unless p.is_a?(String)
162
- options[:filesystem_importer].new(p)
163
- end
164
-
165
- # Backwards compatibility
166
- options[:property_syntax] ||= options[:attribute_syntax]
167
- case options[:property_syntax]
168
- when :alternate; options[:property_syntax] = :new
169
- when :normal; options[:property_syntax] = :old
170
- end
171
-
172
- options
173
- end
174
-
175
- # Returns the {Sass::Engine} for the given file.
176
- # This is preferable to Sass::Engine.new when reading from a file
177
- # because it properly sets up the Engine's metadata,
178
- # enables parse-tree caching,
179
- # and infers the syntax from the filename.
180
- #
181
- # @param filename [String] The path to the Sass or SCSS file
182
- # @param options [{Symbol => Object}] The options hash;
183
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
184
- # @return [Sass::Engine] The Engine for the given Sass or SCSS file.
185
- # @raise [Sass::SyntaxError] if there's an error in the document.
186
- def self.for_file(filename, options)
187
- had_syntax = options[:syntax]
188
-
189
- if had_syntax
190
- # Use what was explicitly specificed
191
- elsif filename =~ /\.scss$/
192
- options.merge!(:syntax => :scss)
193
- elsif filename =~ /\.sass$/
194
- options.merge!(:syntax => :sass)
195
- end
196
-
197
- Sass::Engine.new(File.read(filename), options.merge(:filename => filename))
198
- end
199
-
200
- # The options for the Sass engine.
201
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
202
- #
203
- # @return [{Symbol => Object}]
204
- attr_reader :options
205
-
206
- # Creates a new Engine. Note that Engine should only be used directly
207
- # when compiling in-memory Sass code.
208
- # If you're compiling a single Sass file from the filesystem,
209
- # use \{Sass::Engine.for\_file}.
210
- # If you're compiling multiple files from the filesystem,
211
- # use {Sass::Plugin.
212
- #
213
- # @param template [String] The Sass template.
214
- # This template can be encoded using any encoding
215
- # that can be converted to Unicode.
216
- # If the template contains an `@charset` declaration,
217
- # that overrides the Ruby encoding
218
- # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
219
- # @param options [{Symbol => Object}] An options hash.
220
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
221
- # @see {Sass::Engine.for_file}
222
- # @see {Sass::Plugin}
223
- def initialize(template, options={})
224
- @options = self.class.normalize_options(options)
225
- @template = template
226
- end
227
-
228
- # Render the template to CSS.
229
- #
230
- # @return [String] The CSS
231
- # @raise [Sass::SyntaxError] if there's an error in the document
232
- # @raise [Encoding::UndefinedConversionError] if the source encoding
233
- # cannot be converted to UTF-8
234
- # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
235
- def render
236
- return _render unless @options[:quiet]
237
- Sass::Util.silence_sass_warnings {_render}
238
- end
239
- alias_method :to_css, :render
240
-
241
- # Parses the document into its parse tree.
242
- #
243
- # @return [Sass::Tree::Node] The root of the parse tree.
244
- # @raise [Sass::SyntaxError] if there's an error in the document
245
- def to_tree
246
- return _to_tree unless @options[:quiet]
247
- Sass::Util.silence_sass_warnings {_to_tree}
248
- end
249
-
250
- # Returns the original encoding of the document,
251
- # or `nil` under Ruby 1.8.
252
- #
253
- # @return [Encoding, nil]
254
- # @raise [Encoding::UndefinedConversionError] if the source encoding
255
- # cannot be converted to UTF-8
256
- # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
257
- def source_encoding
258
- check_encoding!
259
- @original_encoding
260
- end
261
-
262
- private
263
-
264
- def _render
265
- rendered = _to_tree.render
266
- return rendered if ruby1_8?
267
- return rendered.encode(source_encoding)
268
- end
269
-
270
- def _to_tree
271
- if (@options[:cache] || @options[:read_cache]) &&
272
- @options[:filename] && @options[:importer]
273
- key = sassc_key
274
- sha = Digest::SHA1.hexdigest(@template)
275
-
276
- if root = @options[:cache_store].retrieve(key, sha)
277
- @options = root.options.merge(@options)
278
- root.options = @options
279
- return root
280
- end
281
- end
282
-
283
- check_encoding!
284
-
285
- if @options[:syntax] == :scss
286
- root = Sass::SCSS::Parser.new(@template).parse
287
- else
288
- root = Tree::RootNode.new(@template)
289
- append_children(root, tree(tabulate(@template)).first, true)
290
- end
291
-
292
- root.options = @options
293
- @options[:cache_store].store(key, sha, root) if @options[:cache] && key && sha
294
- root
295
- rescue SyntaxError => e
296
- e.modify_backtrace(:filename => @options[:filename], :line => @line)
297
- e.sass_template = @template
298
- raise e
299
- end
300
-
301
- def sassc_key
302
- @options[:cache_store].key(*@options[:importer].key(@options[:filename], @options))
303
- end
304
-
305
- def check_encoding!
306
- return if @checked_encoding
307
- @checked_encoding = true
308
- @template, @original_encoding = check_sass_encoding(@template) do |msg, line|
309
- raise Sass::SyntaxError.new(msg, :line => line)
310
- end
311
- end
312
-
313
- def tabulate(string)
314
- tab_str = nil
315
- comment_tab_str = nil
316
- first = true
317
- lines = []
318
- string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^.*?$/).each_with_index do |line, index|
319
- index += (@options[:line] || 1)
320
- if line.strip.empty?
321
- lines.last.text << "\n" if lines.last && lines.last.comment?
322
- next
323
- end
324
-
325
- line_tab_str = line[/^\s*/]
326
- unless line_tab_str.empty?
327
- if tab_str.nil?
328
- comment_tab_str ||= line_tab_str
329
- next if try_comment(line, lines.last, "", comment_tab_str, index)
330
- comment_tab_str = nil
331
- end
332
-
333
- tab_str ||= line_tab_str
334
-
335
- raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
336
- :line => index) if first
337
-
338
- raise SyntaxError.new("Indentation can't use both tabs and spaces.",
339
- :line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
340
- end
341
- first &&= !tab_str.nil?
342
- if tab_str.nil?
343
- lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
344
- next
345
- end
346
-
347
- comment_tab_str ||= line_tab_str
348
- if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index)
349
- next
350
- else
351
- comment_tab_str = nil
352
- end
353
-
354
- line_tabs = line_tab_str.scan(tab_str).size
355
- if tab_str * line_tabs != line_tab_str
356
- message = <<END.strip.gsub("\n", ' ')
357
- Inconsistent indentation: #{Sass::Shared.human_indentation line_tab_str, true} used for indentation,
358
- but the rest of the document was indented using #{Sass::Shared.human_indentation tab_str}.
359
- END
360
- raise SyntaxError.new(message, :line => index)
361
- end
362
-
363
- lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
364
- end
365
- lines
366
- end
367
-
368
- def try_comment(line, last, tab_str, comment_tab_str, index)
369
- return unless last && last.comment?
370
- # Nested comment stuff must be at least one whitespace char deeper
371
- # than the normal indentation
372
- return unless line =~ /^#{tab_str}\s/
373
- unless line =~ /^(?:#{comment_tab_str})(.*)$/
374
- raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
375
- Inconsistent indentation:
376
- previous line was indented by #{Sass::Shared.human_indentation comment_tab_str},
377
- but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
378
- MSG
379
- end
380
-
381
- last.text << "\n" << $1
382
- true
383
- end
384
-
385
- def tree(arr, i = 0)
386
- return [], i if arr[i].nil?
387
-
388
- base = arr[i].tabs
389
- nodes = []
390
- while (line = arr[i]) && line.tabs >= base
391
- if line.tabs > base
392
- raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
393
- :line => line.index) if line.tabs > base + 1
394
-
395
- nodes.last.children, i = tree(arr, i)
396
- else
397
- nodes << line
398
- i += 1
399
- end
400
- end
401
- return nodes, i
402
- end
403
-
404
- def build_tree(parent, line, root = false)
405
- @line = line.index
406
- node_or_nodes = parse_line(parent, line, root)
407
-
408
- Array(node_or_nodes).each do |node|
409
- # Node is a symbol if it's non-outputting, like a variable assignment
410
- next unless node.is_a? Tree::Node
411
-
412
- node.line = line.index
413
- node.filename = line.filename
414
-
415
- append_children(node, line.children, false)
416
- end
417
-
418
- node_or_nodes
419
- end
420
-
421
- def append_children(parent, children, root)
422
- continued_rule = nil
423
- continued_comment = nil
424
- children.each do |line|
425
- child = build_tree(parent, line, root)
426
-
427
- if child.is_a?(Tree::RuleNode) && child.continued?
428
- raise SyntaxError.new("Rules can't end in commas.",
429
- :line => child.line) unless child.children.empty?
430
- if continued_rule
431
- continued_rule.add_rules child
432
- else
433
- continued_rule = child
434
- end
435
- next
436
- end
437
-
438
- if continued_rule
439
- raise SyntaxError.new("Rules can't end in commas.",
440
- :line => continued_rule.line) unless child.is_a?(Tree::RuleNode)
441
- continued_rule.add_rules child
442
- continued_rule.children = child.children
443
- continued_rule, child = nil, continued_rule
444
- end
445
-
446
- if child.is_a?(Tree::CommentNode) && child.silent
447
- if continued_comment &&
448
- child.line == continued_comment.line +
449
- continued_comment.value.count("\n") + 1
450
- continued_comment.value << "\n" << child.value
451
- next
452
- end
453
-
454
- continued_comment = child
455
- end
456
-
457
- check_for_no_children(child)
458
- validate_and_append_child(parent, child, line, root)
459
- end
460
-
461
- raise SyntaxError.new("Rules can't end in commas.",
462
- :line => continued_rule.line) if continued_rule
463
-
464
- parent
465
- end
466
-
467
- def validate_and_append_child(parent, child, line, root)
468
- case child
469
- when Array
470
- child.each {|c| validate_and_append_child(parent, c, line, root)}
471
- when Tree::Node
472
- parent << child
473
- end
474
- end
475
-
476
- def check_for_no_children(node)
477
- return unless node.is_a?(Tree::RuleNode) && node.children.empty?
478
- Sass::Util.sass_warn(<<WARNING.strip)
479
- WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
480
- This selector doesn't have any properties and will not be rendered.
481
- WARNING
482
- end
483
-
484
- def parse_line(parent, line, root)
485
- case line.text[0]
486
- when PROPERTY_CHAR
487
- if line.text[1] == PROPERTY_CHAR ||
488
- (@options[:property_syntax] == :new &&
489
- line.text =~ PROPERTY_OLD && $3.empty?)
490
- # Support CSS3-style pseudo-elements,
491
- # which begin with ::,
492
- # as well as pseudo-classes
493
- # if we're using the new property syntax
494
- Tree::RuleNode.new(parse_interp(line.text))
495
- else
496
- name, eq, value = line.text.scan(PROPERTY_OLD)[0]
497
- raise SyntaxError.new("Invalid property: \"#{line.text}\".",
498
- :line => @line) if name.nil? || value.nil?
499
- parse_property(name, parse_interp(name), eq, value, :old, line)
500
- end
501
- when ?!, ?$
502
- parse_variable(line)
503
- when COMMENT_CHAR
504
- parse_comment(line.text)
505
- when DIRECTIVE_CHAR
506
- parse_directive(parent, line, root)
507
- when ESCAPE_CHAR
508
- Tree::RuleNode.new(parse_interp(line.text[1..-1]))
509
- when MIXIN_DEFINITION_CHAR
510
- parse_mixin_definition(line)
511
- when MIXIN_INCLUDE_CHAR
512
- if line.text[1].nil? || line.text[1] == ?\s
513
- Tree::RuleNode.new(parse_interp(line.text))
514
- else
515
- parse_mixin_include(line, root)
516
- end
517
- else
518
- parse_property_or_rule(line)
519
- end
520
- end
521
-
522
- def parse_property_or_rule(line)
523
- scanner = StringScanner.new(line.text)
524
- hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
525
- parser = Sass::SCSS::SassParser.new(scanner, @line)
526
-
527
- unless res = parser.parse_interp_ident
528
- return Tree::RuleNode.new(parse_interp(line.text))
529
- end
530
- res.unshift(hack_char) if hack_char
531
- if comment = scanner.scan(Sass::SCSS::RX::COMMENT)
532
- res << comment
533
- end
534
-
535
- name = line.text[0...scanner.pos]
536
- if scanner.scan(/\s*([:=])(?:\s|$)/)
537
- parse_property(name, res, scanner[1], scanner.rest, :new, line)
538
- else
539
- res.pop if comment
540
- Tree::RuleNode.new(res + parse_interp(scanner.rest))
541
- end
542
- end
543
-
544
- def parse_property(name, parsed_name, eq, value, prop, line)
545
- if value.strip.empty?
546
- expr = Sass::Script::String.new("")
547
- else
548
- expr = parse_script(value, :offset => line.offset + line.text.index(value))
549
-
550
- if eq.strip[0] == SCRIPT_CHAR
551
- expr.context = :equals
552
- Script.equals_warning("properties", name,
553
- Sass::Tree::PropNode.val_to_sass(expr, @options), false,
554
- @line, line.offset + 1, @options[:filename])
555
- end
556
- end
557
- Tree::PropNode.new(parse_interp(name), expr, prop)
558
- end
559
-
560
- def parse_variable(line)
561
- name, op, value, default = line.text.scan(Script::MATCH)[0]
562
- guarded = op =~ /^\|\|/
563
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
564
- :line => @line + 1) unless line.children.empty?
565
- raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
566
- :line => @line) unless name && value
567
- Script.var_warning(name, @line, line.offset + 1, @options[:filename]) if line.text[0] == ?!
568
-
569
- expr = parse_script(value, :offset => line.offset + line.text.index(value))
570
- if op =~ /=$/
571
- expr.context = :equals
572
- type = guarded ? "variable defaults" : "variables"
573
- Script.equals_warning(type, "$#{name}", expr.to_sass,
574
- guarded, @line, line.offset + 1, @options[:filename])
575
- end
576
-
577
- Tree::VariableNode.new(name, expr, default || guarded)
578
- end
579
-
580
- def parse_comment(line)
581
- if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
582
- silent = line[1] == SASS_COMMENT_CHAR
583
- Tree::CommentNode.new(
584
- format_comment_text(line[2..-1], silent),
585
- silent)
586
- else
587
- Tree::RuleNode.new(parse_interp(line))
588
- end
589
- end
590
-
591
- def parse_directive(parent, line, root)
592
- directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
593
- offset = directive.size + whitespace.size + 1 if whitespace
594
-
595
- # If value begins with url( or ",
596
- # it's a CSS @import rule and we don't want to touch it.
597
- if directive == "import"
598
- parse_import(line, value)
599
- elsif directive == "mixin"
600
- parse_mixin_definition(line)
601
- elsif directive == "include"
602
- parse_mixin_include(line, root)
603
- elsif directive == "for"
604
- parse_for(line, root, value)
605
- elsif directive == "else"
606
- parse_else(parent, line, value)
607
- elsif directive == "while"
608
- raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
609
- Tree::WhileNode.new(parse_script(value, :offset => offset))
610
- elsif directive == "if"
611
- raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
612
- Tree::IfNode.new(parse_script(value, :offset => offset))
613
- elsif directive == "debug"
614
- raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
615
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
616
- :line => @line + 1) unless line.children.empty?
617
- offset = line.offset + line.text.index(value).to_i
618
- Tree::DebugNode.new(parse_script(value, :offset => offset))
619
- elsif directive == "extend"
620
- raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
621
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
622
- :line => @line + 1) unless line.children.empty?
623
- offset = line.offset + line.text.index(value).to_i
624
- Tree::ExtendNode.new(parse_interp(value, offset))
625
- elsif directive == "warn"
626
- raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
627
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
628
- :line => @line + 1) unless line.children.empty?
629
- offset = line.offset + line.text.index(value).to_i
630
- Tree::WarnNode.new(parse_script(value, :offset => offset))
631
- else
632
- Tree::DirectiveNode.new(line.text)
633
- end
634
- end
635
-
636
- def parse_for(line, root, text)
637
- var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
638
-
639
- if var.nil? # scan failed, try to figure out why for error message
640
- if text !~ /^[^\s]+/
641
- expected = "variable name"
642
- elsif text !~ /^[^\s]+\s+from\s+.+/
643
- expected = "'from <expr>'"
644
- else
645
- expected = "'to <expr>' or 'through <expr>'"
646
- end
647
- raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
648
- end
649
- raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
650
- if var.slice!(0) == ?!
651
- offset = line.offset + line.text.index("!" + var) + 1
652
- Script.var_warning(var, @line, offset, @options[:filename])
653
- end
654
-
655
- parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
656
- parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
657
- Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
658
- end
659
-
660
- def parse_else(parent, line, text)
661
- previous = parent.children.last
662
- raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
663
-
664
- if text
665
- if text !~ /^if\s+(.+)/
666
- raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
667
- end
668
- expr = parse_script($1, :offset => line.offset + line.text.index($1))
669
- end
670
-
671
- node = Tree::IfNode.new(expr)
672
- append_children(node, line.children, false)
673
- previous.add_else node
674
- nil
675
- end
676
-
677
- def parse_import(line, value)
678
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
679
- :line => @line + 1) unless line.children.empty?
680
-
681
- scanner = StringScanner.new(value)
682
- values = []
683
-
684
- loop do
685
- unless node = parse_import_arg(scanner)
686
- raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
687
- :line => @line)
688
- end
689
- values << node
690
- break unless scanner.scan(/,\s*/)
691
- end
692
-
693
- return values
694
- end
695
-
696
- def parse_import_arg(scanner)
697
- return if scanner.eos?
698
- unless (str = scanner.scan(Sass::SCSS::RX::STRING)) ||
699
- (uri = scanner.scan(Sass::SCSS::RX::URI))
700
- return Tree::ImportNode.new(scanner.scan(/[^,]+/))
701
- end
702
-
703
- val = scanner[1] || scanner[2]
704
- scanner.scan(/\s*/)
705
- if media = scanner.scan(/[^,].*/)
706
- Tree::DirectiveNode.new("@import #{str || uri} #{media}")
707
- elsif uri
708
- Tree::DirectiveNode.new("@import #{uri}")
709
- elsif val =~ /^http:\/\//
710
- Tree::DirectiveNode.new("@import url(#{val})")
711
- else
712
- Tree::ImportNode.new(val)
713
- end
714
- end
715
-
716
- MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
717
- def parse_mixin_definition(line)
718
- name, arg_string = line.text.scan(MIXIN_DEF_RE).first
719
- raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
720
-
721
- offset = line.offset + line.text.size - arg_string.size
722
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
723
- parse_mixin_definition_arglist
724
- default_arg_found = false
725
- Tree::MixinDefNode.new(name, args)
726
- end
727
-
728
- MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
729
- def parse_mixin_include(line, root)
730
- name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
731
- raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
732
-
733
- offset = line.offset + line.text.size - arg_string.size
734
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
735
- parse_mixin_include_arglist
736
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
737
- :line => @line + 1) unless line.children.empty?
738
- Tree::MixinNode.new(name, args)
739
- end
740
-
741
- def parse_script(script, options = {})
742
- line = options[:line] || @line
743
- offset = options[:offset] || 0
744
- Script.parse(script, line, offset, @options)
745
- end
746
-
747
- def format_comment_text(text, silent)
748
- content = text.split("\n")
749
-
750
- if content.first && content.first.strip.empty?
751
- removed_first = true
752
- content.shift
753
- end
754
-
755
- return silent ? "//" : "/* */" if content.empty?
756
- content.last.gsub!(%r{ ?\*/ *$}, '')
757
- content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
758
- content.first.gsub!(/^ /, '') unless removed_first
759
- if silent
760
- "//" + content.join("\n//")
761
- else
762
- # The #gsub fixes the case of a trailing */
763
- "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
764
- end
765
- end
766
-
767
- def parse_interp(text, offset = 0)
768
- self.class.parse_interp(text, @line, offset, :filename => @filename)
769
- end
770
-
771
- # It's important that this have strings (at least)
772
- # at the beginning, the end, and between each Script::Node.
773
- #
774
- # @private
775
- def self.parse_interp(text, line, offset, options)
776
- res = []
777
- rest = Sass::Shared.handle_interpolation text do |scan|
778
- escapes = scan[2].size
779
- res << scan.matched[0...-2 - escapes]
780
- if escapes % 2 == 1
781
- res << "\\" * (escapes - 1) << '#{'
782
- else
783
- res << "\\" * [0, escapes - 1].max
784
- res << Script::Parser.new(
785
- scan, line, offset + scan.pos - scan.matched_size, options).
786
- parse_interpolated
787
- end
788
- end
789
- res << rest
790
- end
791
- end
792
- end