xass 0.1.0

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 (242) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +11 -0
  3. data/CONTRIBUTING +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +201 -0
  6. data/Rakefile +349 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/push +13 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/update_watch.rb +13 -0
  14. data/init.rb +18 -0
  15. data/lib/sass/cache_stores/base.rb +88 -0
  16. data/lib/sass/cache_stores/chain.rb +33 -0
  17. data/lib/sass/cache_stores/filesystem.rb +64 -0
  18. data/lib/sass/cache_stores/memory.rb +47 -0
  19. data/lib/sass/cache_stores/null.rb +25 -0
  20. data/lib/sass/cache_stores.rb +15 -0
  21. data/lib/sass/callbacks.rb +66 -0
  22. data/lib/sass/css.rb +409 -0
  23. data/lib/sass/engine.rb +930 -0
  24. data/lib/sass/environment.rb +101 -0
  25. data/lib/sass/error.rb +201 -0
  26. data/lib/sass/exec.rb +707 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +186 -0
  29. data/lib/sass/importers.rb +22 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/logger.rb +15 -0
  33. data/lib/sass/media.rb +213 -0
  34. data/lib/sass/plugin/compiler.rb +406 -0
  35. data/lib/sass/plugin/configuration.rb +123 -0
  36. data/lib/sass/plugin/generic.rb +15 -0
  37. data/lib/sass/plugin/merb.rb +48 -0
  38. data/lib/sass/plugin/rack.rb +60 -0
  39. data/lib/sass/plugin/rails.rb +47 -0
  40. data/lib/sass/plugin/staleness_checker.rb +199 -0
  41. data/lib/sass/plugin.rb +133 -0
  42. data/lib/sass/railtie.rb +10 -0
  43. data/lib/sass/repl.rb +57 -0
  44. data/lib/sass/root.rb +7 -0
  45. data/lib/sass/script/arg_list.rb +52 -0
  46. data/lib/sass/script/bool.rb +18 -0
  47. data/lib/sass/script/color.rb +606 -0
  48. data/lib/sass/script/css_lexer.rb +29 -0
  49. data/lib/sass/script/css_parser.rb +31 -0
  50. data/lib/sass/script/funcall.rb +245 -0
  51. data/lib/sass/script/functions.rb +1543 -0
  52. data/lib/sass/script/interpolation.rb +79 -0
  53. data/lib/sass/script/lexer.rb +345 -0
  54. data/lib/sass/script/list.rb +85 -0
  55. data/lib/sass/script/literal.rb +221 -0
  56. data/lib/sass/script/node.rb +99 -0
  57. data/lib/sass/script/null.rb +37 -0
  58. data/lib/sass/script/number.rb +453 -0
  59. data/lib/sass/script/operation.rb +110 -0
  60. data/lib/sass/script/parser.rb +502 -0
  61. data/lib/sass/script/string.rb +51 -0
  62. data/lib/sass/script/string_interpolation.rb +103 -0
  63. data/lib/sass/script/unary_operation.rb +69 -0
  64. data/lib/sass/script/variable.rb +58 -0
  65. data/lib/sass/script.rb +39 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1180 -0
  68. data/lib/sass/scss/rx.rb +133 -0
  69. data/lib/sass/scss/script_lexer.rb +15 -0
  70. data/lib/sass/scss/script_parser.rb +25 -0
  71. data/lib/sass/scss/static_parser.rb +54 -0
  72. data/lib/sass/scss.rb +16 -0
  73. data/lib/sass/selector/abstract_sequence.rb +94 -0
  74. data/lib/sass/selector/comma_sequence.rb +92 -0
  75. data/lib/sass/selector/sequence.rb +507 -0
  76. data/lib/sass/selector/simple.rb +119 -0
  77. data/lib/sass/selector/simple_sequence.rb +215 -0
  78. data/lib/sass/selector.rb +452 -0
  79. data/lib/sass/shared.rb +76 -0
  80. data/lib/sass/supports.rb +229 -0
  81. data/lib/sass/tree/charset_node.rb +22 -0
  82. data/lib/sass/tree/comment_node.rb +82 -0
  83. data/lib/sass/tree/content_node.rb +9 -0
  84. data/lib/sass/tree/css_import_node.rb +60 -0
  85. data/lib/sass/tree/debug_node.rb +18 -0
  86. data/lib/sass/tree/directive_node.rb +42 -0
  87. data/lib/sass/tree/each_node.rb +24 -0
  88. data/lib/sass/tree/extend_node.rb +36 -0
  89. data/lib/sass/tree/for_node.rb +36 -0
  90. data/lib/sass/tree/function_node.rb +34 -0
  91. data/lib/sass/tree/if_node.rb +52 -0
  92. data/lib/sass/tree/import_node.rb +75 -0
  93. data/lib/sass/tree/media_node.rb +58 -0
  94. data/lib/sass/tree/mixin_def_node.rb +38 -0
  95. data/lib/sass/tree/mixin_node.rb +39 -0
  96. data/lib/sass/tree/node.rb +196 -0
  97. data/lib/sass/tree/prop_node.rb +152 -0
  98. data/lib/sass/tree/return_node.rb +18 -0
  99. data/lib/sass/tree/root_node.rb +78 -0
  100. data/lib/sass/tree/rule_node.rb +132 -0
  101. data/lib/sass/tree/supports_node.rb +51 -0
  102. data/lib/sass/tree/trace_node.rb +32 -0
  103. data/lib/sass/tree/variable_node.rb +30 -0
  104. data/lib/sass/tree/visitors/base.rb +75 -0
  105. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  106. data/lib/sass/tree/visitors/convert.rb +316 -0
  107. data/lib/sass/tree/visitors/cssize.rb +241 -0
  108. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  109. data/lib/sass/tree/visitors/extend.rb +68 -0
  110. data/lib/sass/tree/visitors/perform.rb +446 -0
  111. data/lib/sass/tree/visitors/set_options.rb +125 -0
  112. data/lib/sass/tree/visitors/to_css.rb +228 -0
  113. data/lib/sass/tree/warn_node.rb +18 -0
  114. data/lib/sass/tree/while_node.rb +18 -0
  115. data/lib/sass/util/multibyte_string_scanner.rb +155 -0
  116. data/lib/sass/util/subset_map.rb +109 -0
  117. data/lib/sass/util/test.rb +10 -0
  118. data/lib/sass/util.rb +948 -0
  119. data/lib/sass/version.rb +126 -0
  120. data/lib/sass.rb +95 -0
  121. data/rails/init.rb +1 -0
  122. data/test/Gemfile +3 -0
  123. data/test/Gemfile.lock +10 -0
  124. data/test/sass/cache_test.rb +89 -0
  125. data/test/sass/callbacks_test.rb +61 -0
  126. data/test/sass/conversion_test.rb +1760 -0
  127. data/test/sass/css2sass_test.rb +458 -0
  128. data/test/sass/data/hsl-rgb.txt +319 -0
  129. data/test/sass/engine_test.rb +3244 -0
  130. data/test/sass/exec_test.rb +86 -0
  131. data/test/sass/extend_test.rb +1482 -0
  132. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  133. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  134. data/test/sass/functions_test.rb +1139 -0
  135. data/test/sass/importer_test.rb +192 -0
  136. data/test/sass/logger_test.rb +58 -0
  137. data/test/sass/mock_importer.rb +49 -0
  138. data/test/sass/more_results/more1.css +9 -0
  139. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  140. data/test/sass/more_results/more_import.css +29 -0
  141. data/test/sass/more_templates/_more_partial.sass +2 -0
  142. data/test/sass/more_templates/more1.sass +23 -0
  143. data/test/sass/more_templates/more_import.sass +11 -0
  144. data/test/sass/plugin_test.rb +564 -0
  145. data/test/sass/results/alt.css +4 -0
  146. data/test/sass/results/basic.css +9 -0
  147. data/test/sass/results/cached_import_option.css +3 -0
  148. data/test/sass/results/compact.css +5 -0
  149. data/test/sass/results/complex.css +86 -0
  150. data/test/sass/results/compressed.css +1 -0
  151. data/test/sass/results/expanded.css +19 -0
  152. data/test/sass/results/filename_fn.css +3 -0
  153. data/test/sass/results/if.css +3 -0
  154. data/test/sass/results/import.css +31 -0
  155. data/test/sass/results/import_charset.css +5 -0
  156. data/test/sass/results/import_charset_1_8.css +5 -0
  157. data/test/sass/results/import_charset_ibm866.css +5 -0
  158. data/test/sass/results/import_content.css +1 -0
  159. data/test/sass/results/line_numbers.css +49 -0
  160. data/test/sass/results/mixins.css +95 -0
  161. data/test/sass/results/multiline.css +24 -0
  162. data/test/sass/results/nested.css +22 -0
  163. data/test/sass/results/options.css +1 -0
  164. data/test/sass/results/parent_ref.css +13 -0
  165. data/test/sass/results/script.css +16 -0
  166. data/test/sass/results/scss_import.css +31 -0
  167. data/test/sass/results/scss_importee.css +2 -0
  168. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  169. data/test/sass/results/subdir/subdir.css +3 -0
  170. data/test/sass/results/units.css +11 -0
  171. data/test/sass/results/warn.css +0 -0
  172. data/test/sass/results/warn_imported.css +0 -0
  173. data/test/sass/script_conversion_test.rb +299 -0
  174. data/test/sass/script_test.rb +622 -0
  175. data/test/sass/scss/css_test.rb +1100 -0
  176. data/test/sass/scss/rx_test.rb +156 -0
  177. data/test/sass/scss/scss_test.rb +2106 -0
  178. data/test/sass/scss/test_helper.rb +37 -0
  179. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  180. data/test/sass/templates/_double_import_loop2.sass +1 -0
  181. data/test/sass/templates/_filename_fn_import.scss +11 -0
  182. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  183. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  184. data/test/sass/templates/_imported_content.sass +3 -0
  185. data/test/sass/templates/_partial.sass +2 -0
  186. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  187. data/test/sass/templates/alt.sass +16 -0
  188. data/test/sass/templates/basic.sass +23 -0
  189. data/test/sass/templates/bork1.sass +2 -0
  190. data/test/sass/templates/bork2.sass +2 -0
  191. data/test/sass/templates/bork3.sass +2 -0
  192. data/test/sass/templates/bork4.sass +2 -0
  193. data/test/sass/templates/bork5.sass +3 -0
  194. data/test/sass/templates/cached_import_option.scss +3 -0
  195. data/test/sass/templates/compact.sass +17 -0
  196. data/test/sass/templates/complex.sass +305 -0
  197. data/test/sass/templates/compressed.sass +15 -0
  198. data/test/sass/templates/double_import_loop1.sass +1 -0
  199. data/test/sass/templates/expanded.sass +17 -0
  200. data/test/sass/templates/filename_fn.scss +18 -0
  201. data/test/sass/templates/if.sass +11 -0
  202. data/test/sass/templates/import.sass +12 -0
  203. data/test/sass/templates/import_charset.sass +9 -0
  204. data/test/sass/templates/import_charset_1_8.sass +6 -0
  205. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  206. data/test/sass/templates/import_content.sass +4 -0
  207. data/test/sass/templates/importee.less +2 -0
  208. data/test/sass/templates/importee.sass +19 -0
  209. data/test/sass/templates/line_numbers.sass +13 -0
  210. data/test/sass/templates/mixin_bork.sass +5 -0
  211. data/test/sass/templates/mixins.sass +76 -0
  212. data/test/sass/templates/multiline.sass +20 -0
  213. data/test/sass/templates/nested.sass +25 -0
  214. data/test/sass/templates/nested_bork1.sass +2 -0
  215. data/test/sass/templates/nested_bork2.sass +2 -0
  216. data/test/sass/templates/nested_bork3.sass +2 -0
  217. data/test/sass/templates/nested_bork4.sass +2 -0
  218. data/test/sass/templates/nested_import.sass +2 -0
  219. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  220. data/test/sass/templates/options.sass +2 -0
  221. data/test/sass/templates/parent_ref.sass +25 -0
  222. data/test/sass/templates/same_name_different_ext.sass +2 -0
  223. data/test/sass/templates/same_name_different_ext.scss +1 -0
  224. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  225. data/test/sass/templates/script.sass +101 -0
  226. data/test/sass/templates/scss_import.scss +11 -0
  227. data/test/sass/templates/scss_importee.scss +1 -0
  228. data/test/sass/templates/single_import_loop.sass +1 -0
  229. data/test/sass/templates/subdir/import_up1.scss +1 -0
  230. data/test/sass/templates/subdir/import_up2.scss +1 -0
  231. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  232. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  233. data/test/sass/templates/subdir/subdir.sass +6 -0
  234. data/test/sass/templates/units.sass +11 -0
  235. data/test/sass/templates/warn.sass +3 -0
  236. data/test/sass/templates/warn_imported.sass +4 -0
  237. data/test/sass/test_helper.rb +8 -0
  238. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  239. data/test/sass/util/subset_map_test.rb +91 -0
  240. data/test/sass/util_test.rb +382 -0
  241. data/test/test_helper.rb +80 -0
  242. metadata +354 -0
