aliddle-sass 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 (238) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +201 -0
  5. data/Rakefile +347 -0
  6. data/VERSION +1 -0
  7. data/VERSION_NAME +1 -0
  8. data/bin/sass +9 -0
  9. data/bin/sass-convert +8 -0
  10. data/bin/scss +9 -0
  11. data/extra/update_watch.rb +13 -0
  12. data/init.rb +18 -0
  13. data/lib/sass.rb +95 -0
  14. data/lib/sass/cache_stores.rb +15 -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 +60 -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/callbacks.rb +66 -0
  21. data/lib/sass/css.rb +409 -0
  22. data/lib/sass/engine.rb +928 -0
  23. data/lib/sass/environment.rb +101 -0
  24. data/lib/sass/error.rb +201 -0
  25. data/lib/sass/exec.rb +707 -0
  26. data/lib/sass/importers.rb +22 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +190 -0
  29. data/lib/sass/logger.rb +15 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin.rb +132 -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 +183 -0
  41. data/lib/sass/railtie.rb +9 -0
  42. data/lib/sass/repl.rb +57 -0
  43. data/lib/sass/root.rb +7 -0
  44. data/lib/sass/script.rb +39 -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 +237 -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 +348 -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 +495 -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/scss.rb +16 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1179 -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/selector.rb +452 -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 +212 -0
  78. data/lib/sass/shared.rb +76 -0
  79. data/lib/sass/supports.rb +229 -0
  80. data/lib/sass/tree/charset_node.rb +22 -0
  81. data/lib/sass/tree/comment_node.rb +82 -0
  82. data/lib/sass/tree/content_node.rb +9 -0
  83. data/lib/sass/tree/css_import_node.rb +60 -0
  84. data/lib/sass/tree/debug_node.rb +18 -0
  85. data/lib/sass/tree/directive_node.rb +42 -0
  86. data/lib/sass/tree/each_node.rb +24 -0
  87. data/lib/sass/tree/extend_node.rb +36 -0
  88. data/lib/sass/tree/for_node.rb +36 -0
  89. data/lib/sass/tree/function_node.rb +34 -0
  90. data/lib/sass/tree/if_node.rb +52 -0
  91. data/lib/sass/tree/import_node.rb +75 -0
  92. data/lib/sass/tree/media_node.rb +58 -0
  93. data/lib/sass/tree/mixin_def_node.rb +38 -0
  94. data/lib/sass/tree/mixin_node.rb +39 -0
  95. data/lib/sass/tree/node.rb +196 -0
  96. data/lib/sass/tree/prop_node.rb +152 -0
  97. data/lib/sass/tree/return_node.rb +18 -0
  98. data/lib/sass/tree/root_node.rb +28 -0
  99. data/lib/sass/tree/rule_node.rb +132 -0
  100. data/lib/sass/tree/supports_node.rb +51 -0
  101. data/lib/sass/tree/trace_node.rb +32 -0
  102. data/lib/sass/tree/variable_node.rb +30 -0
  103. data/lib/sass/tree/visitors/base.rb +75 -0
  104. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  105. data/lib/sass/tree/visitors/convert.rb +316 -0
  106. data/lib/sass/tree/visitors/cssize.rb +229 -0
  107. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  108. data/lib/sass/tree/visitors/extend.rb +68 -0
  109. data/lib/sass/tree/visitors/perform.rb +446 -0
  110. data/lib/sass/tree/visitors/set_options.rb +125 -0
  111. data/lib/sass/tree/visitors/to_css.rb +230 -0
  112. data/lib/sass/tree/warn_node.rb +18 -0
  113. data/lib/sass/tree/while_node.rb +18 -0
  114. data/lib/sass/util.rb +906 -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/version.rb +126 -0
  119. data/rails/init.rb +1 -0
  120. data/test/Gemfile +3 -0
  121. data/test/Gemfile.lock +10 -0
  122. data/test/sass/cache_test.rb +89 -0
  123. data/test/sass/callbacks_test.rb +61 -0
  124. data/test/sass/conversion_test.rb +1760 -0
  125. data/test/sass/css2sass_test.rb +439 -0
  126. data/test/sass/data/hsl-rgb.txt +319 -0
  127. data/test/sass/engine_test.rb +3243 -0
  128. data/test/sass/exec_test.rb +86 -0
  129. data/test/sass/extend_test.rb +1461 -0
  130. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  131. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  132. data/test/sass/functions_test.rb +1139 -0
  133. data/test/sass/importer_test.rb +192 -0
  134. data/test/sass/logger_test.rb +58 -0
  135. data/test/sass/mock_importer.rb +49 -0
  136. data/test/sass/more_results/more1.css +9 -0
  137. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  138. data/test/sass/more_results/more_import.css +29 -0
  139. data/test/sass/more_templates/_more_partial.sass +2 -0
  140. data/test/sass/more_templates/more1.sass +23 -0
  141. data/test/sass/more_templates/more_import.sass +11 -0
  142. data/test/sass/plugin_test.rb +550 -0
  143. data/test/sass/results/alt.css +4 -0
  144. data/test/sass/results/basic.css +9 -0
  145. data/test/sass/results/cached_import_option.css +3 -0
  146. data/test/sass/results/compact.css +5 -0
  147. data/test/sass/results/complex.css +86 -0
  148. data/test/sass/results/compressed.css +1 -0
  149. data/test/sass/results/expanded.css +19 -0
  150. data/test/sass/results/filename_fn.css +3 -0
  151. data/test/sass/results/if.css +3 -0
  152. data/test/sass/results/import.css +31 -0
  153. data/test/sass/results/import_charset.css +5 -0
  154. data/test/sass/results/import_charset_1_8.css +5 -0
  155. data/test/sass/results/import_charset_ibm866.css +5 -0
  156. data/test/sass/results/import_content.css +1 -0
  157. data/test/sass/results/line_numbers.css +49 -0
  158. data/test/sass/results/mixins.css +95 -0
  159. data/test/sass/results/multiline.css +24 -0
  160. data/test/sass/results/nested.css +22 -0
  161. data/test/sass/results/options.css +1 -0
  162. data/test/sass/results/parent_ref.css +13 -0
  163. data/test/sass/results/script.css +16 -0
  164. data/test/sass/results/scss_import.css +31 -0
  165. data/test/sass/results/scss_importee.css +2 -0
  166. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  167. data/test/sass/results/subdir/subdir.css +3 -0
  168. data/test/sass/results/units.css +11 -0
  169. data/test/sass/results/warn.css +0 -0
  170. data/test/sass/results/warn_imported.css +0 -0
  171. data/test/sass/script_conversion_test.rb +299 -0
  172. data/test/sass/script_test.rb +591 -0
  173. data/test/sass/scss/css_test.rb +1093 -0
  174. data/test/sass/scss/rx_test.rb +156 -0
  175. data/test/sass/scss/scss_test.rb +2043 -0
  176. data/test/sass/scss/test_helper.rb +37 -0
  177. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  178. data/test/sass/templates/_double_import_loop2.sass +1 -0
  179. data/test/sass/templates/_filename_fn_import.scss +11 -0
  180. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  181. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  182. data/test/sass/templates/_imported_content.sass +3 -0
  183. data/test/sass/templates/_partial.sass +2 -0
  184. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  185. data/test/sass/templates/alt.sass +16 -0
  186. data/test/sass/templates/basic.sass +23 -0
  187. data/test/sass/templates/bork1.sass +2 -0
  188. data/test/sass/templates/bork2.sass +2 -0
  189. data/test/sass/templates/bork3.sass +2 -0
  190. data/test/sass/templates/bork4.sass +2 -0
  191. data/test/sass/templates/bork5.sass +3 -0
  192. data/test/sass/templates/cached_import_option.scss +3 -0
  193. data/test/sass/templates/compact.sass +17 -0
  194. data/test/sass/templates/complex.sass +305 -0
  195. data/test/sass/templates/compressed.sass +15 -0
  196. data/test/sass/templates/double_import_loop1.sass +1 -0
  197. data/test/sass/templates/expanded.sass +17 -0
  198. data/test/sass/templates/filename_fn.scss +18 -0
  199. data/test/sass/templates/if.sass +11 -0
  200. data/test/sass/templates/import.sass +12 -0
  201. data/test/sass/templates/import_charset.sass +9 -0
  202. data/test/sass/templates/import_charset_1_8.sass +6 -0
  203. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  204. data/test/sass/templates/import_content.sass +4 -0
  205. data/test/sass/templates/importee.less +2 -0
  206. data/test/sass/templates/importee.sass +19 -0
  207. data/test/sass/templates/line_numbers.sass +13 -0
  208. data/test/sass/templates/mixin_bork.sass +5 -0
  209. data/test/sass/templates/mixins.sass +76 -0
  210. data/test/sass/templates/multiline.sass +20 -0
  211. data/test/sass/templates/nested.sass +25 -0
  212. data/test/sass/templates/nested_bork1.sass +2 -0
  213. data/test/sass/templates/nested_bork2.sass +2 -0
  214. data/test/sass/templates/nested_bork3.sass +2 -0
  215. data/test/sass/templates/nested_bork4.sass +2 -0
  216. data/test/sass/templates/nested_import.sass +2 -0
  217. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  218. data/test/sass/templates/options.sass +2 -0
  219. data/test/sass/templates/parent_ref.sass +25 -0
  220. data/test/sass/templates/same_name_different_ext.sass +2 -0
  221. data/test/sass/templates/same_name_different_ext.scss +1 -0
  222. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  223. data/test/sass/templates/script.sass +101 -0
  224. data/test/sass/templates/scss_import.scss +11 -0
  225. data/test/sass/templates/scss_importee.scss +1 -0
  226. data/test/sass/templates/single_import_loop.sass +1 -0
  227. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  228. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  229. data/test/sass/templates/subdir/subdir.sass +6 -0
  230. data/test/sass/templates/units.sass +11 -0
  231. data/test/sass/templates/warn.sass +3 -0
  232. data/test/sass/templates/warn_imported.sass +4 -0
  233. data/test/sass/test_helper.rb +8 -0
  234. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  235. data/test/sass/util/subset_map_test.rb +91 -0
  236. data/test/sass/util_test.rb +313 -0
  237. data/test/test_helper.rb +80 -0
  238. metadata +348 -0
