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
data/lib/sass/media.rb ADDED
@@ -0,0 +1,213 @@
1
+ # A namespace for the `@media` query parse tree.
2
+ module Sass::Media
3
+ # A comma-separated list of queries.
4
+ #
5
+ # media_query [ ',' S* media_query ]*
6
+ class QueryList
7
+ # The queries contained in this list.
8
+ #
9
+ # @return [Array<Query>]
10
+ attr_accessor :queries
11
+
12
+ # @param queries [Array<Query>] See \{#queries}
13
+ def initialize(queries)
14
+ @queries = queries
15
+ end
16
+
17
+ # Merges this query list with another. The returned query list
18
+ # queries for the intersection between the two inputs.
19
+ #
20
+ # Both query lists should be resolved.
21
+ #
22
+ # @param other [QueryList]
23
+ # @return [QueryList?] The merged list, or nil if there is no intersection.
24
+ def merge(other)
25
+ new_queries = queries.map {|q1| other.queries.map {|q2| q1.merge(q2)}}.flatten.compact
26
+ return if new_queries.empty?
27
+ QueryList.new(new_queries)
28
+ end
29
+
30
+ # Returns the CSS for the media query list.
31
+ #
32
+ # @return [String]
33
+ def to_css
34
+ queries.map {|q| q.to_css}.join(', ')
35
+ end
36
+
37
+ # Returns the Sass/SCSS code for the media query list.
38
+ #
39
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
40
+ # @return [String]
41
+ def to_src(options)
42
+ queries.map {|q| q.to_src(options)}.join(', ')
43
+ end
44
+
45
+ # Returns a representation of the query as an array of strings and
46
+ # potentially {Sass::Script::Node}s (if there's interpolation in it). When
47
+ # the interpolation is resolved and the strings are joined together, this
48
+ # will be the string representation of this query.
49
+ #
50
+ # @return [Array<String, Sass::Script::Node>]
51
+ def to_a
52
+ Sass::Util.intersperse(queries.map {|q| q.to_a}, ', ').flatten
53
+ end
54
+
55
+ # Returns a deep copy of this query list and all its children.
56
+ #
57
+ # @return [QueryList]
58
+ def deep_copy
59
+ QueryList.new(queries.map {|q| q.deep_copy})
60
+ end
61
+ end
62
+
63
+ # A single media query.
64
+ #
65
+ # [ [ONLY | NOT]? S* media_type S* | expression ] [ AND S* expression ]*
66
+ class Query
67
+ # The modifier for the query.
68
+ #
69
+ # When parsed as Sass code, this contains strings and SassScript nodes. When
70
+ # parsed as CSS, it contains a single string (accessible via
71
+ # \{#resolved_modifier}).
72
+ #
73
+ # @return [Array<String, Sass::Script::Node>]
74
+ attr_accessor :modifier
75
+
76
+ # The type of the query (e.g. `"screen"` or `"print"`).
77
+ #
78
+ # When parsed as Sass code, this contains strings and SassScript nodes. When
79
+ # parsed as CSS, it contains a single string (accessible via
80
+ # \{#resolved_type}).
81
+ #
82
+ # @return [Array<String, Sass::Script::Node>]
83
+ attr_accessor :type
84
+
85
+ # The trailing expressions in the query.
86
+ #
87
+ # When parsed as Sass code, each expression contains strings and SassScript
88
+ # nodes. When parsed as CSS, each one contains a single string.
89
+ #
90
+ # @return [Array<Array<String, Sass::Script::Node>>]
91
+ attr_accessor :expressions
92
+
93
+ # @param modifier [Array<String, Sass::Script::Node>] See \{#modifier}
94
+ # @param type [Array<String, Sass::Script::Node>] See \{#type}
95
+ # @param expressions [Array<Array<String, Sass::Script::Node>>] See \{#expressions}
96
+ def initialize(modifier, type, expressions)
97
+ @modifier = modifier
98
+ @type = type
99
+ @expressions = expressions
100
+ end
101
+
102
+ # See \{#modifier}.
103
+ # @return [String]
104
+ def resolved_modifier
105
+ # modifier should contain only a single string
106
+ modifier.first || ''
107
+ end
108
+
109
+ # See \{#type}.
110
+ # @return [String]
111
+ def resolved_type
112
+ # type should contain only a single string
113
+ type.first || ''
114
+ end
115
+
116
+ # Merges this query with another. The returned query queries for
117
+ # the intersection between the two inputs.
118
+ #
119
+ # Both queries should be resolved.
120
+ #
121
+ # @param other [Query]
122
+ # @return [Query?] The merged query, or nil if there is no intersection.
123
+ def merge(other)
124
+ m1, t1 = resolved_modifier.downcase, resolved_type.downcase
125
+ m2, t2 = other.resolved_modifier.downcase, other.resolved_type.downcase
126
+ t1 = t2 if t1.empty?
127
+ t2 = t1 if t2.empty?
128
+ if ((m1 == 'not') ^ (m2 == 'not'))
129
+ return if t1 == t2
130
+ type = m1 == 'not' ? t2 : t1
131
+ mod = m1 == 'not' ? m2 : m1
132
+ elsif m1 == 'not' && m2 == 'not'
133
+ # CSS has no way of representing "neither screen nor print"
134
+ return unless t1 == t2
135
+ type = t1
136
+ mod = 'not'
137
+ elsif t1 != t2
138
+ return
139
+ else # t1 == t2, neither m1 nor m2 are "not"
140
+ type = t1
141
+ mod = m1.empty? ? m2 : m1
142
+ end
143
+ return Query.new([mod], [type], other.expressions + expressions)
144
+ end
145
+
146
+ # Returns the CSS for the media query.
147
+ #
148
+ # @return [String]
149
+ def to_css
150
+ css = ''
151
+ css << resolved_modifier
152
+ css << ' ' unless resolved_modifier.empty?
153
+ css << resolved_type
154
+ css << ' and ' unless resolved_type.empty? || expressions.empty?
155
+ css << expressions.map do |e|
156
+ # It's possible for there to be script nodes in Expressions even when
157
+ # we're converting to CSS in the case where we parsed the document as
158
+ # CSS originally (as in css_test.rb).
159
+ e.map {|c| c.is_a?(Sass::Script::Node) ? c.to_sass : c.to_s}.join
160
+ end.join(' and ')
161
+ css
162
+ end
163
+
164
+ # Returns the Sass/SCSS code for the media query.
165
+ #
166
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
167
+ # @return [String]
168
+ def to_src(options)
169
+ src = ''
170
+ src << Sass::Media._interp_to_src(modifier, options)
171
+ src << ' ' unless modifier.empty?
172
+ src << Sass::Media._interp_to_src(type, options)
173
+ src << ' and ' unless type.empty? || expressions.empty?
174
+ src << expressions.map do |e|
175
+ Sass::Media._interp_to_src(e, options)
176
+ end.join(' and ')
177
+ src
178
+ end
179
+
180
+ # @see \{MediaQuery#to\_a}
181
+ def to_a
182
+ res = []
183
+ res += modifier
184
+ res << ' ' unless modifier.empty?
185
+ res += type
186
+ res << ' and ' unless type.empty? || expressions.empty?
187
+ res += Sass::Util.intersperse(expressions, ' and ').flatten
188
+ res
189
+ end
190
+
191
+ # Returns a deep copy of this query and all its children.
192
+ #
193
+ # @return [Query]
194
+ def deep_copy
195
+ Query.new(
196
+ modifier.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
197
+ type.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
198
+ expressions.map {|e| e.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c}})
199
+ end
200
+ end
201
+
202
+ # Converts an interpolation array to source.
203
+ #
204
+ # @param [Array<String, Sass::Script::Node>] The interpolation array to convert.
205
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
206
+ # @return [String]
207
+ def self._interp_to_src(interp, options)
208
+ interp.map do |r|
209
+ next r if r.is_a?(String)
210
+ "\#{#{r.to_sass(options)}}"
211
+ end.join
212
+ end
213
+ end
@@ -0,0 +1,132 @@
1
+ require 'fileutils'
2
+
3
+ require 'sass'
4
+ require 'sass/plugin/compiler'
5
+
6
+ module Sass
7
+ # This module provides a single interface to the compilation of Sass/SCSS files
8
+ # for an application. It provides global options and checks whether CSS files
9
+ # need to be updated.
10
+ #
11
+ # This module is used as the primary interface with Sass
12
+ # when it's used as a plugin for various frameworks.
13
+ # All Rack-enabled frameworks are supported out of the box.
14
+ # The plugin is {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
15
+ # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}.
16
+ #
17
+ # This module has a large set of callbacks available
18
+ # to allow users to run code (such as logging) when certain things happen.
19
+ # All callback methods are of the form `on_#{name}`,
20
+ # and they all take a block that's called when the given action occurs.
21
+ #
22
+ # Note that this class proxies almost all methods to its {Sass::Plugin::Compiler} instance.
23
+ # See \{#compiler}.
24
+ #
25
+ # @example Using a callback
26
+ # Sass::Plugin.on_updating_stylesheet do |template, css|
27
+ # puts "Compiling #{template} to #{css}"
28
+ # end
29
+ # Sass::Plugin.update_stylesheets
30
+ # #=> Compiling app/sass/screen.scss to public/stylesheets/screen.css
31
+ # #=> Compiling app/sass/print.scss to public/stylesheets/print.css
32
+ # #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css
33
+ # @see Sass::Plugin::Compiler
34
+ module Plugin
35
+ include Sass::Util
36
+ extend self
37
+
38
+ @checked_for_updates = false
39
+
40
+ # Whether or not Sass has **ever** checked if the stylesheets need to be updated
41
+ # (in this Ruby instance).
42
+ #
43
+ # @return [Boolean]
44
+ attr_accessor :checked_for_updates
45
+
46
+ # Same as \{#update\_stylesheets}, but respects \{#checked\_for\_updates}
47
+ # and the {file:SASS_REFERENCE.md#always_update-option `:always_update`}
48
+ # and {file:SASS_REFERENCE.md#always_check-option `:always_check`} options.
49
+ #
50
+ # @see #update_stylesheets
51
+ def check_for_updates
52
+ return unless !Sass::Plugin.checked_for_updates ||
53
+ Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
54
+ update_stylesheets
55
+ end
56
+
57
+ # Returns the singleton compiler instance.
58
+ # This compiler has been pre-configured according
59
+ # to the plugin configuration.
60
+ #
61
+ # @return [Sass::Plugin::Compiler]
62
+ def compiler
63
+ @compiler ||= Compiler.new
64
+ end
65
+
66
+ # Updates out-of-date stylesheets.
67
+ #
68
+ # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
69
+ # to see if it's been modified more recently than the corresponding CSS file
70
+ # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
71
+ # If it has, it updates the CSS file.
72
+ #
73
+ # @param individual_files [Array<(String, String)>]
74
+ # A list of files to check for updates
75
+ # **in addition to those specified by the
76
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
77
+ # The first string in each pair is the location of the Sass/SCSS file,
78
+ # the second is the location of the CSS file that it should be compiled to.
79
+ def update_stylesheets(individual_files = [])
80
+ return if options[:never_update]
81
+ compiler.update_stylesheets(individual_files)
82
+ end
83
+
84
+ # Updates all stylesheets, even those that aren't out-of-date.
85
+ # Ignores the cache.
86
+ #
87
+ # @param individual_files [Array<(String, String)>]
88
+ # A list of files to check for updates
89
+ # **in addition to those specified by the
90
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
91
+ # The first string in each pair is the location of the Sass/SCSS file,
92
+ # the second is the location of the CSS file that it should be compiled to.
93
+ # @see #update_stylesheets
94
+ def force_update_stylesheets(individual_files = [])
95
+ Compiler.new(options.dup.merge(
96
+ :never_update => false,
97
+ :always_update => true,
98
+ :cache => false)).update_stylesheets(individual_files)
99
+ end
100
+
101
+ # All other method invocations are proxied to the \{#compiler}.
102
+ #
103
+ # @see #compiler
104
+ # @see Sass::Plugin::Compiler
105
+ def method_missing(method, *args, &block)
106
+ if compiler.respond_to?(method)
107
+ compiler.send(method, *args, &block)
108
+ else
109
+ super
110
+ end
111
+ end
112
+
113
+ # For parity with method_missing
114
+ def respond_to?(method)
115
+ super || compiler.respond_to?(method)
116
+ end
117
+
118
+ # There's a small speedup by not using method missing for frequently delegated methods.
119
+ def options
120
+ compiler.options
121
+ end
122
+
123
+ end
124
+ end
125
+
126
+ if defined?(ActionController)
127
+ require 'sass/plugin/rails'
128
+ elsif defined?(Merb::Plugins)
129
+ require 'sass/plugin/merb'
130
+ else
131
+ require 'sass/plugin/generic'
132
+ end
@@ -0,0 +1,406 @@
1
+ require 'fileutils'
2
+
3
+ require 'sass'
4
+ # XXX CE: is this still necessary now that we have the compiler class?
5
+ require 'sass/callbacks'
6
+ require 'sass/plugin/configuration'
7
+ require 'sass/plugin/staleness_checker'
8
+
9
+ module Sass::Plugin
10
+
11
+ # The Compiler class handles compilation of multiple files and/or directories,
12
+ # including checking which CSS files are out-of-date and need to be updated
13
+ # and calling Sass to perform the compilation on those files.
14
+ #
15
+ # {Sass::Plugin} uses this class to update stylesheets for a single application.
16
+ # Unlike {Sass::Plugin}, though, the Compiler class has no global state,
17
+ # and so multiple instances may be created and used independently.
18
+ #
19
+ # If you need to compile a Sass string into CSS,
20
+ # please see the {Sass::Engine} class.
21
+ #
22
+ # Unlike {Sass::Plugin}, this class doesn't keep track of
23
+ # whether or how many times a stylesheet should be updated.
24
+ # Therefore, the following `Sass::Plugin` options are ignored by the Compiler:
25
+ #
26
+ # * `:never_update`
27
+ # * `:always_check`
28
+ class Compiler
29
+ include Sass::Util
30
+ include Configuration
31
+ extend Sass::Callbacks
32
+
33
+ # Creates a new compiler.
34
+ #
35
+ # @param options [{Symbol => Object}]
36
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
37
+ def initialize(options = {})
38
+ self.options.merge!(options)
39
+ end
40
+
41
+ # Register a callback to be run after stylesheets are mass-updated.
42
+ # This is run whenever \{#update\_stylesheets} is called,
43
+ # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
44
+ # is enabled.
45
+ #
46
+ # @yield [individual_files]
47
+ # @yieldparam individual_files [<(String, String)>]
48
+ # Individual files to be updated, in addition to the directories
49
+ # specified in the options.
50
+ # The first element of each pair is the source file,
51
+ # the second is the target CSS file.
52
+ define_callback :updating_stylesheets
53
+
54
+ # Register a callback to be run after a single stylesheet is updated.
55
+ # The callback is only run if the stylesheet is really updated;
56
+ # if the CSS file is fresh, this won't be run.
57
+ #
58
+ # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
59
+ # is enabled, this callback won't be run
60
+ # when an exception CSS file is being written.
61
+ # To run an action for those files, use \{#on\_compilation\_error}.
62
+ #
63
+ # @yield [template, css]
64
+ # @yieldparam template [String]
65
+ # The location of the Sass/SCSS file being updated.
66
+ # @yieldparam css [String]
67
+ # The location of the CSS file being generated.
68
+ define_callback :updated_stylesheet
69
+
70
+ # Register a callback to be run before a single stylesheet is updated.
71
+ # The callback is only run if the stylesheet is guaranteed to be updated;
72
+ # if the CSS file is fresh, this won't be run.
73
+ #
74
+ # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
75
+ # is enabled, this callback won't be run
76
+ # when an exception CSS file is being written.
77
+ # To run an action for those files, use \{#on\_compilation\_error}.
78
+ #
79
+ # @yield [template, css]
80
+ # @yieldparam template [String]
81
+ # The location of the Sass/SCSS file being updated.
82
+ # @yieldparam css [String]
83
+ # The location of the CSS file being generated.
84
+ define_callback :updating_stylesheet
85
+
86
+ def on_updating_stylesheet_with_deprecation_warning(&block)
87
+ Sass::Util.sass_warn("Sass::Compiler#on_updating_stylesheet callback is deprecated and will be removed in a future release. Use Sass::Compiler#on_updated_stylesheet instead, which is run after stylesheet compilation.")
88
+ on_updating_stylesheet_without_deprecation_warning(&block)
89
+ end
90
+ alias_method :on_updating_stylesheet_without_deprecation_warning, :on_updating_stylesheet
91
+ alias_method :on_updating_stylesheet, :on_updating_stylesheet_with_deprecation_warning
92
+
93
+ # Register a callback to be run when Sass decides not to update a stylesheet.
94
+ # In particular, the callback is run when Sass finds that
95
+ # the template file and none of its dependencies
96
+ # have been modified since the last compilation.
97
+ #
98
+ # Note that this is **not** run when the
99
+ # \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set,
100
+ # nor when Sass decides not to compile a partial.
101
+ #
102
+ # @yield [template, css]
103
+ # @yieldparam template [String]
104
+ # The location of the Sass/SCSS file not being updated.
105
+ # @yieldparam css [String]
106
+ # The location of the CSS file not being generated.
107
+ define_callback :not_updating_stylesheet
108
+
109
+ # Register a callback to be run when there's an error
110
+ # compiling a Sass file.
111
+ # This could include not only errors in the Sass document,
112
+ # but also errors accessing the file at all.
113
+ #
114
+ # @yield [error, template, css]
115
+ # @yieldparam error [Exception] The exception that was raised.
116
+ # @yieldparam template [String]
117
+ # The location of the Sass/SCSS file being updated.
118
+ # @yieldparam css [String]
119
+ # The location of the CSS file being generated.
120
+ define_callback :compilation_error
121
+
122
+ # Register a callback to be run when Sass creates a directory
123
+ # into which to put CSS files.
124
+ #
125
+ # Note that even if multiple levels of directories need to be created,
126
+ # the callback may only be run once.
127
+ # For example, if "foo/" exists and "foo/bar/baz/" needs to be created,
128
+ # this may only be run for "foo/bar/baz/".
129
+ # This is not a guarantee, however;
130
+ # it may also be run for "foo/bar/".
131
+ #
132
+ # @yield [dirname]
133
+ # @yieldparam dirname [String]
134
+ # The location of the directory that was created.
135
+ define_callback :creating_directory
136
+
137
+ # Register a callback to be run when Sass detects
138
+ # that a template has been modified.
139
+ # This is only run when using \{#watch}.
140
+ #
141
+ # @yield [template]
142
+ # @yieldparam template [String]
143
+ # The location of the template that was modified.
144
+ define_callback :template_modified
145
+
146
+ # Register a callback to be run when Sass detects
147
+ # that a new template has been created.
148
+ # This is only run when using \{#watch}.
149
+ #
150
+ # @yield [template]
151
+ # @yieldparam template [String]
152
+ # The location of the template that was created.
153
+ define_callback :template_created
154
+
155
+ # Register a callback to be run when Sass detects
156
+ # that a template has been deleted.
157
+ # This is only run when using \{#watch}.
158
+ #
159
+ # @yield [template]
160
+ # @yieldparam template [String]
161
+ # The location of the template that was deleted.
162
+ define_callback :template_deleted
163
+
164
+ # Register a callback to be run when Sass deletes a CSS file.
165
+ # This happens when the corresponding Sass/SCSS file has been deleted.
166
+ #
167
+ # @yield [filename]
168
+ # @yieldparam filename [String]
169
+ # The location of the CSS file that was deleted.
170
+ define_callback :deleting_css
171
+
172
+ # Updates out-of-date stylesheets.
173
+ #
174
+ # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
175
+ # to see if it's been modified more recently than the corresponding CSS file
176
+ # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
177
+ # If it has, it updates the CSS file.
178
+ #
179
+ # @param individual_files [Array<(String, String)>]
180
+ # A list of files to check for updates
181
+ # **in addition to those specified by the
182
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
183
+ # The first string in each pair is the location of the Sass/SCSS file,
184
+ # the second is the location of the CSS file that it should be compiled to.
185
+ def update_stylesheets(individual_files = [])
186
+ individual_files = individual_files.dup
187
+ Sass::Plugin.checked_for_updates = true
188
+ staleness_checker = StalenessChecker.new(engine_options)
189
+
190
+ template_location_array.each do |template_location, css_location|
191
+ Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
192
+ # Get the relative path to the file
193
+ name = file.sub(template_location.to_s.sub(/\/*$/, '/'), "")
194
+ css = css_filename(name, css_location)
195
+ individual_files << [file, css]
196
+ end
197
+ end
198
+
199
+ run_updating_stylesheets individual_files
200
+
201
+ individual_files.each do |file, css|
202
+ if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
203
+ update_stylesheet(file, css)
204
+ else
205
+ run_not_updating_stylesheet(file, css)
206
+ end
207
+ end
208
+ end
209
+
210
+ # Watches the template directory (or directories)
211
+ # and updates the CSS files whenever the related Sass/SCSS files change.
212
+ # `watch` never returns.
213
+ #
214
+ # Whenever a change is detected to a Sass/SCSS file in
215
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`},
216
+ # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
217
+ # will be recompiled.
218
+ # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
219
+ #
220
+ # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
221
+ #
222
+ # Note that `watch` uses the [Listen](http://github.com/guard/listen) library
223
+ # to monitor the filesystem for changes.
224
+ # Listen isn't loaded until `watch` is run.
225
+ # The version of Listen distributed with Sass is loaded by default,
226
+ # but if another version has already been loaded that will be used instead.
227
+ #
228
+ # @param individual_files [Array<(String, String)>]
229
+ # A list of files to watch for updates
230
+ # **in addition to those specified by the
231
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
232
+ # The first string in each pair is the location of the Sass/SCSS file,
233
+ # the second is the location of the CSS file that it should be compiled to.
234
+ def watch(individual_files = [])
235
+ update_stylesheets(individual_files)
236
+
237
+ load_listen!
238
+
239
+ template_paths = template_locations # cache the locations
240
+ individual_files_hash = individual_files.inject({}) do |h, files|
241
+ parent = File.dirname(files.first)
242
+ (h[parent] ||= []) << files unless template_paths.include?(parent)
243
+ h
244
+ end
245
+ directories = template_paths + individual_files_hash.keys +
246
+ [{:relative_paths => true}]
247
+
248
+ # TODO: Keep better track of what depends on what
249
+ # so we don't have to run a global update every time anything changes.
250
+ listener = Listen::MultiListener.new(*directories) do |modified, added, removed|
251
+ modified.each do |f|
252
+ parent = File.dirname(f)
253
+ if files = individual_files_hash[parent]
254
+ next unless files.first == f
255
+ else
256
+ next unless f =~ /\.s[ac]ss$/
257
+ end
258
+ run_template_modified(f)
259
+ end
260
+
261
+ added.each do |f|
262
+ parent = File.dirname(f)
263
+ if files = individual_files_hash[parent]
264
+ next unless files.first == f
265
+ else
266
+ next unless f =~ /\.s[ac]ss$/
267
+ end
268
+ run_template_created(f)
269
+ end
270
+
271
+ removed.each do |f|
272
+ parent = File.dirname(f)
273
+ if files = individual_files_hash[parent]
274
+ next unless files.first == f
275
+ try_delete_css files[1]
276
+ else
277
+ next unless f =~ /\.s[ac]ss$/
278
+ try_delete_css f.gsub(/\.s[ac]ss$/, '.css')
279
+ end
280
+ run_template_deleted(f)
281
+ end
282
+
283
+ update_stylesheets(individual_files)
284
+ end
285
+
286
+ # The native windows listener is much slower than the polling
287
+ # option, according to https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e#commitcomment-1295118
288
+ listener.force_polling(true) if @options[:poll] || Sass::Util.windows?
289
+
290
+ begin
291
+ listener.start
292
+ rescue Exception => e
293
+ raise e unless e.is_a?(Interrupt)
294
+ end
295
+ end
296
+
297
+ # Non-destructively modifies \{#options} so that default values are properly set,
298
+ # and returns the result.
299
+ #
300
+ # @param additional_options [{Symbol => Object}] An options hash with which to merge \{#options}
301
+ # @return [{Symbol => Object}] The modified options hash
302
+ def engine_options(additional_options = {})
303
+ opts = options.merge(additional_options)
304
+ opts[:load_paths] = load_paths(opts)
305
+ opts
306
+ end
307
+
308
+ # Compass expects this to exist
309
+ def stylesheet_needs_update?(css_file, template_file)
310
+ StalenessChecker.stylesheet_needs_update?(css_file, template_file)
311
+ end
312
+
313
+ private
314
+
315
+ def load_listen!
316
+ if defined?(gem)
317
+ begin
318
+ gem 'listen', '~> 0.7'
319
+ require 'listen'
320
+ rescue Gem::LoadError
321
+ dir = Sass::Util.scope("vendor/listen/lib")
322
+ $LOAD_PATH.unshift dir
323
+ begin
324
+ require 'listen'
325
+ rescue LoadError => e
326
+ e.message << "\n" <<
327
+ if File.exists?(scope(".git"))
328
+ 'Run "git submodule update --init" to get the recommended version.'
329
+ else
330
+ 'Run "gem install listen" to get it.'
331
+ end
332
+ raise e
333
+ end
334
+ end
335
+ else
336
+ begin
337
+ require 'listen'
338
+ rescue LoadError => e
339
+ dir = Sass::Util.scope("vendor/listen/lib")
340
+ if $LOAD_PATH.include?(dir)
341
+ raise e unless File.exists?(scope(".git"))
342
+ e.message << "\n" <<
343
+ 'Run "git submodule update --init" to get the recommended version.'
344
+ else
345
+ $LOAD_PATH.unshift dir
346
+ retry
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ def update_stylesheet(filename, css)
353
+ dir = File.dirname(css)
354
+ unless File.exists?(dir)
355
+ run_creating_directory dir
356
+ FileUtils.mkdir_p dir
357
+ end
358
+
359
+ begin
360
+ File.read(filename) unless File.readable?(filename) # triggers an error for handling
361
+ engine_opts = engine_options(:css_filename => css, :filename => filename)
362
+ result = Sass::Engine.for_file(filename, engine_opts).render
363
+ rescue Exception => e
364
+ compilation_error_occured = true
365
+ run_compilation_error e, filename, css
366
+ result = Sass::SyntaxError.exception_to_css(e, options)
367
+ else
368
+ run_updating_stylesheet filename, css
369
+ end
370
+
371
+ write_file(css, result)
372
+ run_updated_stylesheet(filename, css) unless compilation_error_occured
373
+ end
374
+
375
+ def write_file(css, content)
376
+ flag = 'w'
377
+ flag = 'wb' if Sass::Util.windows? && options[:unix_newlines]
378
+ File.open(css, flag) do |file|
379
+ file.set_encoding(content.encoding) unless Sass::Util.ruby1_8?
380
+ file.print(content)
381
+ end
382
+ end
383
+
384
+ def try_delete_css(css)
385
+ return unless File.exists?(css)
386
+ run_deleting_css css
387
+ File.delete css
388
+ end
389
+
390
+ def load_paths(opts = options)
391
+ (opts[:load_paths] || []) + template_locations
392
+ end
393
+
394
+ def template_locations
395
+ template_location_array.to_a.map {|l| l.first}
396
+ end
397
+
398
+ def css_locations
399
+ template_location_array.to_a.map {|l| l.last}
400
+ end
401
+
402
+ def css_filename(name, path)
403
+ "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
404
+ end
405
+ end
406
+ end