sass 3.1.0.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/EDGE_GEM_VERSION +1 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +201 -0
  6. data/REVISION +1 -0
  7. data/Rakefile +353 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/css2sass +13 -0
  11. data/bin/sass +8 -0
  12. data/bin/sass-convert +7 -0
  13. data/extra/update_watch.rb +13 -0
  14. data/init.rb +18 -0
  15. data/lib/sass.rb +71 -0
  16. data/lib/sass/cache_store.rb +208 -0
  17. data/lib/sass/callbacks.rb +66 -0
  18. data/lib/sass/css.rb +294 -0
  19. data/lib/sass/engine.rb +792 -0
  20. data/lib/sass/environment.rb +143 -0
  21. data/lib/sass/error.rb +201 -0
  22. data/lib/sass/exec.rb +619 -0
  23. data/lib/sass/importers.rb +22 -0
  24. data/lib/sass/importers/base.rb +138 -0
  25. data/lib/sass/importers/filesystem.rb +121 -0
  26. data/lib/sass/less.rb +363 -0
  27. data/lib/sass/plugin.rb +126 -0
  28. data/lib/sass/plugin/compiler.rb +346 -0
  29. data/lib/sass/plugin/configuration.rb +123 -0
  30. data/lib/sass/plugin/generic.rb +15 -0
  31. data/lib/sass/plugin/merb.rb +48 -0
  32. data/lib/sass/plugin/rack.rb +47 -0
  33. data/lib/sass/plugin/rails.rb +41 -0
  34. data/lib/sass/plugin/staleness_checker.rb +145 -0
  35. data/lib/sass/railtie.rb +8 -0
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/root.rb +7 -0
  38. data/lib/sass/script.rb +63 -0
  39. data/lib/sass/script/bool.rb +18 -0
  40. data/lib/sass/script/color.rb +490 -0
  41. data/lib/sass/script/css_lexer.rb +29 -0
  42. data/lib/sass/script/css_parser.rb +31 -0
  43. data/lib/sass/script/funcall.rb +78 -0
  44. data/lib/sass/script/functions.rb +852 -0
  45. data/lib/sass/script/interpolation.rb +70 -0
  46. data/lib/sass/script/lexer.rb +337 -0
  47. data/lib/sass/script/literal.rb +236 -0
  48. data/lib/sass/script/node.rb +101 -0
  49. data/lib/sass/script/number.rb +420 -0
  50. data/lib/sass/script/operation.rb +92 -0
  51. data/lib/sass/script/parser.rb +392 -0
  52. data/lib/sass/script/string.rb +67 -0
  53. data/lib/sass/script/string_interpolation.rb +93 -0
  54. data/lib/sass/script/unary_operation.rb +57 -0
  55. data/lib/sass/script/variable.rb +48 -0
  56. data/lib/sass/scss.rb +17 -0
  57. data/lib/sass/scss/css_parser.rb +51 -0
  58. data/lib/sass/scss/parser.rb +838 -0
  59. data/lib/sass/scss/rx.rb +126 -0
  60. data/lib/sass/scss/sass_parser.rb +11 -0
  61. data/lib/sass/scss/script_lexer.rb +15 -0
  62. data/lib/sass/scss/script_parser.rb +25 -0
  63. data/lib/sass/scss/static_parser.rb +40 -0
  64. data/lib/sass/selector.rb +361 -0
  65. data/lib/sass/selector/abstract_sequence.rb +62 -0
  66. data/lib/sass/selector/comma_sequence.rb +82 -0
  67. data/lib/sass/selector/sequence.rb +236 -0
  68. data/lib/sass/selector/simple.rb +113 -0
  69. data/lib/sass/selector/simple_sequence.rb +135 -0
  70. data/lib/sass/shared.rb +78 -0
  71. data/lib/sass/tree/comment_node.rb +128 -0
  72. data/lib/sass/tree/debug_node.rb +36 -0
  73. data/lib/sass/tree/directive_node.rb +75 -0
  74. data/lib/sass/tree/extend_node.rb +65 -0
  75. data/lib/sass/tree/for_node.rb +67 -0
  76. data/lib/sass/tree/if_node.rb +81 -0
  77. data/lib/sass/tree/import_node.rb +124 -0
  78. data/lib/sass/tree/mixin_def_node.rb +60 -0
  79. data/lib/sass/tree/mixin_node.rb +123 -0
  80. data/lib/sass/tree/node.rb +490 -0
  81. data/lib/sass/tree/prop_node.rb +220 -0
  82. data/lib/sass/tree/root_node.rb +125 -0
  83. data/lib/sass/tree/rule_node.rb +273 -0
  84. data/lib/sass/tree/variable_node.rb +39 -0
  85. data/lib/sass/tree/warn_node.rb +42 -0
  86. data/lib/sass/tree/while_node.rb +48 -0
  87. data/lib/sass/util.rb +687 -0
  88. data/lib/sass/util/subset_map.rb +101 -0
  89. data/lib/sass/version.rb +109 -0
  90. data/rails/init.rb +1 -0
  91. data/test/sass/cache_test.rb +74 -0
  92. data/test/sass/callbacks_test.rb +61 -0
  93. data/test/sass/conversion_test.rb +1210 -0
  94. data/test/sass/css2sass_test.rb +364 -0
  95. data/test/sass/data/hsl-rgb.txt +319 -0
  96. data/test/sass/engine_test.rb +2273 -0
  97. data/test/sass/extend_test.rb +1348 -0
  98. data/test/sass/functions_test.rb +565 -0
  99. data/test/sass/importer_test.rb +104 -0
  100. data/test/sass/less_conversion_test.rb +632 -0
  101. data/test/sass/mock_importer.rb +49 -0
  102. data/test/sass/more_results/more1.css +9 -0
  103. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  104. data/test/sass/more_results/more_import.css +29 -0
  105. data/test/sass/more_templates/_more_partial.sass +2 -0
  106. data/test/sass/more_templates/more1.sass +23 -0
  107. data/test/sass/more_templates/more_import.sass +11 -0
  108. data/test/sass/plugin_test.rb +430 -0
  109. data/test/sass/results/alt.css +4 -0
  110. data/test/sass/results/basic.css +9 -0
  111. data/test/sass/results/compact.css +5 -0
  112. data/test/sass/results/complex.css +86 -0
  113. data/test/sass/results/compressed.css +1 -0
  114. data/test/sass/results/expanded.css +19 -0
  115. data/test/sass/results/import.css +31 -0
  116. data/test/sass/results/line_numbers.css +49 -0
  117. data/test/sass/results/mixins.css +95 -0
  118. data/test/sass/results/multiline.css +24 -0
  119. data/test/sass/results/nested.css +22 -0
  120. data/test/sass/results/options.css +1 -0
  121. data/test/sass/results/parent_ref.css +13 -0
  122. data/test/sass/results/script.css +16 -0
  123. data/test/sass/results/scss_import.css +31 -0
  124. data/test/sass/results/scss_importee.css +2 -0
  125. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  126. data/test/sass/results/subdir/subdir.css +3 -0
  127. data/test/sass/results/units.css +11 -0
  128. data/test/sass/results/warn.css +0 -0
  129. data/test/sass/results/warn_imported.css +0 -0
  130. data/test/sass/script_conversion_test.rb +254 -0
  131. data/test/sass/script_test.rb +459 -0
  132. data/test/sass/scss/css_test.rb +897 -0
  133. data/test/sass/scss/rx_test.rb +156 -0
  134. data/test/sass/scss/scss_test.rb +1088 -0
  135. data/test/sass/scss/test_helper.rb +37 -0
  136. data/test/sass/templates/_partial.sass +2 -0
  137. data/test/sass/templates/alt.sass +16 -0
  138. data/test/sass/templates/basic.sass +23 -0
  139. data/test/sass/templates/bork1.sass +2 -0
  140. data/test/sass/templates/bork2.sass +2 -0
  141. data/test/sass/templates/bork3.sass +2 -0
  142. data/test/sass/templates/bork4.sass +2 -0
  143. data/test/sass/templates/compact.sass +17 -0
  144. data/test/sass/templates/complex.sass +305 -0
  145. data/test/sass/templates/compressed.sass +15 -0
  146. data/test/sass/templates/expanded.sass +17 -0
  147. data/test/sass/templates/import.sass +12 -0
  148. data/test/sass/templates/importee.less +2 -0
  149. data/test/sass/templates/importee.sass +19 -0
  150. data/test/sass/templates/line_numbers.sass +13 -0
  151. data/test/sass/templates/mixin_bork.sass +5 -0
  152. data/test/sass/templates/mixins.sass +76 -0
  153. data/test/sass/templates/multiline.sass +20 -0
  154. data/test/sass/templates/nested.sass +25 -0
  155. data/test/sass/templates/nested_bork1.sass +2 -0
  156. data/test/sass/templates/nested_bork2.sass +2 -0
  157. data/test/sass/templates/nested_bork3.sass +2 -0
  158. data/test/sass/templates/nested_bork4.sass +2 -0
  159. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  160. data/test/sass/templates/options.sass +2 -0
  161. data/test/sass/templates/parent_ref.sass +25 -0
  162. data/test/sass/templates/script.sass +101 -0
  163. data/test/sass/templates/scss_import.scss +11 -0
  164. data/test/sass/templates/scss_importee.scss +1 -0
  165. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  166. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  167. data/test/sass/templates/subdir/subdir.sass +6 -0
  168. data/test/sass/templates/units.sass +11 -0
  169. data/test/sass/templates/warn.sass +3 -0
  170. data/test/sass/templates/warn_imported.sass +4 -0
  171. data/test/sass/test_helper.rb +8 -0
  172. data/test/sass/util/subset_map_test.rb +91 -0
  173. data/test/sass/util_test.rb +275 -0
  174. data/test/test_helper.rb +64 -0
  175. data/vendor/fssm/LICENSE +20 -0
  176. data/vendor/fssm/README.markdown +55 -0
  177. data/vendor/fssm/Rakefile +59 -0
  178. data/vendor/fssm/VERSION.yml +5 -0
  179. data/vendor/fssm/example.rb +9 -0
  180. data/vendor/fssm/fssm.gemspec +77 -0
  181. data/vendor/fssm/lib/fssm.rb +33 -0
  182. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  183. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  184. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  185. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  186. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  187. data/vendor/fssm/lib/fssm/path.rb +91 -0
  188. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  189. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  190. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  191. data/vendor/fssm/lib/fssm/support.rb +63 -0
  192. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  193. data/vendor/fssm/profile/prof-cache.rb +40 -0
  194. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  195. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  196. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  197. data/vendor/fssm/profile/prof.html +2379 -0
  198. data/vendor/fssm/spec/path_spec.rb +75 -0
  199. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  200. data/vendor/fssm/spec/root/file.css +0 -0
  201. data/vendor/fssm/spec/root/file.rb +0 -0
  202. data/vendor/fssm/spec/root/file.yml +0 -0
  203. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  204. data/vendor/fssm/spec/spec_helper.rb +14 -0
  205. metadata +297 -0