@@ -0,0 +1,930 @@
1
+ require 'set'
2
+ require 'digest/sha1'
3
+ require 'sass/cache_stores'
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/media_node'
11
+ require 'sass/tree/supports_node'
12
+ require 'sass/tree/css_import_node'
13
+ require 'sass/tree/variable_node'
14
+ require 'sass/tree/mixin_def_node'
15
+ require 'sass/tree/mixin_node'
16
+ require 'sass/tree/trace_node'
17
+ require 'sass/tree/content_node'
18
+ require 'sass/tree/function_node'
19
+ require 'sass/tree/return_node'
20
+ require 'sass/tree/extend_node'
21
+ require 'sass/tree/if_node'
22
+ require 'sass/tree/while_node'
23
+ require 'sass/tree/for_node'
24
+ require 'sass/tree/each_node'
25
+ require 'sass/tree/debug_node'
26
+ require 'sass/tree/warn_node'
27
+ require 'sass/tree/import_node'
28
+ require 'sass/tree/charset_node'
29
+ require 'sass/tree/visitors/base'
30
+ require 'sass/tree/visitors/perform'
31
+ require 'sass/tree/visitors/cssize'
32
+ require 'sass/tree/visitors/extend'
33
+ require 'sass/tree/visitors/convert'
34
+ require 'sass/tree/visitors/to_css'
35
+ require 'sass/tree/visitors/deep_copy'
36
+ require 'sass/tree/visitors/set_options'
37
+ require 'sass/tree/visitors/check_nesting'
38
+ require 'sass/selector'
39
+ require 'sass/environment'
40
+ require 'sass/script'
41
+ require 'sass/scss'
42
+ require 'sass/error'
43
+ require 'sass/importers'
44
+ require 'sass/shared'
45
+ require 'sass/media'
46
+ require 'sass/supports'
47
+
48
+ module Sass
49
+
50
+ # A Sass mixin or function.
51
+ #
52
+ # `name`: `String`
53
+ # : The name of the mixin/function.
54
+ #
55
+ # `args`: `Array<(Script::Node, Script::Node)>`
56
+ # : The arguments for the mixin/function.
57
+ # Each element is a tuple containing the variable node of the argument
58
+ # and the parse tree for the default value of the argument.
59
+ #
60
+ # `splat`: `Script::Node?`
61
+ # : The variable node of the splat argument for this callable, or null.
62
+ #
63
+ # `environment`: {Sass::Environment}
64
+ # : The environment in which the mixin/function was defined.
65
+ # This is captured so that the mixin/function can have access
66
+ # to local variables defined in its scope.
67
+ #
68
+ # `tree`: `Array<Tree::Node>`
69
+ # : The parse tree for the mixin/function.
70
+ #
71
+ # `has_content`: `Boolean`
72
+ # : Whether the callable accepts a content block.
73
+ #
74
+ # `type`: `String`
75
+ # : The user-friendly name of the type of the callable.
76
+ Callable = Struct.new(:name, :args, :splat, :environment, :tree, :has_content, :type)
77
+
78
+ # This class handles the parsing and compilation of the Sass template.
79
+ # Example usage:
80
+ #
81
+ # template = File.load('stylesheets/sassy.sass')
82
+ # sass_engine = Sass::Engine.new(template)
83
+ # output = sass_engine.render
84
+ # puts output
85
+ class Engine
86
+ include Sass::Util
87
+
88
+ # A line of Sass code.
89
+ #
90
+ # `text`: `String`
91
+ # : The text in the line, without any whitespace at the beginning or end.
92
+ #
93
+ # `tabs`: `Fixnum`
94
+ # : The level of indentation of the line.
95
+ #
96
+ # `index`: `Fixnum`
97
+ # : The line number in the original document.
98
+ #
99
+ # `offset`: `Fixnum`
100
+ # : The number of bytes in on the line that the text begins.
101
+ # This ends up being the number of bytes of leading whitespace.
102
+ #
103
+ # `filename`: `String`
104
+ # : The name of the file in which this line appeared.
105
+ #
106
+ # `children`: `Array<Line>`
107
+ # : The lines nested below this one.
108
+ #
109
+ # `comment_tab_str`: `String?`
110
+ # : The prefix indentation for this comment, if it is a comment.
111
+ class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
112
+ def comment?
113
+ text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
114
+ end
115
+ end
116
+
117
+ # The character that begins a CSS property.
118
+ PROPERTY_CHAR = ?:
119
+
120
+ # The character that designates the beginning of a comment,
121
+ # either Sass or CSS.
122
+ COMMENT_CHAR = ?/
123
+
124
+ # The character that follows the general COMMENT_CHAR and designates a Sass comment,
125
+ # which is not output as a CSS comment.
126
+ SASS_COMMENT_CHAR = ?/
127
+
128
+ # The character that indicates that a comment allows interpolation
129
+ # and should be preserved even in `:compressed` mode.
130
+ SASS_LOUD_COMMENT_CHAR = ?!
131
+
132
+ # The character that follows the general COMMENT_CHAR and designates a CSS comment,
133
+ # which is embedded in the CSS document.
134
+ CSS_COMMENT_CHAR = ?*
135
+
136
+ # The character used to denote a compiler directive.
137
+ DIRECTIVE_CHAR = ?@
138
+
139
+ # Designates a non-parsed rule.
140
+ ESCAPE_CHAR = ?\\
141
+
142
+ # Designates block as mixin definition rather than CSS rules to output
143
+ MIXIN_DEFINITION_CHAR = ?=
144
+
145
+ # Includes named mixin declared using MIXIN_DEFINITION_CHAR
146
+ MIXIN_INCLUDE_CHAR = ?+
147
+
148
+ # The regex that matches and extracts data from
149
+ # properties of the form `:name prop`.
150
+ PROPERTY_OLD = /^:([^\s=:"]+)\s*(?:\s+|$)(.*)/
151
+
152
+ # The default options for Sass::Engine.
153
+ # @api public
154
+ DEFAULT_OPTIONS = {
155
+ :style => :nested,
156
+ :load_paths => ['.'],
157
+ :cache => true,
158
+ :cache_location => './.sass-cache',
159
+ :syntax => :sass,
160
+ :filesystem_importer => Sass::Importers::Filesystem
161
+ }.freeze
162
+
163
+ # Converts a Sass options hash into a standard form, filling in
164
+ # default values and resolving aliases.
165
+ #
166
+ # @param options [{Symbol => Object}] The options hash;
167
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
168
+ # @return [{Symbol => Object}] The normalized options hash.
169
+ # @private
170
+ def self.normalize_options(options)
171
+ options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
172
+
173
+ # If the `:filename` option is passed in without an importer,
174
+ # assume it's using the default filesystem importer.
175
+ options[:importer] ||= options[:filesystem_importer].new(".") if options[:filename]
176
+
177
+ # Tracks the original filename of the top-level Sass file
178
+ options[:original_filename] ||= options[:filename]
179
+
180
+ options[:cache_store] ||= Sass::CacheStores::Chain.new(
181
+ Sass::CacheStores::Memory.new, Sass::CacheStores::Filesystem.new(options[:cache_location]))
182
+ # Support both, because the docs said one and the other actually worked
183
+ # for quite a long time.
184
+ options[:line_comments] ||= options[:line_numbers]
185
+
186
+ options[:load_paths] = (options[:load_paths] + Sass.load_paths).map do |p|
187
+ next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname))
188
+ options[:filesystem_importer].new(p.to_s)
189
+ end
190
+
191
+ # Backwards compatibility
192
+ options[:property_syntax] ||= options[:attribute_syntax]
193
+ case options[:property_syntax]
194
+ when :alternate; options[:property_syntax] = :new
195
+ when :normal; options[:property_syntax] = :old
196
+ end
197
+
198
+ options
199
+ end
200
+
201
+ # Returns the {Sass::Engine} for the given file.
202
+ # This is preferable to Sass::Engine.new when reading from a file
203
+ # because it properly sets up the Engine's metadata,
204
+ # enables parse-tree caching,
205
+ # and infers the syntax from the filename.
206
+ #
207
+ # @param filename [String] The path to the Sass or SCSS file
208
+ # @param options [{Symbol => Object}] The options hash;
209
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
210
+ # @return [Sass::Engine] The Engine for the given Sass or SCSS file.
211
+ # @raise [Sass::SyntaxError] if there's an error in the document.
212
+ def self.for_file(filename, options)
213
+ had_syntax = options[:syntax]
214
+
215
+ if had_syntax
216
+ # Use what was explicitly specificed
217
+ elsif filename =~ /\.scss$/
218
+ options.merge!(:syntax => :scss)
219
+ elsif filename =~ /\.sass$/
220
+ options.merge!(:syntax => :sass)
221
+ end
222
+
223
+ Sass::Engine.new(File.read(filename), options.merge(:filename => filename))
224
+ end
225
+
226
+ # The options for the Sass engine.
227
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
228
+ #
229
+ # @return [{Symbol => Object}]
230
+ attr_reader :options
231
+
232
+ # Creates a new Engine. Note that Engine should only be used directly
233
+ # when compiling in-memory Sass code.
234
+ # If you're compiling a single Sass file from the filesystem,
235
+ # use \{Sass::Engine.for\_file}.
236
+ # If you're compiling multiple files from the filesystem,
237
+ # use {Sass::Plugin}.
238
+ #
239
+ # @param template [String] The Sass template.
240
+ # This template can be encoded using any encoding
241
+ # that can be converted to Unicode.
242
+ # If the template contains an `@charset` declaration,
243
+ # that overrides the Ruby encoding
244
+ # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
245
+ # @param options [{Symbol => Object}] An options hash.
246
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
247
+ # @see {Sass::Engine.for_file}
248
+ # @see {Sass::Plugin}
249
+ def initialize(template, options={})
250
+ @options = self.class.normalize_options(options)
251
+ @template = template
252
+ end
253
+
254
+ # Render the template to CSS.
255
+ #
256
+ # @return [String] The CSS
257
+ # @raise [Sass::SyntaxError] if there's an error in the document
258
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
259
+ # cannot be converted to UTF-8
260
+ # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
261
+ def render
262
+ return _render unless @options[:quiet]
263
+ Sass::Util.silence_sass_warnings {_render}
264
+ end
265
+ alias_method :to_css, :render
266
+
267
+ # Parses the document into its parse tree. Memoized.
268
+ #
269
+ # @return [Sass::Tree::Node] The root of the parse tree.
270
+ # @raise [Sass::SyntaxError] if there's an error in the document
271
+ def to_tree
272
+ @tree ||= @options[:quiet] ?
273
+ Sass::Util.silence_sass_warnings {_to_tree} :
274
+ _to_tree
275
+ end
276
+
277
+ # Returns the original encoding of the document,
278
+ # or `nil` under Ruby 1.8.
279
+ #
280
+ # @return [Encoding, nil]
281
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
282
+ # cannot be converted to UTF-8
283
+ # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
284
+ def source_encoding
285
+ check_encoding!
286
+ @original_encoding
287
+ end
288
+
289
+ # Gets a set of all the documents
290
+ # that are (transitive) dependencies of this document,
291
+ # not including the document itself.
292
+ #
293
+ # @return [[Sass::Engine]] The dependency documents.
294
+ def dependencies
295
+ _dependencies(Set.new, engines = Set.new)
296
+ Sass::Util.array_minus(engines, [self])
297
+ end
298
+
299
+ # Helper for \{#dependencies}.
300
+ #
301
+ # @private
302
+ def _dependencies(seen, engines)
303
+ return if seen.include?(key = [@options[:filename], @options[:importer]])
304
+ seen << key
305
+ engines << self
306
+ to_tree.grep(Tree::ImportNode) do |n|
307
+ next if n.css_import?
308
+ n.imported_file._dependencies(seen, engines)
309
+ end
310
+ end
311
+
312
+ private
313
+
314
+ def _render
315
+ rendered = _to_tree.render
316
+ return rendered if ruby1_8?
317
+ begin
318
+ # Try to convert the result to the original encoding,
319
+ # but if that doesn't work fall back on UTF-8
320
+ rendered = rendered.encode(source_encoding)
321
+ rescue EncodingError
322
+ end
323
+ rendered.gsub(Regexp.new('\A@charset "(.*?)"'.encode(source_encoding)),
324
+ "@charset \"#{source_encoding.name}\"".encode(source_encoding))
325
+ end
326
+
327
+ def _to_tree
328
+ if (@options[:cache] || @options[:read_cache]) &&
329
+ @options[:filename] && @options[:importer]
330
+ key = sassc_key
331
+ sha = Digest::SHA1.hexdigest(@template)
332
+
333
+ if root = @options[:cache_store].retrieve(key, sha)
334
+ root.options = @options
335
+ return root
336
+ end
337
+ end
338
+
339
+ check_encoding!
340
+
341
+ if @options[:syntax] == :scss
342
+ root = Sass::SCSS::Parser.new(@template, @options[:filename]).parse
343
+ else
344
+ root = Tree::RootNode.new(@template)
345
+ append_children(root, tree(tabulate(@template)).first, true)
346
+ end
347
+
348
+ root.options = @options
349
+ if @options[:cache] && key && sha
350
+ begin
351
+ old_options = root.options
352
+ root.options = {}
353
+ @options[:cache_store].store(key, sha, root)
354
+ ensure
355
+ root.options = old_options
356
+ end
357
+ end
358
+ root
359
+ rescue SyntaxError => e
360
+ e.modify_backtrace(:filename => @options[:filename], :line => @line)
361
+ e.sass_template = @template
362
+ raise e
363
+ end
364
+
365
+ def sassc_key
366
+ @options[:cache_store].key(*@options[:importer].key(@options[:filename], @options))
367
+ end
368
+
369
+ def check_encoding!
370
+ return if @checked_encoding
371
+ @checked_encoding = true
372
+ @template, @original_encoding = check_sass_encoding(@template) do |msg, line|
373
+ raise Sass::SyntaxError.new(msg, :line => line)
374
+ end
375
+ end
376
+
377
+ def tabulate(string)
378
+ tab_str = nil
379
+ comment_tab_str = nil
380
+ first = true
381
+ lines = []
382
+ string.gsub(/\r\n|\r|\n/, "\n").scan(/^[^\n]*?$/).each_with_index do |line, index|
383
+ index += (@options[:line] || 1)
384
+ if line.strip.empty?
385
+ lines.last.text << "\n" if lines.last && lines.last.comment?
386
+ next
387
+ end
388
+
389
+ line_tab_str = line[/^\s*/]
390
+ unless line_tab_str.empty?
391
+ if tab_str.nil?
392
+ comment_tab_str ||= line_tab_str
393
+ next if try_comment(line, lines.last, "", comment_tab_str, index)
394
+ comment_tab_str = nil
395
+ end
396
+
397
+ tab_str ||= line_tab_str
398
+
399
+ raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
400
+ :line => index) if first
401
+
402
+ raise SyntaxError.new("Indentation can't use both tabs and spaces.",
403
+ :line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
404
+ end
405
+ first &&= !tab_str.nil?
406
+ if tab_str.nil?
407
+ lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
408
+ next
409
+ end
410
+
411
+ comment_tab_str ||= line_tab_str
412
+ if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index)
413
+ next
414
+ else
415
+ comment_tab_str = nil
416
+ end
417
+
418
+ line_tabs = line_tab_str.scan(tab_str).size
419
+ if tab_str * line_tabs != line_tab_str
420
+ message = <<END.strip.gsub("\n", ' ')
421
+ Inconsistent indentation: #{Sass::Shared.human_indentation line_tab_str, true} used for indentation,
422
+ but the rest of the document was indented using #{Sass::Shared.human_indentation tab_str}.
423
+ END
424
+ raise SyntaxError.new(message, :line => index)
425
+ end
426
+
427
+ lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
428
+ end
429
+ lines
430
+ end
431
+
432
+ def try_comment(line, last, tab_str, comment_tab_str, index)
433
+ return unless last && last.comment?
434
+ # Nested comment stuff must be at least one whitespace char deeper
435
+ # than the normal indentation
436
+ return unless line =~ /^#{tab_str}\s/
437
+ unless line =~ /^(?:#{comment_tab_str})(.*)$/
438
+ raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
439
+ Inconsistent indentation:
440
+ previous line was indented by #{Sass::Shared.human_indentation comment_tab_str},
441
+ but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
442
+ MSG
443
+ end
444
+
445
+ last.comment_tab_str ||= comment_tab_str
446
+ last.text << "\n" << line
447
+ true
448
+ end
449
+
450
+ def tree(arr, i = 0)
451
+ return [], i if arr[i].nil?
452
+
453
+ base = arr[i].tabs
454
+ nodes = []
455
+ while (line = arr[i]) && line.tabs >= base
456
+ if line.tabs > base
457
+ raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
458
+ :line => line.index) if line.tabs > base + 1
459
+
460
+ nodes.last.children, i = tree(arr, i)
461
+ else
462
+ nodes << line
463
+ i += 1
464
+ end
465
+ end
466
+ return nodes, i
467
+ end
468
+
469
+ def build_tree(parent, line, root = false)
470
+ @line = line.index
471
+ node_or_nodes = parse_line(parent, line, root)
472
+
473
+ Array(node_or_nodes).each do |node|
474
+ # Node is a symbol if it's non-outputting, like a variable assignment
475
+ next unless node.is_a? Tree::Node
476
+
477
+ node.line = line.index
478
+ node.filename = line.filename
479
+
480
+ append_children(node, line.children, false)
481
+ end
482
+
483
+ node_or_nodes
484
+ end
485
+
486
+ def append_children(parent, children, root)
487
+ continued_rule = nil
488
+ continued_comment = nil
489
+ children.each do |line|
490
+ child = build_tree(parent, line, root)
491
+
492
+ if child.is_a?(Tree::RuleNode)
493
+ if child.continued? && child.children.empty?
494
+ if continued_rule
495
+ continued_rule.add_rules child
496
+ else
497
+ continued_rule = child
498
+ end
499
+ next
500
+ elsif continued_rule
501
+ continued_rule.add_rules child
502
+ continued_rule.children = child.children
503
+ continued_rule, child = nil, continued_rule
504
+ end
505
+ elsif continued_rule
506
+ continued_rule = nil
507
+ end
508
+
509
+ if child.is_a?(Tree::CommentNode) && child.type == :silent
510
+ if continued_comment &&
511
+ child.line == continued_comment.line +
512
+ continued_comment.lines + 1
513
+ continued_comment.value += ["\n"] + child.value
514
+ next
515
+ end
516
+
517
+ continued_comment = child
518
+ end
519
+
520
+ check_for_no_children(child)
521
+ validate_and_append_child(parent, child, line, root)
522
+ end
523
+
524
+ parent
525
+ end
526
+
527
+ def validate_and_append_child(parent, child, line, root)
528
+ case child
529
+ when Array
530
+ child.each {|c| validate_and_append_child(parent, c, line, root)}
531
+ when Tree::Node
532
+ parent << child
533
+ end
534
+ end
535
+
536
+ def check_for_no_children(node)
537
+ return unless node.is_a?(Tree::RuleNode) && node.children.empty?
538
+ Sass::Util.sass_warn(<<WARNING.strip)
539
+ WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
540
+ This selector doesn't have any properties and will not be rendered.
541
+ WARNING
542
+ end
543
+
544
+ def parse_line(parent, line, root)
545
+ case line.text[0]
546
+ when PROPERTY_CHAR
547
+ if line.text[1] == PROPERTY_CHAR ||
548
+ (@options[:property_syntax] == :new &&
549
+ line.text =~ PROPERTY_OLD && $2.empty?)
550
+ # Support CSS3-style pseudo-elements,
551
+ # which begin with ::,
552
+ # as well as pseudo-classes
553
+ # if we're using the new property syntax
554
+ Tree::RuleNode.new(parse_interp(line.text))
555
+ else
556
+ name, value = line.text.scan(PROPERTY_OLD)[0]
557
+ raise SyntaxError.new("Invalid property: \"#{line.text}\".",
558
+ :line => @line) if name.nil? || value.nil?
559
+ parse_property(name, parse_interp(name), value, :old, line)
560
+ end
561
+ when ?$
562
+ parse_variable(line)
563
+ when COMMENT_CHAR
564
+ parse_comment(line)
565
+ when DIRECTIVE_CHAR
566
+ parse_directive(parent, line, root)
567
+ when ESCAPE_CHAR
568
+ Tree::RuleNode.new(parse_interp(line.text[1..-1]))
569
+ when MIXIN_DEFINITION_CHAR
570
+ parse_mixin_definition(line)
571
+ when MIXIN_INCLUDE_CHAR
572
+ if line.text[1].nil? || line.text[1] == ?\s
573
+ Tree::RuleNode.new(parse_interp(line.text))
574
+ else
575
+ parse_mixin_include(line, root)
576
+ end
577
+ else
578
+ parse_property_or_rule(line)
579
+ end
580
+ end
581
+
582
+ def parse_property_or_rule(line)
583
+ scanner = Sass::Util::MultibyteStringScanner.new(line.text)
584
+ hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
585
+ parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
586
+
587
+ unless res = parser.parse_interp_ident
588
+ return Tree::RuleNode.new(parse_interp(line.text))
589
+ end
590
+ res.unshift(hack_char) if hack_char
591
+ if comment = scanner.scan(Sass::SCSS::RX::COMMENT)
592
+ res << comment
593
+ end
594
+
595
+ name = line.text[0...scanner.pos]
596
+ if scanner.scan(/\s*:(?:\s|$)/)
597
+ parse_property(name, res, scanner.rest, :new, line)
598
+ else
599
+ res.pop if comment
600
+ Tree::RuleNode.new(res + parse_interp(scanner.rest))
601
+ end
602
+ end
603
+
604
+ def parse_property(name, parsed_name, value, prop, line)
605
+ if value.strip.empty?
606
+ expr = Sass::Script::String.new("")
607
+ else
608
+ expr = parse_script(value, :offset => line.offset + line.text.index(value))
609
+ end
610
+ node = Tree::PropNode.new(parse_interp(name), expr, prop)
611
+ if value.strip.empty? && line.children.empty?
612
+ raise SyntaxError.new(
613
+ "Invalid property: \"#{node.declaration}\" (no value)." +
614
+ node.pseudo_class_selector_message)
615
+ end
616
+
617
+ node
618
+ end
619
+
620
+ def parse_variable(line)
621
+ name, value, default = line.text.scan(Script::MATCH)[0]
622
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
623
+ :line => @line + 1) unless line.children.empty?
624
+ raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
625
+ :line => @line) unless name && value
626
+
627
+ expr = parse_script(value, :offset => line.offset + line.text.index(value))
628
+
629
+ Tree::VariableNode.new(name, expr, default)
630
+ end
631
+
632
+ def parse_comment(line)
633
+ if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
634
+ silent = line.text[1] == SASS_COMMENT_CHAR
635
+ loud = !silent && line.text[2] == SASS_LOUD_COMMENT_CHAR
636
+ if silent
637
+ value = [line.text]
638
+ else
639
+ value = self.class.parse_interp(line.text, line.index, line.offset, :filename => @filename)
640
+ end
641
+ value = with_extracted_values(value) do |str|
642
+ str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
643
+ format_comment_text(str, silent)
644
+ end
645
+ type = if silent then :silent elsif loud then :loud else :normal end
646
+ Tree::CommentNode.new(value, type)
647
+ else
648
+ Tree::RuleNode.new(parse_interp(line.text))
649
+ end
650
+ end
651
+
652
+ def parse_directive(parent, line, root)
653
+ directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
654
+ offset = directive.size + whitespace.size + 1 if whitespace
655
+
656
+ # If value begins with url( or ",
657
+ # it's a CSS @import rule and we don't want to touch it.
658
+ case directive
659
+ when 'import'
660
+ parse_import(line, value, offset)
661
+ when 'mixin'
662
+ parse_mixin_definition(line)
663
+ when 'content'
664
+ parse_content_directive(line)
665
+ when 'include'
666
+ parse_mixin_include(line, root)
667
+ when 'function'
668
+ parse_function(line, root)
669
+ when 'for'
670
+ parse_for(line, root, value)
671
+ when 'each'
672
+ parse_each(line, root, value)
673
+ when 'else'
674
+ parse_else(parent, line, value)
675
+ when 'while'
676
+ raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
677
+ Tree::WhileNode.new(parse_script(value, :offset => offset))
678
+ when 'if'
679
+ raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
680
+ Tree::IfNode.new(parse_script(value, :offset => offset))
681
+ when 'debug'
682
+ raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
683
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
684
+ :line => @line + 1) unless line.children.empty?
685
+ offset = line.offset + line.text.index(value).to_i
686
+ Tree::DebugNode.new(parse_script(value, :offset => offset))
687
+ when 'extend'
688
+ raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
689
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
690
+ :line => @line + 1) unless line.children.empty?
691
+ optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
692
+ offset = line.offset + line.text.index(value).to_i
693
+ Tree::ExtendNode.new(parse_interp(value, offset), optional)
694
+ when 'warn'
695
+ raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
696
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
697
+ :line => @line + 1) unless line.children.empty?
698
+ offset = line.offset + line.text.index(value).to_i
699
+ Tree::WarnNode.new(parse_script(value, :offset => offset))
700
+ when 'return'
701
+ raise SyntaxError.new("Invalid @return: expected expression.") unless value
702
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
703
+ :line => @line + 1) unless line.children.empty?
704
+ offset = line.offset + line.text.index(value).to_i
705
+ Tree::ReturnNode.new(parse_script(value, :offset => offset))
706
+ when 'charset'
707
+ name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
708
+ raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
709
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
710
+ :line => @line + 1) unless line.children.empty?
711
+ Tree::CharsetNode.new(name)
712
+ when 'media'
713
+ parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
714
+ Tree::MediaNode.new(parser.parse_media_query_list.to_a)
715
+ when nil
716
+ raise SyntaxError.new("Invalid directive: '@'.")
717
+ else
718
+ unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
719
+ if unprefixed_directive == 'supports'
720
+ parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
721
+ return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
722
+ end
723
+
724
+ Tree::DirectiveNode.new(
725
+ value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
726
+ end
727
+ end
728
+
729
+ def parse_for(line, root, text)
730
+ var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
731
+
732
+ if var.nil? # scan failed, try to figure out why for error message
733
+ if text !~ /^[^\s]+/
734
+ expected = "variable name"
735
+ elsif text !~ /^[^\s]+\s+from\s+.+/
736
+ expected = "'from <expr>'"
737
+ else
738
+ expected = "'to <expr>' or 'through <expr>'"
739
+ end
740
+ raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
741
+ end
742
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
743
+
744
+ var = var[1..-1]
745
+ parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
746
+ parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
747
+ Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
748
+ end
749
+
750
+ def parse_each(line, root, text)
751
+ var, list_expr = text.scan(/^([^\s]+)\s+in\s+(.+)$/).first
752
+
753
+ if var.nil? # scan failed, try to figure out why for error message
754
+ if text !~ /^[^\s]+/
755
+ expected = "variable name"
756
+ elsif text !~ /^[^\s]+\s+from\s+.+/
757
+ expected = "'in <expr>'"
758
+ end
759
+ raise SyntaxError.new("Invalid for directive '@each #{text}': expected #{expected}.")
760
+ end
761
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
762
+
763
+ var = var[1..-1]
764
+ parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
765
+ Tree::EachNode.new(var, parsed_list)
766
+ end
767
+
768
+ def parse_else(parent, line, text)
769
+ previous = parent.children.last
770
+ raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
771
+
772
+ if text
773
+ if text !~ /^if\s+(.+)/
774
+ raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
775
+ end
776
+ expr = parse_script($1, :offset => line.offset + line.text.index($1))
777
+ end
778
+
779
+ node = Tree::IfNode.new(expr)
780
+ append_children(node, line.children, false)
781
+ previous.add_else node
782
+ nil
783
+ end
784
+
785
+ def parse_import(line, value, offset)
786
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
787
+ :line => @line + 1) unless line.children.empty?
788
+
789
+ scanner = Sass::Util::MultibyteStringScanner.new(value)
790
+ values = []
791
+
792
+ loop do
793
+ unless node = parse_import_arg(scanner, offset + scanner.pos)
794
+ raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
795
+ :line => @line)
796
+ end
797
+ values << node
798
+ break unless scanner.scan(/,\s*/)
799
+ end
800
+
801
+ if scanner.scan(/;/)
802
+ raise SyntaxError.new("Invalid @import: expected end of line, was \";\".",
803
+ :line => @line)
804
+ end
805
+
806
+ return values
807
+ end
808
+
809
+ def parse_import_arg(scanner, offset)
810
+ return if scanner.eos?
811
+
812
+ if scanner.match?(/url\(/i)
813
+ script_parser = Sass::Script::Parser.new(scanner, @line, offset, @options)
814
+ str = script_parser.parse_string
815
+ media_parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
816
+ media = media_parser.parse_media_query_list
817
+ return Tree::CssImportNode.new(str, media.to_a)
818
+ end
819
+
820
+ unless str = scanner.scan(Sass::SCSS::RX::STRING)
821
+ return Tree::ImportNode.new(scanner.scan(/[^,;]+/))
822
+ end
823
+
824
+ val = scanner[1] || scanner[2]
825
+ scanner.scan(/\s*/)
826
+ if !scanner.match?(/[,;]|$/)
827
+ media_parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
828
+ media = media_parser.parse_media_query_list
829
+ Tree::CssImportNode.new(str || uri, media.to_a)
830
+ elsif val =~ /^(https?:)?\/\//
831
+ Tree::CssImportNode.new("url(#{val})")
832
+ else
833
+ Tree::ImportNode.new(val)
834
+ end
835
+ end
836
+
837
+ MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
838
+ def parse_mixin_definition(line)
839
+ name, arg_string = line.text.scan(MIXIN_DEF_RE).first
840
+ raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
841
+
842
+ offset = line.offset + line.text.size - arg_string.size
843
+ args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
844
+ parse_mixin_definition_arglist
845
+ Tree::MixinDefNode.new(name, args, splat)
846
+ end
847
+
848
+ CONTENT_RE = /^@content\s*(.+)?$/
849
+ def parse_content_directive(line)
850
+ trailing = line.text.scan(CONTENT_RE).first.first
851
+ raise SyntaxError.new("Invalid content directive. Trailing characters found: \"#{trailing}\".") unless trailing.nil?
852
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
853
+ :line => line.index + 1) unless line.children.empty?
854
+ Tree::ContentNode.new
855
+ end
856
+
857
+ MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
858
+ def parse_mixin_include(line, root)
859
+ name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
860
+ raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
861
+
862
+ offset = line.offset + line.text.size - arg_string.size
863
+ args, keywords, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
864
+ parse_mixin_include_arglist
865
+ Tree::MixinNode.new(name, args, keywords, splat)
866
+ end
867
+
868
+ FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
869
+ def parse_function(line, root)
870
+ name, arg_string = line.text.scan(FUNCTION_RE).first
871
+ raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
872
+
873
+ offset = line.offset + line.text.size - arg_string.size
874
+ args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
875
+ parse_function_definition_arglist
876
+ Tree::FunctionNode.new(name, args, splat)
877
+ end
878
+
879
+ def parse_script(script, options = {})
880
+ line = options[:line] || @line
881
+ offset = options[:offset] || 0
882
+ Script.parse(script, line, offset, @options)
883
+ end
884
+
885
+ def format_comment_text(text, silent)
886
+ content = text.split("\n")
887
+
888
+ if content.first && content.first.strip.empty?
889
+ removed_first = true
890
+ content.shift
891
+ end
892
+
893
+ return silent ? "//" : "/* */" if content.empty?
894
+ content.last.gsub!(%r{ ?\*/ *$}, '')
895
+ content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
896
+ content.first.gsub!(/^ /, '') unless removed_first
897
+ if silent
898
+ "//" + content.join("\n//")
899
+ else
900
+ # The #gsub fixes the case of a trailing */
901
+ "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
902
+ end
903
+ end
904
+
905
+ def parse_interp(text, offset = 0)
906
+ self.class.parse_interp(text, @line, offset, :filename => @filename)
907
+ end
908
+
909
+ # It's important that this have strings (at least)
910
+ # at the beginning, the end, and between each Script::Node.
911
+ #
912
+ # @private
913
+ def self.parse_interp(text, line, offset, options)
914
+ res = []
915
+ rest = Sass::Shared.handle_interpolation text do |scan|
916
+ escapes = scan[2].size
917
+ res << scan.matched[0...-2 - escapes]
918
+ if escapes % 2 == 1
919
+ res << "\\" * (escapes - 1) << '#{'
920
+ else
921
+ res << "\\" * [0, escapes - 1].max
922
+ res << Script::Parser.new(
923
+ scan, line, offset + scan.pos - scan.matched_size, options).
924
+ parse_interpolated
925
+ end
926
+ end
927
+ res << rest
928
+ end
929
+ end
930
+ end