sass 3.1.0.alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/EDGE_GEM_VERSION +1 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +201 -0
  6. data/REVISION +1 -0
  7. data/Rakefile +353 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/css2sass +13 -0
  11. data/bin/sass +8 -0
  12. data/bin/sass-convert +7 -0
  13. data/extra/update_watch.rb +13 -0
  14. data/init.rb +18 -0
  15. data/lib/sass.rb +71 -0
  16. data/lib/sass/cache_store.rb +208 -0
  17. data/lib/sass/callbacks.rb +66 -0
  18. data/lib/sass/css.rb +294 -0
  19. data/lib/sass/engine.rb +792 -0
  20. data/lib/sass/environment.rb +143 -0
  21. data/lib/sass/error.rb +201 -0
  22. data/lib/sass/exec.rb +619 -0
  23. data/lib/sass/importers.rb +22 -0
  24. data/lib/sass/importers/base.rb +138 -0
  25. data/lib/sass/importers/filesystem.rb +121 -0
  26. data/lib/sass/less.rb +363 -0
  27. data/lib/sass/plugin.rb +126 -0
  28. data/lib/sass/plugin/compiler.rb +346 -0
  29. data/lib/sass/plugin/configuration.rb +123 -0
  30. data/lib/sass/plugin/generic.rb +15 -0
  31. data/lib/sass/plugin/merb.rb +48 -0
  32. data/lib/sass/plugin/rack.rb +47 -0
  33. data/lib/sass/plugin/rails.rb +41 -0
  34. data/lib/sass/plugin/staleness_checker.rb +145 -0
  35. data/lib/sass/railtie.rb +8 -0
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/root.rb +7 -0
  38. data/lib/sass/script.rb +63 -0
  39. data/lib/sass/script/bool.rb +18 -0
  40. data/lib/sass/script/color.rb +490 -0
  41. data/lib/sass/script/css_lexer.rb +29 -0
  42. data/lib/sass/script/css_parser.rb +31 -0
  43. data/lib/sass/script/funcall.rb +78 -0
  44. data/lib/sass/script/functions.rb +852 -0
  45. data/lib/sass/script/interpolation.rb +70 -0
  46. data/lib/sass/script/lexer.rb +337 -0
  47. data/lib/sass/script/literal.rb +236 -0
  48. data/lib/sass/script/node.rb +101 -0
  49. data/lib/sass/script/number.rb +420 -0
  50. data/lib/sass/script/operation.rb +92 -0
  51. data/lib/sass/script/parser.rb +392 -0
  52. data/lib/sass/script/string.rb +67 -0
  53. data/lib/sass/script/string_interpolation.rb +93 -0
  54. data/lib/sass/script/unary_operation.rb +57 -0
  55. data/lib/sass/script/variable.rb +48 -0
  56. data/lib/sass/scss.rb +17 -0
  57. data/lib/sass/scss/css_parser.rb +51 -0
  58. data/lib/sass/scss/parser.rb +838 -0
  59. data/lib/sass/scss/rx.rb +126 -0
  60. data/lib/sass/scss/sass_parser.rb +11 -0
  61. data/lib/sass/scss/script_lexer.rb +15 -0
  62. data/lib/sass/scss/script_parser.rb +25 -0
  63. data/lib/sass/scss/static_parser.rb +40 -0
  64. data/lib/sass/selector.rb +361 -0
  65. data/lib/sass/selector/abstract_sequence.rb +62 -0
  66. data/lib/sass/selector/comma_sequence.rb +82 -0
  67. data/lib/sass/selector/sequence.rb +236 -0
  68. data/lib/sass/selector/simple.rb +113 -0
  69. data/lib/sass/selector/simple_sequence.rb +135 -0
  70. data/lib/sass/shared.rb +78 -0
  71. data/lib/sass/tree/comment_node.rb +128 -0
  72. data/lib/sass/tree/debug_node.rb +36 -0
  73. data/lib/sass/tree/directive_node.rb +75 -0
  74. data/lib/sass/tree/extend_node.rb +65 -0
  75. data/lib/sass/tree/for_node.rb +67 -0
  76. data/lib/sass/tree/if_node.rb +81 -0
  77. data/lib/sass/tree/import_node.rb +124 -0
  78. data/lib/sass/tree/mixin_def_node.rb +60 -0
  79. data/lib/sass/tree/mixin_node.rb +123 -0
  80. data/lib/sass/tree/node.rb +490 -0
  81. data/lib/sass/tree/prop_node.rb +220 -0
  82. data/lib/sass/tree/root_node.rb +125 -0
  83. data/lib/sass/tree/rule_node.rb +273 -0
  84. data/lib/sass/tree/variable_node.rb +39 -0
  85. data/lib/sass/tree/warn_node.rb +42 -0
  86. data/lib/sass/tree/while_node.rb +48 -0
  87. data/lib/sass/util.rb +687 -0
  88. data/lib/sass/util/subset_map.rb +101 -0
  89. data/lib/sass/version.rb +109 -0
  90. data/rails/init.rb +1 -0
  91. data/test/sass/cache_test.rb +74 -0
  92. data/test/sass/callbacks_test.rb +61 -0
  93. data/test/sass/conversion_test.rb +1210 -0
  94. data/test/sass/css2sass_test.rb +364 -0
  95. data/test/sass/data/hsl-rgb.txt +319 -0
  96. data/test/sass/engine_test.rb +2273 -0
  97. data/test/sass/extend_test.rb +1348 -0
  98. data/test/sass/functions_test.rb +565 -0
  99. data/test/sass/importer_test.rb +104 -0
  100. data/test/sass/less_conversion_test.rb +632 -0
  101. data/test/sass/mock_importer.rb +49 -0
  102. data/test/sass/more_results/more1.css +9 -0
  103. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  104. data/test/sass/more_results/more_import.css +29 -0
  105. data/test/sass/more_templates/_more_partial.sass +2 -0
  106. data/test/sass/more_templates/more1.sass +23 -0
  107. data/test/sass/more_templates/more_import.sass +11 -0
  108. data/test/sass/plugin_test.rb +430 -0
  109. data/test/sass/results/alt.css +4 -0
  110. data/test/sass/results/basic.css +9 -0
  111. data/test/sass/results/compact.css +5 -0
  112. data/test/sass/results/complex.css +86 -0
  113. data/test/sass/results/compressed.css +1 -0
  114. data/test/sass/results/expanded.css +19 -0
  115. data/test/sass/results/import.css +31 -0
  116. data/test/sass/results/line_numbers.css +49 -0
  117. data/test/sass/results/mixins.css +95 -0
  118. data/test/sass/results/multiline.css +24 -0
  119. data/test/sass/results/nested.css +22 -0
  120. data/test/sass/results/options.css +1 -0
  121. data/test/sass/results/parent_ref.css +13 -0
  122. data/test/sass/results/script.css +16 -0
  123. data/test/sass/results/scss_import.css +31 -0
  124. data/test/sass/results/scss_importee.css +2 -0
  125. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  126. data/test/sass/results/subdir/subdir.css +3 -0
  127. data/test/sass/results/units.css +11 -0
  128. data/test/sass/results/warn.css +0 -0
  129. data/test/sass/results/warn_imported.css +0 -0
  130. data/test/sass/script_conversion_test.rb +254 -0
  131. data/test/sass/script_test.rb +459 -0
  132. data/test/sass/scss/css_test.rb +897 -0
  133. data/test/sass/scss/rx_test.rb +156 -0
  134. data/test/sass/scss/scss_test.rb +1088 -0
  135. data/test/sass/scss/test_helper.rb +37 -0
  136. data/test/sass/templates/_partial.sass +2 -0
  137. data/test/sass/templates/alt.sass +16 -0
  138. data/test/sass/templates/basic.sass +23 -0
  139. data/test/sass/templates/bork1.sass +2 -0
  140. data/test/sass/templates/bork2.sass +2 -0
  141. data/test/sass/templates/bork3.sass +2 -0
  142. data/test/sass/templates/bork4.sass +2 -0
  143. data/test/sass/templates/compact.sass +17 -0
  144. data/test/sass/templates/complex.sass +305 -0
  145. data/test/sass/templates/compressed.sass +15 -0
  146. data/test/sass/templates/expanded.sass +17 -0
  147. data/test/sass/templates/import.sass +12 -0
  148. data/test/sass/templates/importee.less +2 -0
  149. data/test/sass/templates/importee.sass +19 -0
  150. data/test/sass/templates/line_numbers.sass +13 -0
  151. data/test/sass/templates/mixin_bork.sass +5 -0
  152. data/test/sass/templates/mixins.sass +76 -0
  153. data/test/sass/templates/multiline.sass +20 -0
  154. data/test/sass/templates/nested.sass +25 -0
  155. data/test/sass/templates/nested_bork1.sass +2 -0
  156. data/test/sass/templates/nested_bork2.sass +2 -0
  157. data/test/sass/templates/nested_bork3.sass +2 -0
  158. data/test/sass/templates/nested_bork4.sass +2 -0
  159. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  160. data/test/sass/templates/options.sass +2 -0
  161. data/test/sass/templates/parent_ref.sass +25 -0
  162. data/test/sass/templates/script.sass +101 -0
  163. data/test/sass/templates/scss_import.scss +11 -0
  164. data/test/sass/templates/scss_importee.scss +1 -0
  165. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  166. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  167. data/test/sass/templates/subdir/subdir.sass +6 -0
  168. data/test/sass/templates/units.sass +11 -0
  169. data/test/sass/templates/warn.sass +3 -0
  170. data/test/sass/templates/warn_imported.sass +4 -0
  171. data/test/sass/test_helper.rb +8 -0
  172. data/test/sass/util/subset_map_test.rb +91 -0
  173. data/test/sass/util_test.rb +275 -0
  174. data/test/test_helper.rb +64 -0
  175. data/vendor/fssm/LICENSE +20 -0
  176. data/vendor/fssm/README.markdown +55 -0
  177. data/vendor/fssm/Rakefile +59 -0
  178. data/vendor/fssm/VERSION.yml +5 -0
  179. data/vendor/fssm/example.rb +9 -0
  180. data/vendor/fssm/fssm.gemspec +77 -0
  181. data/vendor/fssm/lib/fssm.rb +33 -0
  182. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  183. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  184. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  185. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  186. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  187. data/vendor/fssm/lib/fssm/path.rb +91 -0
  188. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  189. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  190. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  191. data/vendor/fssm/lib/fssm/support.rb +63 -0
  192. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  193. data/vendor/fssm/profile/prof-cache.rb +40 -0
  194. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  195. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  196. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  197. data/vendor/fssm/profile/prof.html +2379 -0
  198. data/vendor/fssm/spec/path_spec.rb +75 -0
  199. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  200. data/vendor/fssm/spec/root/file.css +0 -0
  201. data/vendor/fssm/spec/root/file.rb +0 -0
  202. data/vendor/fssm/spec/root/file.yml +0 -0
  203. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  204. data/vendor/fssm/spec/spec_helper.rb +14 -0
  205. metadata +297 -0
@@ -0,0 +1,792 @@
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