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,79 @@
1
+ module Sass::Script
2
+ # A SassScript object representing `#{}` interpolation outside a string.
3
+ #
4
+ # @see StringInterpolation
5
+ class Interpolation < Node
6
+ # Interpolation in a property is of the form `before #{mid} after`.
7
+ #
8
+ # @param before [Node] The SassScript before the interpolation
9
+ # @param mid [Node] The SassScript within the interpolation
10
+ # @param after [Node] The SassScript after the interpolation
11
+ # @param wb [Boolean] Whether there was whitespace between `before` and `#{`
12
+ # @param wa [Boolean] Whether there was whitespace between `}` and `after`
13
+ # @param originally_text [Boolean]
14
+ # Whether the original format of the interpolation was plain text,
15
+ # not an interpolation.
16
+ # This is used when converting back to SassScript.
17
+ def initialize(before, mid, after, wb, wa, originally_text = false)
18
+ @before = before
19
+ @mid = mid
20
+ @after = after
21
+ @whitespace_before = wb
22
+ @whitespace_after = wa
23
+ @originally_text = originally_text
24
+ end
25
+
26
+ # @return [String] A human-readable s-expression representation of the interpolation
27
+ def inspect
28
+ "(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
29
+ end
30
+
31
+ # @see Node#to_sass
32
+ def to_sass(opts = {})
33
+ res = ""
34
+ res << @before.to_sass(opts) if @before
35
+ res << ' ' if @before && @whitespace_before
36
+ res << '#{' unless @originally_text
37
+ res << @mid.to_sass(opts)
38
+ res << '}' unless @originally_text
39
+ res << ' ' if @after && @whitespace_after
40
+ res << @after.to_sass(opts) if @after
41
+ res
42
+ end
43
+
44
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
45
+ #
46
+ # @return [Array<Node>]
47
+ # @see #initialize
48
+ # @see Node#children
49
+ def children
50
+ [@before, @mid, @after].compact
51
+ end
52
+
53
+ # @see Node#deep_copy
54
+ def deep_copy
55
+ node = dup
56
+ node.instance_variable_set('@before', @before.deep_copy) if @before
57
+ node.instance_variable_set('@mid', @mid.deep_copy)
58
+ node.instance_variable_set('@after', @after.deep_copy) if @after
59
+ node
60
+ end
61
+
62
+ protected
63
+
64
+ # Evaluates the interpolation.
65
+ #
66
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
67
+ # @return [Sass::Script::String] The SassScript string that is the value of the interpolation
68
+ def _perform(environment)
69
+ res = ""
70
+ res << @before.perform(environment).to_s if @before
71
+ res << " " if @before && @whitespace_before
72
+ val = @mid.perform(environment)
73
+ res << (val.is_a?(Sass::Script::String) ? val.value : val.to_s)
74
+ res << " " if @after && @whitespace_after
75
+ res << @after.perform(environment).to_s if @after
76
+ opts(Sass::Script::String.new(res))
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,348 @@
1
+ require 'sass/scss/rx'
2
+
3
+ module Sass
4
+ module Script
5
+ # The lexical analyzer for SassScript.
6
+ # It takes a raw string and converts it to individual tokens
7
+ # that are easier to parse.
8
+ class Lexer
9
+ include Sass::SCSS::RX
10
+
11
+ # A struct containing information about an individual token.
12
+ #
13
+ # `type`: \[`Symbol`\]
14
+ # : The type of token.
15
+ #
16
+ # `value`: \[`Object`\]
17
+ # : The Ruby object corresponding to the value of the token.
18
+ #
19
+ # `line`: \[`Fixnum`\]
20
+ # : The line of the source file on which the token appears.
21
+ #
22
+ # `offset`: \[`Fixnum`\]
23
+ # : The number of bytes into the line the SassScript token appeared.
24
+ #
25
+ # `pos`: \[`Fixnum`\]
26
+ # : The scanner position at which the SassScript token appeared.
27
+ Token = Struct.new(:type, :value, :line, :offset, :pos)
28
+
29
+ # The line number of the lexer's current position.
30
+ #
31
+ # @return [Fixnum]
32
+ attr_reader :line
33
+
34
+ # The number of bytes into the current line
35
+ # of the lexer's current position.
36
+ #
37
+ # @return [Fixnum]
38
+ attr_reader :offset
39
+
40
+ # A hash from operator strings to the corresponding token types.
41
+ OPERATORS = {
42
+ '+' => :plus,
43
+ '-' => :minus,
44
+ '*' => :times,
45
+ '/' => :div,
46
+ '%' => :mod,
47
+ '=' => :single_eq,
48
+ ':' => :colon,
49
+ '(' => :lparen,
50
+ ')' => :rparen,
51
+ ',' => :comma,
52
+ 'and' => :and,
53
+ 'or' => :or,
54
+ 'not' => :not,
55
+ '==' => :eq,
56
+ '!=' => :neq,
57
+ '>=' => :gte,
58
+ '<=' => :lte,
59
+ '>' => :gt,
60
+ '<' => :lt,
61
+ '#{' => :begin_interpolation,
62
+ '}' => :end_interpolation,
63
+ ';' => :semicolon,
64
+ '{' => :lcurly,
65
+ '...' => :splat,
66
+ }
67
+
68
+ OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
69
+
70
+ TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge({
71
+ :const => "variable (e.g. $foo)",
72
+ :ident => "identifier (e.g. middle)",
73
+ :bool => "boolean (e.g. true, false)",
74
+ })
75
+
76
+ # A list of operator strings ordered with longer names first
77
+ # so that `>` and `<` don't clobber `>=` and `<=`.
78
+ OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
79
+
80
+ # A sub-list of {OP_NAMES} that only includes operators
81
+ # with identifier names.
82
+ IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
83
+
84
+ # A hash of regular expressions that are used for tokenizing.
85
+ REGULAR_EXPRESSIONS = {
86
+ :whitespace => /\s+/,
87
+ :comment => COMMENT,
88
+ :single_line_comment => SINGLE_LINE_COMMENT,
89
+ :variable => /(\$)(#{IDENT})/,
90
+ :ident => /(#{IDENT})(\()?/,
91
+ :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
92
+ :color => HEXCOLOR,
93
+ :bool => /(true|false)\b/,
94
+ :null => /null\b/,
95
+ :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})},
96
+ :op => %r{(#{Regexp.union(*OP_NAMES)})},
97
+ }
98
+
99
+ class << self
100
+ private
101
+ def string_re(open, close)
102
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
103
+ end
104
+ end
105
+
106
+ # A hash of regular expressions that are used for tokenizing strings.
107
+ #
108
+ # The key is a `[Symbol, Boolean]` pair.
109
+ # The symbol represents which style of quotation to use,
110
+ # while the boolean represents whether or not the string
111
+ # is following an interpolated segment.
112
+ STRING_REGULAR_EXPRESSIONS = {
113
+ [:double, false] => string_re('"', '"'),
114
+ [:single, false] => string_re("'", "'"),
115
+ [:double, true] => string_re('', '"'),
116
+ [:single, true] => string_re('', "'"),
117
+ [:uri, false] => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
118
+ [:uri, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
119
+ # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
120
+ # non-standard version of http://www.w3.org/TR/css3-conditional/
121
+ [:url_prefix, false] => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
122
+ [:url_prefix, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
123
+ [:domain, false] => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
124
+ [:domain, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
125
+ }
126
+
127
+ # @param str [String, StringScanner] The source text to lex
128
+ # @param line [Fixnum] The line on which the SassScript appears.
129
+ # Used for error reporting
130
+ # @param offset [Fixnum] The number of characters in on which the SassScript appears.
131
+ # Used for error reporting
132
+ # @param options [{Symbol => Object}] An options hash;
133
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
134
+ def initialize(str, line, offset, options)
135
+ @scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
136
+ @line = line
137
+ @offset = offset
138
+ @options = options
139
+ @interpolation_stack = []
140
+ @prev = nil
141
+ end
142
+
143
+ # Moves the lexer forward one token.
144
+ #
145
+ # @return [Token] The token that was moved past
146
+ def next
147
+ @tok ||= read_token
148
+ @tok, tok = nil, @tok
149
+ @prev = tok
150
+ return tok
151
+ end
152
+
153
+ # Returns whether or not there's whitespace before the next token.
154
+ #
155
+ # @return [Boolean]
156
+ def whitespace?(tok = @tok)
157
+ if tok
158
+ @scanner.string[0...tok.pos] =~ /\s\Z/
159
+ else
160
+ @scanner.string[@scanner.pos, 1] =~ /^\s/ ||
161
+ @scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/
162
+ end
163
+ end
164
+
165
+ # Returns the next token without moving the lexer forward.
166
+ #
167
+ # @return [Token] The next token
168
+ def peek
169
+ @tok ||= read_token
170
+ end
171
+
172
+ # Rewinds the underlying StringScanner
173
+ # to before the token returned by \{#peek}.
174
+ def unpeek!
175
+ @scanner.pos = @tok.pos if @tok
176
+ end
177
+
178
+ # @return [Boolean] Whether or not there's more source text to lex.
179
+ def done?
180
+ whitespace unless after_interpolation? && @interpolation_stack.last
181
+ @scanner.eos? && @tok.nil?
182
+ end
183
+
184
+ # @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
185
+ def after_interpolation?
186
+ @prev && @prev.type == :end_interpolation
187
+ end
188
+
189
+ # Raise an error to the effect that `name` was expected in the input stream
190
+ # and wasn't found.
191
+ #
192
+ # This calls \{#unpeek!} to rewind the scanner to immediately after
193
+ # the last returned token.
194
+ #
195
+ # @param name [String] The name of the entity that was expected but not found
196
+ # @raise [Sass::SyntaxError]
197
+ def expected!(name)
198
+ unpeek!
199
+ Sass::SCSS::Parser.expected(@scanner, name, @line)
200
+ end
201
+
202
+ # Records all non-comment text the lexer consumes within the block
203
+ # and returns it as a string.
204
+ #
205
+ # @yield A block in which text is recorded
206
+ # @return [String]
207
+ def str
208
+ old_pos = @tok ? @tok.pos : @scanner.pos
209
+ yield
210
+ new_pos = @tok ? @tok.pos : @scanner.pos
211
+ @scanner.string[old_pos...new_pos]
212
+ end
213
+
214
+ private
215
+
216
+ def read_token
217
+ return if done?
218
+ return unless value = token
219
+ type, val, size = value
220
+ size ||= @scanner.matched_size
221
+
222
+ val.line = @line if val.is_a?(Script::Node)
223
+ Token.new(type, val, @line,
224
+ current_position - size, @scanner.pos - size)
225
+ end
226
+
227
+ def whitespace
228
+ nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
229
+ scan(REGULAR_EXPRESSIONS[:comment]) ||
230
+ scan(REGULAR_EXPRESSIONS[:single_line_comment])
231
+ end
232
+
233
+ def token
234
+ if after_interpolation? && (interp_type = @interpolation_stack.pop)
235
+ return string(interp_type, true)
236
+ end
237
+
238
+ variable || string(:double, false) || string(:single, false) || number ||
239
+ color || bool || null || string(:uri, false) || raw(UNICODERANGE) ||
240
+ special_fun || special_val || ident_op || ident || op
241
+ end
242
+
243
+ def variable
244
+ _variable(REGULAR_EXPRESSIONS[:variable])
245
+ end
246
+
247
+ def _variable(rx)
248
+ return unless scan(rx)
249
+
250
+ [:const, @scanner[2]]
251
+ end
252
+
253
+ def ident
254
+ return unless scan(REGULAR_EXPRESSIONS[:ident])
255
+ [@scanner[2] ? :funcall : :ident, @scanner[1]]
256
+ end
257
+
258
+ def string(re, open)
259
+ return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
260
+ if @scanner[2] == '#{' #'
261
+ @scanner.pos -= 2 # Don't actually consume the #{
262
+ @interpolation_stack << re
263
+ end
264
+ str =
265
+ if re == :uri
266
+ Script::String.new("#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}")
267
+ else
268
+ Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
269
+ end
270
+ [:string, str]
271
+ end
272
+
273
+ def number
274
+ return unless scan(REGULAR_EXPRESSIONS[:number])
275
+ value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
276
+ value = -value if @scanner[1]
277
+ [:number, Script::Number.new(value, Array(@scanner[4]))]
278
+ end
279
+
280
+ def color
281
+ return unless s = scan(REGULAR_EXPRESSIONS[:color])
282
+ raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
283
+ Colors must have either three or six digits: '#{s}'
284
+ MESSAGE
285
+ value = s.scan(/^#(..?)(..?)(..?)$/).first.
286
+ map {|num| num.ljust(2, num).to_i(16)}
287
+ [:color, Script::Color.new(value)]
288
+ end
289
+
290
+ def bool
291
+ return unless s = scan(REGULAR_EXPRESSIONS[:bool])
292
+ [:bool, Script::Bool.new(s == 'true')]
293
+ end
294
+
295
+ def null
296
+ return unless scan(REGULAR_EXPRESSIONS[:null])
297
+ [:null, Script::Null.new]
298
+ end
299
+
300
+ def special_fun
301
+ return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
302
+ str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
303
+ c = str2.count("\n")
304
+ old_line = @line
305
+ old_offset = @offset
306
+ @line += c
307
+ @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
308
+ [:special_fun,
309
+ Sass::Util.merge_adjacent_strings(
310
+ [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
311
+ str1.size + str2.size]
312
+ end
313
+
314
+ def special_val
315
+ return unless scan(/!important/i)
316
+ [:string, Script::String.new("!important")]
317
+ end
318
+
319
+ def ident_op
320
+ return unless op = scan(REGULAR_EXPRESSIONS[:ident_op])
321
+ [OPERATORS[op]]
322
+ end
323
+
324
+ def op
325
+ return unless op = scan(REGULAR_EXPRESSIONS[:op])
326
+ @interpolation_stack << nil if op == :begin_interpolation
327
+ [OPERATORS[op]]
328
+ end
329
+
330
+ def raw(rx)
331
+ return unless val = scan(rx)
332
+ [:raw, val]
333
+ end
334
+
335
+ def scan(re)
336
+ return unless str = @scanner.scan(re)
337
+ c = str.count("\n")
338
+ @line += c
339
+ @offset = (c == 0 ? @offset + str.size : str[/\n(.*)/, 1].size)
340
+ str
341
+ end
342
+
343
+ def current_position
344
+ @offset + 1
345
+ end
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,85 @@
1
+ module Sass::Script
2
+ # A SassScript object representing a CSS list.
3
+ # This includes both comma-separated lists and space-separated lists.
4
+ class List < Literal
5
+ # The Ruby array containing the contents of the list.
6
+ #
7
+ # @return [Array<Literal>]
8
+ attr_reader :value
9
+ alias_method :children, :value
10
+ alias_method :to_a, :value
11
+
12
+ # The operator separating the values of the list.
13
+ # Either `:comma` or `:space`.
14
+ #
15
+ # @return [Symbol]
16
+ attr_reader :separator
17
+
18
+ # Creates a new list.
19
+ #
20
+ # @param value [Array<Literal>] See \{#value}
21
+ # @param separator [String] See \{#separator}
22
+ def initialize(value, separator)
23
+ super(value)
24
+ @separator = separator
25
+ end
26
+
27
+ # @see Node#deep_copy
28
+ def deep_copy
29
+ node = dup
30
+ node.instance_variable_set('@value', value.map {|c| c.deep_copy})
31
+ node
32
+ end
33
+
34
+ # @see Node#eq
35
+ def eq(other)
36
+ Sass::Script::Bool.new(
37
+ other.is_a?(List) && self.value == other.value &&
38
+ self.separator == other.separator)
39
+ end
40
+
41
+ # @see Node#to_s
42
+ def to_s(opts = {})
43
+ raise Sass::SyntaxError.new("() isn't a valid CSS value.") if value.empty?
44
+ return value.reject {|e| e.is_a?(Null) || e.is_a?(List) && e.value.empty?}.map {|e| e.to_s(opts)}.join(sep_str)
45
+ end
46
+
47
+ # @see Node#to_sass
48
+ def to_sass(opts = {})
49
+ return "()" if value.empty?
50
+ precedence = Sass::Script::Parser.precedence_of(separator)
51
+ value.reject {|e| e.is_a?(Null)}.map do |v|
52
+ if v.is_a?(List) && Sass::Script::Parser.precedence_of(v.separator) <= precedence ||
53
+ separator == :space && v.is_a?(UnaryOperation) && (v.operator == :minus || v.operator == :plus)
54
+ "(#{v.to_sass(opts)})"
55
+ else
56
+ v.to_sass(opts)
57
+ end
58
+ end.join(sep_str(nil))
59
+ end
60
+
61
+ # @see Node#inspect
62
+ def inspect
63
+ "(#{to_sass})"
64
+ end
65
+
66
+ protected
67
+
68
+ # @see Node#_perform
69
+ def _perform(environment)
70
+ list = Sass::Script::List.new(
71
+ value.map {|e| e.perform(environment)},
72
+ separator)
73
+ list.options = self.options
74
+ list
75
+ end
76
+
77
+ private
78
+
79
+ def sep_str(opts = self.options)
80
+ return ' ' if separator == :space
81
+ return ',' if opts && opts[:style] == :compressed
82
+ return ', '
83
+ end
84
+ end
85
+ end