@@ -0,0 +1,928 @@
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
+ else
716
+ unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
717
+ if unprefixed_directive == 'supports'
718
+ parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
719
+ return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
720
+ end
721
+
722
+ Tree::DirectiveNode.new(
723
+ value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
724
+ end
725
+ end
726
+
727
+ def parse_for(line, root, text)
728
+ var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
729
+
730
+ if var.nil? # scan failed, try to figure out why for error message
731
+ if text !~ /^[^\s]+/
732
+ expected = "variable name"
733
+ elsif text !~ /^[^\s]+\s+from\s+.+/
734
+ expected = "'from <expr>'"
735
+ else
736
+ expected = "'to <expr>' or 'through <expr>'"
737
+ end
738
+ raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
739
+ end
740
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
741
+
742
+ var = var[1..-1]
743
+ parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
744
+ parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
745
+ Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
746
+ end
747
+
748
+ def parse_each(line, root, text)
749
+ var, list_expr = text.scan(/^([^\s]+)\s+in\s+(.+)$/).first
750
+
751
+ if var.nil? # scan failed, try to figure out why for error message
752
+ if text !~ /^[^\s]+/
753
+ expected = "variable name"
754
+ elsif text !~ /^[^\s]+\s+from\s+.+/
755
+ expected = "'in <expr>'"
756
+ end
757
+ raise SyntaxError.new("Invalid for directive '@each #{text}': expected #{expected}.")
758
+ end
759
+ raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
760
+
761
+ var = var[1..-1]
762
+ parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
763
+ Tree::EachNode.new(var, parsed_list)
764
+ end
765
+
766
+ def parse_else(parent, line, text)
767
+ previous = parent.children.last
768
+ raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
769
+
770
+ if text
771
+ if text !~ /^if\s+(.+)/
772
+ raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
773
+ end
774
+ expr = parse_script($1, :offset => line.offset + line.text.index($1))
775
+ end
776
+
777
+ node = Tree::IfNode.new(expr)
778
+ append_children(node, line.children, false)
779
+ previous.add_else node
780
+ nil
781
+ end
782
+
783
+ def parse_import(line, value, offset)
784
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
785
+ :line => @line + 1) unless line.children.empty?
786
+
787
+ scanner = Sass::Util::MultibyteStringScanner.new(value)
788
+ values = []
789
+
790
+ loop do
791
+ unless node = parse_import_arg(scanner, offset + scanner.pos)
792
+ raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
793
+ :line => @line)
794
+ end
795
+ values << node
796
+ break unless scanner.scan(/,\s*/)
797
+ end
798
+
799
+ if scanner.scan(/;/)
800
+ raise SyntaxError.new("Invalid @import: expected end of line, was \";\".",
801
+ :line => @line)
802
+ end
803
+
804
+ return values
805
+ end
806
+
807
+ def parse_import_arg(scanner, offset)
808
+ return if scanner.eos?
809
+
810
+ if scanner.match?(/url\(/i)
811
+ script_parser = Sass::Script::Parser.new(scanner, @line, offset, @options)
812
+ str = script_parser.parse_string
813
+ media_parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
814
+ media = media_parser.parse_media_query_list
815
+ return Tree::CssImportNode.new(str, media.to_a)
816
+ end
817
+
818
+ unless str = scanner.scan(Sass::SCSS::RX::STRING)
819
+ return Tree::ImportNode.new(scanner.scan(/[^,;]+/))
820
+ end
821
+
822
+ val = scanner[1] || scanner[2]
823
+ scanner.scan(/\s*/)
824
+ if !scanner.match?(/[,;]|$/)
825
+ media_parser = Sass::SCSS::Parser.new(scanner, @options[:filename], @line)
826
+ media = media_parser.parse_media_query_list
827
+ Tree::CssImportNode.new(str || uri, media.to_a)
828
+ elsif val =~ /^(https?:)?\/\//
829
+ Tree::CssImportNode.new("url(#{val})")
830
+ else
831
+ Tree::ImportNode.new(val)
832
+ end
833
+ end
834
+
835
+ MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
836
+ def parse_mixin_definition(line)
837
+ name, arg_string = line.text.scan(MIXIN_DEF_RE).first
838
+ raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
839
+
840
+ offset = line.offset + line.text.size - arg_string.size
841
+ args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
842
+ parse_mixin_definition_arglist
843
+ Tree::MixinDefNode.new(name, args, splat)
844
+ end
845
+
846
+ CONTENT_RE = /^@content\s*(.+)?$/
847
+ def parse_content_directive(line)
848
+ trailing = line.text.scan(CONTENT_RE).first.first
849
+ raise SyntaxError.new("Invalid content directive. Trailing characters found: \"#{trailing}\".") unless trailing.nil?
850
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
851
+ :line => line.index + 1) unless line.children.empty?
852
+ Tree::ContentNode.new
853
+ end
854
+
855
+ MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
856
+ def parse_mixin_include(line, root)
857
+ name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
858
+ raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
859
+
860
+ offset = line.offset + line.text.size - arg_string.size
861
+ args, keywords, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
862
+ parse_mixin_include_arglist
863
+ Tree::MixinNode.new(name, args, keywords, splat)
864
+ end
865
+
866
+ FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
867
+ def parse_function(line, root)
868
+ name, arg_string = line.text.scan(FUNCTION_RE).first
869
+ raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
870
+
871
+ offset = line.offset + line.text.size - arg_string.size
872
+ args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
873
+ parse_function_definition_arglist
874
+ Tree::FunctionNode.new(name, args, splat)
875
+ end
876
+
877
+ def parse_script(script, options = {})
878
+ line = options[:line] || @line
879
+ offset = options[:offset] || 0
880
+ Script.parse(script, line, offset, @options)
881
+ end
882
+
883
+ def format_comment_text(text, silent)
884
+ content = text.split("\n")
885
+
886
+ if content.first && content.first.strip.empty?
887
+ removed_first = true
888
+ content.shift
889
+ end
890
+
891
+ return silent ? "//" : "/* */" if content.empty?
892
+ content.last.gsub!(%r{ ?\*/ *$}, '')
893
+ content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
894
+ content.first.gsub!(/^ /, '') unless removed_first
895
+ if silent
896
+ "//" + content.join("\n//")
897
+ else
898
+ # The #gsub fixes the case of a trailing */
899
+ "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
900
+ end
901
+ end
902
+
903
+ def parse_interp(text, offset = 0)
904
+ self.class.parse_interp(text, @line, offset, :filename => @filename)
905
+ end
906
+
907
+ # It's important that this have strings (at least)
908
+ # at the beginning, the end, and between each Script::Node.
909
+ #
910
+ # @private
911
+ def self.parse_interp(text, line, offset, options)
912
+ res = []
913
+ rest = Sass::Shared.handle_interpolation text do |scan|
914
+ escapes = scan[2].size
915
+ res << scan.matched[0...-2 - escapes]
916
+ if escapes % 2 == 1
917
+ res << "\\" * (escapes - 1) << '#{'
918
+ else
919
+ res << "\\" * [0, escapes - 1].max
920
+ res << Script::Parser.new(
921
+ scan, line, offset + scan.pos - scan.matched_size, options).
922
+ parse_interpolated
923
+ end
924
+ end
925
+ res << rest
926
+ end
927
+ end
928
+ end