@@ -0,0 +1,39 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing a variable definition.
4
+ #
5
+ # @see Sass::Tree
6
+ class VariableNode < Node
7
+ # @param name [String] The name of the variable
8
+ # @param expr [Script::Node] The parse tree for the initial variable value
9
+ # @param guarded [Boolean] Whether this is a guarded variable assignment (`||=`)
10
+ def initialize(name, expr, guarded)
11
+ @name = name
12
+ @expr = expr
13
+ @guarded = guarded
14
+ super()
15
+ end
16
+
17
+ protected
18
+
19
+ # @see Node#to_src
20
+ def to_src(tabs, opts, fmt)
21
+ "#{' ' * tabs}$#{dasherize(@name, opts)}: #{@expr.to_sass(opts)}#{' !default' if @guarded}#{semi fmt}\n"
22
+ end
23
+
24
+ # Loads the new variable value into the environment.
25
+ #
26
+ # @param environment [Sass::Environment] The lexical environment containing
27
+ # variable and mixin values
28
+ def _perform(environment)
29
+ return [] if @guarded && !environment.var(@name).nil?
30
+ val = @expr.perform(environment)
31
+ if @expr.context == :equals && val.is_a?(Sass::Script::String)
32
+ val = Sass::Script::String.new(val.value)
33
+ end
34
+ environment.set_var(@name, val)
35
+ []
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ module Sass
2
+ module Tree
3
+ # A dynamic node representing a Sass `@warn` statement.
4
+ #
5
+ # @see Sass::Tree
6
+ class WarnNode < Node
7
+ # @param expr [Script::Node] The expression to print
8
+ def initialize(expr)
9
+ @expr = expr
10
+ super()
11
+ end
12
+
13
+ protected
14
+
15
+ # @see Node#to_src
16
+ def to_src(tabs, opts, fmt)
17
+ "#{' ' * tabs}@warn #{@expr.to_sass(opts)}#{semi fmt}\n"
18
+ end
19
+
20
+ # Prints the expression to STDERR with a stylesheet trace.
21
+ #
22
+ # @param environment [Sass::Environment] The lexical environment containing
23
+ # variable and mixin values
24
+ def _perform(environment)
25
+ environment.push_frame(:filename => filename, :line => line)
26
+ res = @expr.perform(environment)
27
+ res = res.value if res.is_a?(Sass::Script::String)
28
+ msg = "WARNING: #{res}\n"
29
+ environment.stack.reverse.each_with_index do |entry, i|
30
+ msg << " #{i == 0 ? "on" : "from"} line #{entry[:line]}" <<
31
+ " of #{entry[:filename] || "an unknown file"}"
32
+ msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
33
+ msg << "\n"
34
+ end
35
+ Sass::Util.sass_warn msg
36
+ []
37
+ ensure
38
+ environment.pop_frame
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A dynamic node representing a Sass `@while` loop.
5
+ #
6
+ # @see Sass::Tree
7
+ class WhileNode < Node
8
+ # @param expr [Script::Node] The parse tree for the continue expression
9
+ def initialize(expr)
10
+ @expr = expr
11
+ super()
12
+ end
13
+
14
+ protected
15
+
16
+ # @see Node#to_src
17
+ def to_src(tabs, opts, fmt)
18
+ "#{' ' * tabs}@while #{@expr.to_sass(opts)}" + children_to_src(tabs, opts, fmt)
19
+ end
20
+
21
+ # Runs the child nodes until the continue expression becomes false.
22
+ #
23
+ # @param environment [Sass::Environment] The lexical environment containing
24
+ # variable and mixin values
25
+ # @return [Array<Tree::Node>] The resulting static nodes
26
+ # @see Sass::Tree
27
+ def _perform(environment)
28
+ children = []
29
+ new_environment = Sass::Environment.new(environment)
30
+ while @expr.perform(environment).to_bool
31
+ children += perform_children(new_environment)
32
+ end
33
+ children
34
+ end
35
+
36
+ # Returns an error message if the given child node is invalid,
37
+ # and false otherwise.
38
+ #
39
+ # {ExtendNode}s are valid within {WhileNode}s.
40
+ #
41
+ # @param child [Tree::Node] A potential child node.
42
+ # @return [Boolean, String] Whether or not the child node is valid,
43
+ # as well as the error message to display if it is invalid
44
+ def invalid_child?(child)
45
+ super unless child.is_a?(ExtendNode)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,687 @@
1
+ require 'erb'
2
+ require 'set'
3
+ require 'enumerator'
4
+ require 'stringio'
5
+ require 'strscan'
6
+ require 'rbconfig'
7
+
8
+ require 'sass/root'
9
+ require 'sass/util/subset_map'
10
+
11
+ module Sass
12
+ # A module containing various useful functions.
13
+ module Util
14
+ extend self
15
+
16
+ # An array of ints representing the Ruby version number.
17
+ # @api public
18
+ RUBY_VERSION = ::RUBY_VERSION.split(".").map {|s| s.to_i}
19
+
20
+ # Returns the path of a file relative to the Sass root directory.
21
+ #
22
+ # @param file [String] The filename relative to the Sass root
23
+ # @return [String] The filename relative to the the working directory
24
+ def scope(file)
25
+ File.join(Sass::ROOT_DIR, file)
26
+ end
27
+
28
+ # Converts an array of `[key, value]` pairs to a hash.
29
+ #
30
+ # @example
31
+ # to_hash([[:foo, "bar"], [:baz, "bang"]])
32
+ # #=> {:foo => "bar", :baz => "bang"}
33
+ # @param arr [Array<(Object, Object)>] An array of pairs
34
+ # @return [Hash] A hash
35
+ def to_hash(arr)
36
+ arr.compact.inject({}) {|h, (k, v)| h[k] = v; h}
37
+ end
38
+
39
+ # Maps the keys in a hash according to a block.
40
+ #
41
+ # @example
42
+ # map_keys({:foo => "bar", :baz => "bang"}) {|k| k.to_s}
43
+ # #=> {"foo" => "bar", "baz" => "bang"}
44
+ # @param hash [Hash] The hash to map
45
+ # @yield [key] A block in which the keys are transformed
46
+ # @yieldparam key [Object] The key that should be mapped
47
+ # @yieldreturn [Object] The new value for the key
48
+ # @return [Hash] The mapped hash
49
+ # @see #map_vals
50
+ # @see #map_hash
51
+ def map_keys(hash)
52
+ to_hash(hash.map {|k, v| [yield(k), v]})
53
+ end
54
+
55
+ # Maps the values in a hash according to a block.
56
+ #
57
+ # @example
58
+ # map_values({:foo => "bar", :baz => "bang"}) {|v| v.to_sym}
59
+ # #=> {:foo => :bar, :baz => :bang}
60
+ # @param hash [Hash] The hash to map
61
+ # @yield [value] A block in which the values are transformed
62
+ # @yieldparam value [Object] The value that should be mapped
63
+ # @yieldreturn [Object] The new value for the value
64
+ # @return [Hash] The mapped hash
65
+ # @see #map_keys
66
+ # @see #map_hash
67
+ def map_vals(hash)
68
+ to_hash(hash.map {|k, v| [k, yield(v)]})
69
+ end
70
+
71
+ # Maps the key-value pairs of a hash according to a block.
72
+ #
73
+ # @example
74
+ # map_hash({:foo => "bar", :baz => "bang"}) {|k, v| [k.to_s, v.to_sym]}
75
+ # #=> {"foo" => :bar, "baz" => :bang}
76
+ # @param hash [Hash] The hash to map
77
+ # @yield [key, value] A block in which the key-value pairs are transformed
78
+ # @yieldparam [key] The hash key
79
+ # @yieldparam [value] The hash value
80
+ # @yieldreturn [(Object, Object)] The new value for the `[key, value]` pair
81
+ # @return [Hash] The mapped hash
82
+ # @see #map_keys
83
+ # @see #map_vals
84
+ def map_hash(hash, &block)
85
+ to_hash(hash.map(&block))
86
+ end
87
+
88
+ # Computes the powerset of the given array.
89
+ # This is the set of all subsets of the array.
90
+ #
91
+ # @example
92
+ # powerset([1, 2, 3]) #=>
93
+ # Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]]
94
+ # @param arr [Enumerable]
95
+ # @return [Set<Set>] The subsets of `arr`
96
+ def powerset(arr)
97
+ arr.inject([Set.new].to_set) do |powerset, el|
98
+ new_powerset = Set.new
99
+ powerset.each do |subset|
100
+ new_powerset << subset
101
+ new_powerset << subset + [el]
102
+ end
103
+ new_powerset
104
+ end
105
+ end
106
+
107
+ # Restricts a number to falling within a given range.
108
+ # Returns the number if it falls within the range,
109
+ # or the closest value in the range if it doesn't.
110
+ #
111
+ # @param value [Numeric]
112
+ # @param range [Range<Numeric>]
113
+ # @return [Numeric]
114
+ def restrict(value, range)
115
+ [[value, range.first].max, range.last].min
116
+ end
117
+
118
+ # Concatenates all strings that are adjacent in an array,
119
+ # while leaving other elements as they are.
120
+ #
121
+ # @example
122
+ # merge_adjacent_strings([1, "foo", "bar", 2, "baz"])
123
+ # #=> [1, "foobar", 2, "baz"]
124
+ # @param enum [Enumerable]
125
+ # @return [Array] The enumerable with strings merged
126
+ def merge_adjacent_strings(enum)
127
+ enum.inject([]) do |a, e|
128
+ if e.is_a?(String)
129
+ if a.last.is_a?(String)
130
+ a.last << e
131
+ else
132
+ a << e.dup
133
+ end
134
+ else
135
+ a << e
136
+ end
137
+ a
138
+ end
139
+ end
140
+
141
+ # Intersperses a value in an enumerable, as would be done with `Array#join`
142
+ # but without concatenating the array together afterwards.
143
+ #
144
+ # @param enum [Enumerable]
145
+ # @param val
146
+ # @return [Array]
147
+ def intersperse(enum, val)
148
+ enum.inject([]) {|a, e| a << e << val}[0...-1]
149
+ end
150
+
151
+ # Substitutes a sub-array of one array with another sub-array.
152
+ #
153
+ # @param ary [Array] The array in which to make the substitution
154
+ # @param from [Array] The sequence of elements to replace with `to`
155
+ # @param to [Array] The sequence of elements to replace `from` with
156
+ def substitute(ary, from, to)
157
+ res = ary.dup
158
+ i = 0
159
+ while i < res.size
160
+ if res[i...i+from.size] == from
161
+ res[i...i+from.size] = to
162
+ end
163
+ i += 1
164
+ end
165
+ res
166
+ end
167
+
168
+ # Destructively strips whitespace from the beginning and end
169
+ # of the first and last elements, respectively,
170
+ # in the array (if those elements are strings).
171
+ #
172
+ # @param arr [Array]
173
+ # @return [Array] `arr`
174
+ def strip_string_array(arr)
175
+ arr.first.lstrip! if arr.first.is_a?(String)
176
+ arr.last.rstrip! if arr.last.is_a?(String)
177
+ arr
178
+ end
179
+
180
+ # Return an array of all possible paths through the given arrays.
181
+ #
182
+ # @param arrs [Array<Array>]
183
+ # @return [Array<Arrays>]
184
+ #
185
+ # @example
186
+ # paths([[1, 2], [3, 4], [5]]) #=>
187
+ # # [[1, 3, 5],
188
+ # # [2, 3, 5],
189
+ # # [1, 4, 5],
190
+ # # [2, 4, 5]]
191
+ def paths(arrs)
192
+ arrs.inject([[]]) do |paths, arr|
193
+ flatten(arr.map {|e| paths.map {|path| path + [e]}}, 1)
194
+ end
195
+ end
196
+
197
+ # Computes a single longest common subsequence for `x` and `y`.
198
+ # If there are more than one longest common subsequences,
199
+ # the one returned is that which starts first in `x`.
200
+ #
201
+ # @param x [Array]
202
+ # @param y [Array]
203
+ # @yield [a, b] An optional block to use in place of a check for equality
204
+ # between elements of `x` and `y`.
205
+ # @yieldreturn [Object, nil] If the two values register as equal,
206
+ # this will return the value to use in the LCS array.
207
+ # @return [Array] The LCS
208
+ def lcs(x, y, &block)
209
+ x = [nil, *x]
210
+ y = [nil, *y]
211
+ block ||= proc {|a, b| a == b && a}
212
+ lcs_backtrace(lcs_table(x, y, &block), x, y, x.size-1, y.size-1, &block)
213
+ end
214
+
215
+ # Returns information about the caller of the previous method.
216
+ #
217
+ # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
218
+ # @return [[String, Fixnum, (String, nil)]] An array containing the filename, line, and method name of the caller.
219
+ # The method name may be nil
220
+ def caller_info(entry = caller[1])
221
+ info = entry.scan(/^(.*?):(-?.*?)(?::.*`(.+)')?$/).first
222
+ info[1] = info[1].to_i
223
+ # This is added by Rubinius to designate a block, but we don't care about it.
224
+ info[2].sub!(/ \{\}\Z/, '') if info[2]
225
+ info
226
+ end
227
+
228
+ # Returns whether one version string represents a more recent version than another.
229
+ #
230
+ # @param v1 [String] A version string.
231
+ # @param v2 [String] Another version string.
232
+ # @return [Boolean]
233
+ def version_gt(v1, v2)
234
+ # Construct an array to make sure the shorter version is padded with nil
235
+ Array.new([v1.length, v2.length].max).zip(v1.split("."), v2.split(".")) do |_, p1, p2|
236
+ p1 ||= "0"
237
+ p2 ||= "0"
238
+ release1 = p1 =~ /^[0-9]+$/
239
+ release2 = p2 =~ /^[0-9]+$/
240
+ if release1 && release2
241
+ # Integer comparison if both are full releases
242
+ p1, p2 = p1.to_i, p2.to_i
243
+ next if p1 == p2
244
+ return p1 > p2
245
+ elsif !release1 && !release2
246
+ # String comparison if both are prereleases
247
+ next if p1 == p2
248
+ return p1 > p2
249
+ else
250
+ # If only one is a release, that one is newer
251
+ return release1
252
+ end
253
+ end
254
+ end
255
+
256
+ # Returns whether one version string represents the same or a more
257
+ # recent version than another.
258
+ #
259
+ # @param v1 [String] A version string.
260
+ # @param v2 [String] Another version string.
261
+ # @return [Boolean]
262
+ def version_geq(v1, v2)
263
+ version_gt(v1, v2) || !version_gt(v2, v1)
264
+ end
265
+
266
+ # A wrapper for `Marshal.dump` that calls `#_before_dump` on the object
267
+ # before dumping it, `#_after_dump` afterwards.
268
+ # It also calls `#_around_dump` and passes it a block in which the object is dumped.
269
+ #
270
+ # If any of these methods are undefined, they are not called.
271
+ #
272
+ # This will recursively call itself on members of arrays and hashes,
273
+ # but not of user-defined objects.
274
+ # This means that user-defined objects that need their members' `#_before_dump` etc. methods called
275
+ # must call `Haml::Util.dump` and `Haml::Util.load` manually on those members.
276
+ #
277
+ # @param obj [Object] The object to dump.
278
+ # @return [String] The dumped data.
279
+ def dump(obj)
280
+ obj._before_dump if obj.respond_to?(:_before_dump)
281
+ return convert_and_dump(obj) unless obj.respond_to?(:_around_dump)
282
+ res = nil
283
+ obj._around_dump {res = convert_and_dump(obj)}
284
+ res
285
+ ensure
286
+ obj._after_dump if obj.respond_to?(:_after_dump)
287
+ end
288
+
289
+ # A wrapper for `Marshal.load` that calls `#_after_load` on the object
290
+ # after loading it, if it's defined.
291
+ #
292
+ # @param data [String] The data to load.
293
+ # @return [Object] The loaded object.
294
+ def load(data)
295
+ obj = Marshal.load(data)
296
+
297
+ if obj.is_a?(Array)
298
+ obj = obj.map {|e| Sass::Util.load(e)}
299
+ elsif obj.is_a?(Hash)
300
+ obj = map_hash(obj) {|k, v| [Sass::Util.load(k), Sass::Util.load(v)]}
301
+ end
302
+
303
+ obj._after_load if obj.respond_to?(:_after_load)
304
+ obj
305
+ end
306
+
307
+ # Throws a NotImplementedError for an abstract method.
308
+ #
309
+ # @param obj [Object] `self`
310
+ # @raise [NotImplementedError]
311
+ def abstract(obj)
312
+ raise NotImplementedError.new("#{obj.class} must implement ##{caller_info[2]}")
313
+ end
314
+
315
+ # Silence all output to STDERR within a block.
316
+ #
317
+ # @yield A block in which no output will be printed to STDERR
318
+ def silence_warnings
319
+ the_real_stderr, $stderr = $stderr, StringIO.new
320
+ yield
321
+ ensure
322
+ $stderr = the_real_stderr
323
+ end
324
+
325
+ @@silence_warnings = false
326
+ # Silences all Sass warnings within a block.
327
+ #
328
+ # @yield A block in which no Sass warnings will be printed
329
+ def silence_sass_warnings
330
+ old_silence_warnings = @@silence_warnings
331
+ @@silence_warnings = true
332
+ yield
333
+ ensure
334
+ @@silence_warnings = old_silence_warnings
335
+ end
336
+
337
+ # The same as `Kernel#warn`, but is silenced by \{#silence\_sass\_warnings}.
338
+ #
339
+ # @param msg [String]
340
+ def sass_warn(msg)
341
+ return if @@silence_warnings
342
+ warn(msg)
343
+ end
344
+
345
+ ## Cross Rails Version Compatibility
346
+
347
+ # Returns the root of the Rails application,
348
+ # if this is running in a Rails context.
349
+ # Returns `nil` if no such root is defined.
350
+ #
351
+ # @return [String, nil]
352
+ def rails_root
353
+ if defined?(::Rails.root)
354
+ return ::Rails.root.to_s if ::Rails.root
355
+ raise "ERROR: Rails.root is nil!"
356
+ end
357
+ return RAILS_ROOT.to_s if defined?(RAILS_ROOT)
358
+ return nil
359
+ end
360
+
361
+ # Returns the environment of the Rails application,
362
+ # if this is running in a Rails context.
363
+ # Returns `nil` if no such environment is defined.
364
+ #
365
+ # @return [String, nil]
366
+ def rails_env
367
+ return ::Rails.env.to_s if defined?(::Rails.env)
368
+ return RAILS_ENV.to_s if defined?(RAILS_ENV)
369
+ return nil
370
+ end
371
+
372
+ # Returns whether this environment is using ActionPack
373
+ # version 3.0.0 or greater.
374
+ #
375
+ # @return [Boolean]
376
+ def ap_geq_3?
377
+ ap_geq?("3.0.0.beta1")
378
+ end
379
+
380
+ # Returns whether this environment is using ActionPack
381
+ # of a version greater than or equal to that specified.
382
+ #
383
+ # @param version [String] The string version number to check against.
384
+ # Should be greater than or equal to Rails 3,
385
+ # because otherwise ActionPack::VERSION isn't autoloaded
386
+ # @return [Boolean]
387
+ def ap_geq?(version)
388
+ # The ActionPack module is always loaded automatically in Rails >= 3
389
+ return false unless defined?(ActionPack) && defined?(ActionPack::VERSION) &&
390
+ defined?(ActionPack::VERSION::STRING)
391
+
392
+ version_geq(ActionPack::VERSION::STRING, version)
393
+ end
394
+
395
+ # Returns an ActionView::Template* class.
396
+ # In pre-3.0 versions of Rails, most of these classes
397
+ # were of the form `ActionView::TemplateFoo`,
398
+ # while afterwards they were of the form `ActionView;:Template::Foo`.
399
+ #
400
+ # @param name [#to_s] The name of the class to get.
401
+ # For example, `:Error` will return `ActionView::TemplateError`
402
+ # or `ActionView::Template::Error`.
403
+ def av_template_class(name)
404
+ return ActionView.const_get("Template#{name}") if ActionView.const_defined?("Template#{name}")
405
+ return ActionView::Template.const_get(name.to_s)
406
+ end
407
+
408
+ ## Cross-OS Compatibility
409
+
410
+ # Whether or not this is running on Windows.
411
+ #
412
+ # @return [Boolean]
413
+ def windows?
414
+ RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i
415
+ end
416
+
417
+ ## Cross-Ruby-Version Compatibility
418
+
419
+ # Whether or not this is running under Ruby 1.8 or lower.
420
+ #
421
+ # @return [Boolean]
422
+ def ruby1_8?
423
+ Sass::Util::RUBY_VERSION[0] == 1 && Sass::Util::RUBY_VERSION[1] < 9
424
+ end
425
+
426
+ # Whether or not this is running under Ruby 1.8.6 or lower.
427
+ # Note that lower versions are not officially supported.
428
+ #
429
+ # @return [Boolean]
430
+ def ruby1_8_6?
431
+ ruby1_8? && Sass::Util::RUBY_VERSION[2] < 7
432
+ end
433
+
434
+ # Checks that the encoding of a string is valid in Ruby 1.9
435
+ # and cleans up potential encoding gotchas like the UTF-8 BOM.
436
+ # If it's not, yields an error string describing the invalid character
437
+ # and the line on which it occurrs.
438
+ #
439
+ # @param str [String] The string of which to check the encoding
440
+ # @yield [msg] A block in which an encoding error can be raised.
441
+ # Only yields if there is an encoding error
442
+ # @yieldparam msg [String] The error message to be raised
443
+ # @return [String] `str`, potentially with encoding gotchas like BOMs removed
444
+ def check_encoding(str)
445
+ if ruby1_8?
446
+ return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
447
+ elsif str.valid_encoding?
448
+ # Get rid of the Unicode BOM if possible
449
+ if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
450
+ return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
451
+ else
452
+ return str
453
+ end
454
+ end
455
+
456
+ encoding = str.encoding
457
+ newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
458
+ str.force_encoding("binary").split(newlines).each_with_index do |line, i|
459
+ begin
460
+ line.encode(encoding)
461
+ rescue Encoding::UndefinedConversionError => e
462
+ yield <<MSG.rstrip, i + 1
463
+ Invalid #{encoding.name} character #{e.error_char.dump}
464
+ MSG
465
+ end
466
+ end
467
+ return str
468
+ end
469
+
470
+ # Like {\#check\_encoding}, but also checks for a `@charset` declaration
471
+ # at the beginning of the file and uses that encoding if it exists.
472
+ #
473
+ # The Sass encoding rules are simple.
474
+ # If a `@charset` declaration exists,
475
+ # we assume that that's the original encoding of the document.
476
+ # Otherwise, we use whatever encoding Ruby has.
477
+ # Then we convert that to UTF-8 to process internally.
478
+ # The UTF-8 end result is what's returned by this method.
479
+ #
480
+ # @param str [String] The string of which to check the encoding
481
+ # @yield [msg] A block in which an encoding error can be raised.
482
+ # Only yields if there is an encoding error
483
+ # @yieldparam msg [String] The error message to be raised
484
+ # @return [(String, Encoding)] The original string encoded as UTF-8,
485
+ # and the source encoding of the string (or `nil` under Ruby 1.8)
486
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
487
+ # cannot be converted to UTF-8
488
+ # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
489
+ def check_sass_encoding(str, &block)
490
+ return check_encoding(str, &block), nil if ruby1_8?
491
+ # We allow any printable ASCII characters but double quotes in the charset decl
492
+ bin = str.dup.force_encoding("BINARY")
493
+ encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
494
+ bin =~ Sass::Util::CHARSET_REGEXPS[enc]
495
+ end
496
+ charset, bom = $1, $2
497
+ if charset
498
+ charset = charset.force_encoding(encoding).encode("UTF-8")
499
+ if endianness = encoding[/[BL]E$/]
500
+ begin
501
+ Encoding.find(charset + endianness)
502
+ charset << endianness
503
+ rescue ArgumentError # Encoding charset + endianness doesn't exist
504
+ end
505
+ end
506
+ str.force_encoding(charset)
507
+ elsif bom
508
+ str.force_encoding(encoding)
509
+ end
510
+
511
+ str = check_encoding(str, &block)
512
+ return str.encode("UTF-8"), str.encoding
513
+ end
514
+
515
+ unless ruby1_8?
516
+ # @private
517
+ def _enc(string, encoding)
518
+ string.encode(encoding).force_encoding("BINARY")
519
+ end
520
+
521
+ # We could automatically add in any non-ASCII-compatible encodings here,
522
+ # but there's not really a good way to do that
523
+ # without manually checking that each encoding
524
+ # encodes all ASCII characters properly,
525
+ # which takes long enough to affect the startup time of the CLI.
526
+ ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
527
+
528
+ CHARSET_REGEXPS = Hash.new do |h, e|
529
+ h[e] =
530
+ begin
531
+ # /\A(?:\uFEFF)?@charset "(.*?)"|\A(\uFEFF)/
532
+ Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
533
+ _enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
534
+ _enc("\uFEFF", e)})/)
535
+ rescue
536
+ # /\A@charset "(.*?)"/
537
+ Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
538
+ end
539
+ end
540
+ end
541
+
542
+ # Checks to see if a class has a given method.
543
+ # For example:
544
+ #
545
+ # Sass::Util.has?(:public_instance_method, String, :gsub) #=> true
546
+ #
547
+ # Method collections like `Class#instance_methods`
548
+ # return strings in Ruby 1.8 and symbols in Ruby 1.9 and on,
549
+ # so this handles checking for them in a compatible way.
550
+ #
551
+ # @param attr [#to_s] The (singular) name of the method-collection method
552
+ # (e.g. `:instance_methods`, `:private_methods`)
553
+ # @param klass [Module] The class to check the methods of which to check
554
+ # @param method [String, Symbol] The name of the method do check for
555
+ # @return [Boolean] Whether or not the given collection has the given method
556
+ def has?(attr, klass, method)
557
+ klass.send("#{attr}s").include?(ruby1_8? ? method.to_s : method.to_sym)
558
+ end
559
+
560
+ # A version of `Enumerable#enum_with_index` that works in Ruby 1.8 and 1.9.
561
+ #
562
+ # @param enum [Enumerable] The enumerable to get the enumerator for
563
+ # @return [Enumerator] The with-index enumerator
564
+ def enum_with_index(enum)
565
+ ruby1_8? ? enum.enum_with_index : enum.each_with_index
566
+ end
567
+
568
+ # A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
569
+ #
570
+ # @param enum [Enumerable] The enumerable to get the enumerator for
571
+ # @param n [Fixnum] The size of each cons
572
+ # @return [Enumerator] The consed enumerator
573
+ def enum_cons(enum, n)
574
+ ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
575
+ end
576
+
577
+ # A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
578
+ #
579
+ # @param enum [Enumerable] The enumerable to get the enumerator for
580
+ # @param n [Fixnum] The size of each slice
581
+ # @return [Enumerator] The consed enumerator
582
+ def enum_slice(enum, n)
583
+ ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
584
+ end
585
+
586
+ # Returns the ASCII code of the given character.
587
+ #
588
+ # @param c [String] All characters but the first are ignored.
589
+ # @return [Fixnum] The ASCII code of `c`.
590
+ def ord(c)
591
+ ruby1_8? ? c[0] : c.ord
592
+ end
593
+
594
+ # Flattens the first `n` nested arrays in a cross-version manner.
595
+ #
596
+ # @param arr [Array] The array to flatten
597
+ # @param n [Fixnum] The number of levels to flatten
598
+ # @return [Array] The flattened array
599
+ def flatten(arr, n)
600
+ return arr.flatten(n) unless ruby1_8_6?
601
+ return arr if n == 0
602
+ arr.inject([]) {|res, e| e.is_a?(Array) ? res.concat(flatten(e, n - 1)) : res << e}
603
+ end
604
+
605
+ # Returns the hash code for a set in a cross-version manner.
606
+ # Aggravatingly, this is order-dependent in Ruby 1.8.6.
607
+ #
608
+ # @param set [Set]
609
+ # @return [Fixnum] The order-independent hashcode of `set`
610
+ def set_hash(set)
611
+ return set.hash unless ruby1_8_6?
612
+ set.map {|e| e.hash}.uniq.sort.hash
613
+ end
614
+
615
+ # Tests the hash-equality of two sets in a cross-version manner.
616
+ # Aggravatingly, this is order-dependent in Ruby 1.8.6.
617
+ #
618
+ # @param set1 [Set]
619
+ # @param set2 [Set]
620
+ # @return [Boolean] Whether or not the sets are hashcode equal
621
+ def set_eql?(set1, set2)
622
+ return set1.eql?(set2) unless ruby1_8_6?
623
+ set1.to_a.uniq.sort_by {|e| e.hash}.eql?(set2.to_a.uniq.sort_by {|e| e.hash})
624
+ end
625
+
626
+ ## Static Method Stuff
627
+
628
+ # The context in which the ERB for \{#def\_static\_method} will be run.
629
+ class StaticConditionalContext
630
+ # @param set [#include?] The set of variables that are defined for this context.
631
+ def initialize(set)
632
+ @set = set
633
+ end
634
+
635
+ # Checks whether or not a variable is defined for this context.
636
+ #
637
+ # @param name [Symbol] The name of the variable
638
+ # @return [Boolean]
639
+ def method_missing(name, *args, &block)
640
+ super unless args.empty? && block.nil?
641
+ @set.include?(name)
642
+ end
643
+ end
644
+
645
+ private
646
+
647
+ # Calculates the memoization table for the Least Common Subsequence algorithm.
648
+ # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
649
+ def lcs_table(x, y)
650
+ c = Array.new(x.size) {[]}
651
+ x.size.times {|i| c[i][0] = 0}
652
+ y.size.times {|j| c[0][j] = 0}
653
+ (1...x.size).each do |i|
654
+ (1...y.size).each do |j|
655
+ c[i][j] =
656
+ if yield x[i], y[j]
657
+ c[i-1][j-1] + 1
658
+ else
659
+ [c[i][j-1], c[i-1][j]].max
660
+ end
661
+ end
662
+ end
663
+ return c
664
+ end
665
+
666
+ # Computes a single longest common subsequence for arrays x and y.
667
+ # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
668
+ def lcs_backtrace(c, x, y, i, j, &block)
669
+ return [] if i == 0 || j == 0
670
+ if v = yield(x[i], y[j])
671
+ return lcs_backtrace(c, x, y, i-1, j-1, &block) << v
672
+ end
673
+
674
+ return lcs_backtrace(c, x, y, i, j-1, &block) if c[i][j-1] > c[i-1][j]
675
+ return lcs_backtrace(c, x, y, i-1, j, &block)
676
+ end
677
+
678
+ def convert_and_dump(obj)
679
+ if obj.is_a?(Array)
680
+ obj = obj.map {|e| dump(e)}
681
+ elsif obj.is_a?(Hash)
682
+ obj = map_hash(obj) {|k, v| [dump(k), dump(v)]}
683
+ end
684
+ Marshal.dump(obj)
685
+ end
686
+ end
687
+ end