xass 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +11 -0
  3. data/CONTRIBUTING +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +201 -0
  6. data/Rakefile +349 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/push +13 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/update_watch.rb +13 -0
  14. data/init.rb +18 -0
  15. data/lib/sass/cache_stores/base.rb +88 -0
  16. data/lib/sass/cache_stores/chain.rb +33 -0
  17. data/lib/sass/cache_stores/filesystem.rb +64 -0
  18. data/lib/sass/cache_stores/memory.rb +47 -0
  19. data/lib/sass/cache_stores/null.rb +25 -0
  20. data/lib/sass/cache_stores.rb +15 -0
  21. data/lib/sass/callbacks.rb +66 -0
  22. data/lib/sass/css.rb +409 -0
  23. data/lib/sass/engine.rb +930 -0
  24. data/lib/sass/environment.rb +101 -0
  25. data/lib/sass/error.rb +201 -0
  26. data/lib/sass/exec.rb +707 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +186 -0
  29. data/lib/sass/importers.rb +22 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/logger.rb +15 -0
  33. data/lib/sass/media.rb +213 -0
  34. data/lib/sass/plugin/compiler.rb +406 -0
  35. data/lib/sass/plugin/configuration.rb +123 -0
  36. data/lib/sass/plugin/generic.rb +15 -0
  37. data/lib/sass/plugin/merb.rb +48 -0
  38. data/lib/sass/plugin/rack.rb +60 -0
  39. data/lib/sass/plugin/rails.rb +47 -0
  40. data/lib/sass/plugin/staleness_checker.rb +199 -0
  41. data/lib/sass/plugin.rb +133 -0
  42. data/lib/sass/railtie.rb +10 -0
  43. data/lib/sass/repl.rb +57 -0
  44. data/lib/sass/root.rb +7 -0
  45. data/lib/sass/script/arg_list.rb +52 -0
  46. data/lib/sass/script/bool.rb +18 -0
  47. data/lib/sass/script/color.rb +606 -0
  48. data/lib/sass/script/css_lexer.rb +29 -0
  49. data/lib/sass/script/css_parser.rb +31 -0
  50. data/lib/sass/script/funcall.rb +245 -0
  51. data/lib/sass/script/functions.rb +1543 -0
  52. data/lib/sass/script/interpolation.rb +79 -0
  53. data/lib/sass/script/lexer.rb +345 -0
  54. data/lib/sass/script/list.rb +85 -0
  55. data/lib/sass/script/literal.rb +221 -0
  56. data/lib/sass/script/node.rb +99 -0
  57. data/lib/sass/script/null.rb +37 -0
  58. data/lib/sass/script/number.rb +453 -0
  59. data/lib/sass/script/operation.rb +110 -0
  60. data/lib/sass/script/parser.rb +502 -0
  61. data/lib/sass/script/string.rb +51 -0
  62. data/lib/sass/script/string_interpolation.rb +103 -0
  63. data/lib/sass/script/unary_operation.rb +69 -0
  64. data/lib/sass/script/variable.rb +58 -0
  65. data/lib/sass/script.rb +39 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1180 -0
  68. data/lib/sass/scss/rx.rb +133 -0
  69. data/lib/sass/scss/script_lexer.rb +15 -0
  70. data/lib/sass/scss/script_parser.rb +25 -0
  71. data/lib/sass/scss/static_parser.rb +54 -0
  72. data/lib/sass/scss.rb +16 -0
  73. data/lib/sass/selector/abstract_sequence.rb +94 -0
  74. data/lib/sass/selector/comma_sequence.rb +92 -0
  75. data/lib/sass/selector/sequence.rb +507 -0
  76. data/lib/sass/selector/simple.rb +119 -0
  77. data/lib/sass/selector/simple_sequence.rb +215 -0
  78. data/lib/sass/selector.rb +452 -0
  79. data/lib/sass/shared.rb +76 -0
  80. data/lib/sass/supports.rb +229 -0
  81. data/lib/sass/tree/charset_node.rb +22 -0
  82. data/lib/sass/tree/comment_node.rb +82 -0
  83. data/lib/sass/tree/content_node.rb +9 -0
  84. data/lib/sass/tree/css_import_node.rb +60 -0
  85. data/lib/sass/tree/debug_node.rb +18 -0
  86. data/lib/sass/tree/directive_node.rb +42 -0
  87. data/lib/sass/tree/each_node.rb +24 -0
  88. data/lib/sass/tree/extend_node.rb +36 -0
  89. data/lib/sass/tree/for_node.rb +36 -0
  90. data/lib/sass/tree/function_node.rb +34 -0
  91. data/lib/sass/tree/if_node.rb +52 -0
  92. data/lib/sass/tree/import_node.rb +75 -0
  93. data/lib/sass/tree/media_node.rb +58 -0
  94. data/lib/sass/tree/mixin_def_node.rb +38 -0
  95. data/lib/sass/tree/mixin_node.rb +39 -0
  96. data/lib/sass/tree/node.rb +196 -0
  97. data/lib/sass/tree/prop_node.rb +152 -0
  98. data/lib/sass/tree/return_node.rb +18 -0
  99. data/lib/sass/tree/root_node.rb +78 -0
  100. data/lib/sass/tree/rule_node.rb +132 -0
  101. data/lib/sass/tree/supports_node.rb +51 -0
  102. data/lib/sass/tree/trace_node.rb +32 -0
  103. data/lib/sass/tree/variable_node.rb +30 -0
  104. data/lib/sass/tree/visitors/base.rb +75 -0
  105. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  106. data/lib/sass/tree/visitors/convert.rb +316 -0
  107. data/lib/sass/tree/visitors/cssize.rb +241 -0
  108. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  109. data/lib/sass/tree/visitors/extend.rb +68 -0
  110. data/lib/sass/tree/visitors/perform.rb +446 -0
  111. data/lib/sass/tree/visitors/set_options.rb +125 -0
  112. data/lib/sass/tree/visitors/to_css.rb +228 -0
  113. data/lib/sass/tree/warn_node.rb +18 -0
  114. data/lib/sass/tree/while_node.rb +18 -0
  115. data/lib/sass/util/multibyte_string_scanner.rb +155 -0
  116. data/lib/sass/util/subset_map.rb +109 -0
  117. data/lib/sass/util/test.rb +10 -0
  118. data/lib/sass/util.rb +948 -0
  119. data/lib/sass/version.rb +126 -0
  120. data/lib/sass.rb +95 -0
  121. data/rails/init.rb +1 -0
  122. data/test/Gemfile +3 -0
  123. data/test/Gemfile.lock +10 -0
  124. data/test/sass/cache_test.rb +89 -0
  125. data/test/sass/callbacks_test.rb +61 -0
  126. data/test/sass/conversion_test.rb +1760 -0
  127. data/test/sass/css2sass_test.rb +458 -0
  128. data/test/sass/data/hsl-rgb.txt +319 -0
  129. data/test/sass/engine_test.rb +3244 -0
  130. data/test/sass/exec_test.rb +86 -0
  131. data/test/sass/extend_test.rb +1482 -0
  132. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  133. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  134. data/test/sass/functions_test.rb +1139 -0
  135. data/test/sass/importer_test.rb +192 -0
  136. data/test/sass/logger_test.rb +58 -0
  137. data/test/sass/mock_importer.rb +49 -0
  138. data/test/sass/more_results/more1.css +9 -0
  139. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  140. data/test/sass/more_results/more_import.css +29 -0
  141. data/test/sass/more_templates/_more_partial.sass +2 -0
  142. data/test/sass/more_templates/more1.sass +23 -0
  143. data/test/sass/more_templates/more_import.sass +11 -0
  144. data/test/sass/plugin_test.rb +564 -0
  145. data/test/sass/results/alt.css +4 -0
  146. data/test/sass/results/basic.css +9 -0
  147. data/test/sass/results/cached_import_option.css +3 -0
  148. data/test/sass/results/compact.css +5 -0
  149. data/test/sass/results/complex.css +86 -0
  150. data/test/sass/results/compressed.css +1 -0
  151. data/test/sass/results/expanded.css +19 -0
  152. data/test/sass/results/filename_fn.css +3 -0
  153. data/test/sass/results/if.css +3 -0
  154. data/test/sass/results/import.css +31 -0
  155. data/test/sass/results/import_charset.css +5 -0
  156. data/test/sass/results/import_charset_1_8.css +5 -0
  157. data/test/sass/results/import_charset_ibm866.css +5 -0
  158. data/test/sass/results/import_content.css +1 -0
  159. data/test/sass/results/line_numbers.css +49 -0
  160. data/test/sass/results/mixins.css +95 -0
  161. data/test/sass/results/multiline.css +24 -0
  162. data/test/sass/results/nested.css +22 -0
  163. data/test/sass/results/options.css +1 -0
  164. data/test/sass/results/parent_ref.css +13 -0
  165. data/test/sass/results/script.css +16 -0
  166. data/test/sass/results/scss_import.css +31 -0
  167. data/test/sass/results/scss_importee.css +2 -0
  168. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  169. data/test/sass/results/subdir/subdir.css +3 -0
  170. data/test/sass/results/units.css +11 -0
  171. data/test/sass/results/warn.css +0 -0
  172. data/test/sass/results/warn_imported.css +0 -0
  173. data/test/sass/script_conversion_test.rb +299 -0
  174. data/test/sass/script_test.rb +622 -0
  175. data/test/sass/scss/css_test.rb +1100 -0
  176. data/test/sass/scss/rx_test.rb +156 -0
  177. data/test/sass/scss/scss_test.rb +2106 -0
  178. data/test/sass/scss/test_helper.rb +37 -0
  179. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  180. data/test/sass/templates/_double_import_loop2.sass +1 -0
  181. data/test/sass/templates/_filename_fn_import.scss +11 -0
  182. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  183. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  184. data/test/sass/templates/_imported_content.sass +3 -0
  185. data/test/sass/templates/_partial.sass +2 -0
  186. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  187. data/test/sass/templates/alt.sass +16 -0
  188. data/test/sass/templates/basic.sass +23 -0
  189. data/test/sass/templates/bork1.sass +2 -0
  190. data/test/sass/templates/bork2.sass +2 -0
  191. data/test/sass/templates/bork3.sass +2 -0
  192. data/test/sass/templates/bork4.sass +2 -0
  193. data/test/sass/templates/bork5.sass +3 -0
  194. data/test/sass/templates/cached_import_option.scss +3 -0
  195. data/test/sass/templates/compact.sass +17 -0
  196. data/test/sass/templates/complex.sass +305 -0
  197. data/test/sass/templates/compressed.sass +15 -0
  198. data/test/sass/templates/double_import_loop1.sass +1 -0
  199. data/test/sass/templates/expanded.sass +17 -0
  200. data/test/sass/templates/filename_fn.scss +18 -0
  201. data/test/sass/templates/if.sass +11 -0
  202. data/test/sass/templates/import.sass +12 -0
  203. data/test/sass/templates/import_charset.sass +9 -0
  204. data/test/sass/templates/import_charset_1_8.sass +6 -0
  205. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  206. data/test/sass/templates/import_content.sass +4 -0
  207. data/test/sass/templates/importee.less +2 -0
  208. data/test/sass/templates/importee.sass +19 -0
  209. data/test/sass/templates/line_numbers.sass +13 -0
  210. data/test/sass/templates/mixin_bork.sass +5 -0
  211. data/test/sass/templates/mixins.sass +76 -0
  212. data/test/sass/templates/multiline.sass +20 -0
  213. data/test/sass/templates/nested.sass +25 -0
  214. data/test/sass/templates/nested_bork1.sass +2 -0
  215. data/test/sass/templates/nested_bork2.sass +2 -0
  216. data/test/sass/templates/nested_bork3.sass +2 -0
  217. data/test/sass/templates/nested_bork4.sass +2 -0
  218. data/test/sass/templates/nested_import.sass +2 -0
  219. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  220. data/test/sass/templates/options.sass +2 -0
  221. data/test/sass/templates/parent_ref.sass +25 -0
  222. data/test/sass/templates/same_name_different_ext.sass +2 -0
  223. data/test/sass/templates/same_name_different_ext.scss +1 -0
  224. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  225. data/test/sass/templates/script.sass +101 -0
  226. data/test/sass/templates/scss_import.scss +11 -0
  227. data/test/sass/templates/scss_importee.scss +1 -0
  228. data/test/sass/templates/single_import_loop.sass +1 -0
  229. data/test/sass/templates/subdir/import_up1.scss +1 -0
  230. data/test/sass/templates/subdir/import_up2.scss +1 -0
  231. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  232. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  233. data/test/sass/templates/subdir/subdir.sass +6 -0
  234. data/test/sass/templates/units.sass +11 -0
  235. data/test/sass/templates/warn.sass +3 -0
  236. data/test/sass/templates/warn_imported.sass +4 -0
  237. data/test/sass/test_helper.rb +8 -0
  238. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  239. data/test/sass/util/subset_map_test.rb +91 -0
  240. data/test/sass/util_test.rb +382 -0
  241. data/test/test_helper.rb +80 -0
  242. metadata +354 -0
@@ -0,0 +1,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,345 @@
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
+ })
74
+
75
+ # A list of operator strings ordered with longer names first
76
+ # so that `>` and `<` don't clobber `>=` and `<=`.
77
+ OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
78
+
79
+ # A sub-list of {OP_NAMES} that only includes operators
80
+ # with identifier names.
81
+ IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
82
+
83
+ # A hash of regular expressions that are used for tokenizing.
84
+ REGULAR_EXPRESSIONS = {
85
+ :whitespace => /\s+/,
86
+ :comment => COMMENT,
87
+ :single_line_comment => SINGLE_LINE_COMMENT,
88
+ :variable => /(\$)(#{IDENT})/,
89
+ :ident => /(#{IDENT})(\()?/,
90
+ :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
91
+ :color => HEXCOLOR,
92
+ :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})},
93
+ :op => %r{(#{Regexp.union(*OP_NAMES)})},
94
+ }
95
+
96
+ class << self
97
+ private
98
+ def string_re(open, close)
99
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
100
+ end
101
+ end
102
+
103
+ # A hash of regular expressions that are used for tokenizing strings.
104
+ #
105
+ # The key is a `[Symbol, Boolean]` pair.
106
+ # The symbol represents which style of quotation to use,
107
+ # while the boolean represents whether or not the string
108
+ # is following an interpolated segment.
109
+ STRING_REGULAR_EXPRESSIONS = {
110
+ :double => {
111
+ false => string_re('"', '"'),
112
+ true => string_re('', '"')
113
+ },
114
+ :single => {
115
+ false => string_re("'", "'"),
116
+ true => string_re('', "'")
117
+ },
118
+ :uri => {
119
+ false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
120
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
121
+ },
122
+ # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
123
+ # non-standard version of http://www.w3.org/TR/css3-conditional/
124
+ :url_prefix => {
125
+ false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
126
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
127
+ },
128
+ :domain => {
129
+ false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
130
+ true => /(#{URLCHAR}*?)(#{W}\)|#\{)/
131
+ }
132
+ }
133
+
134
+ # @param str [String, StringScanner] The source text to lex
135
+ # @param line [Fixnum] The line on which the SassScript appears.
136
+ # Used for error reporting
137
+ # @param offset [Fixnum] The number of characters in on which the SassScript appears.
138
+ # Used for error reporting
139
+ # @param options [{Symbol => Object}] An options hash;
140
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
141
+ def initialize(str, line, offset, options)
142
+ @scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
143
+ @line = line
144
+ @offset = offset
145
+ @options = options
146
+ @interpolation_stack = []
147
+ @prev = nil
148
+ end
149
+
150
+ # Moves the lexer forward one token.
151
+ #
152
+ # @return [Token] The token that was moved past
153
+ def next
154
+ @tok ||= read_token
155
+ @tok, tok = nil, @tok
156
+ @prev = tok
157
+ return tok
158
+ end
159
+
160
+ # Returns whether or not there's whitespace before the next token.
161
+ #
162
+ # @return [Boolean]
163
+ def whitespace?(tok = @tok)
164
+ if tok
165
+ @scanner.string[0...tok.pos] =~ /\s\Z/
166
+ else
167
+ @scanner.string[@scanner.pos, 1] =~ /^\s/ ||
168
+ @scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/
169
+ end
170
+ end
171
+
172
+ # Returns the next token without moving the lexer forward.
173
+ #
174
+ # @return [Token] The next token
175
+ def peek
176
+ @tok ||= read_token
177
+ end
178
+
179
+ # Rewinds the underlying StringScanner
180
+ # to before the token returned by \{#peek}.
181
+ def unpeek!
182
+ @scanner.pos = @tok.pos if @tok
183
+ end
184
+
185
+ # @return [Boolean] Whether or not there's more source text to lex.
186
+ def done?
187
+ whitespace unless after_interpolation? && @interpolation_stack.last
188
+ @scanner.eos? && @tok.nil?
189
+ end
190
+
191
+ # @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
192
+ def after_interpolation?
193
+ @prev && @prev.type == :end_interpolation
194
+ end
195
+
196
+ # Raise an error to the effect that `name` was expected in the input stream
197
+ # and wasn't found.
198
+ #
199
+ # This calls \{#unpeek!} to rewind the scanner to immediately after
200
+ # the last returned token.
201
+ #
202
+ # @param name [String] The name of the entity that was expected but not found
203
+ # @raise [Sass::SyntaxError]
204
+ def expected!(name)
205
+ unpeek!
206
+ Sass::SCSS::Parser.expected(@scanner, name, @line)
207
+ end
208
+
209
+ # Records all non-comment text the lexer consumes within the block
210
+ # and returns it as a string.
211
+ #
212
+ # @yield A block in which text is recorded
213
+ # @return [String]
214
+ def str
215
+ old_pos = @tok ? @tok.pos : @scanner.pos
216
+ yield
217
+ new_pos = @tok ? @tok.pos : @scanner.pos
218
+ @scanner.string[old_pos...new_pos]
219
+ end
220
+
221
+ private
222
+
223
+ def read_token
224
+ return if done?
225
+ return unless value = token
226
+ type, val, size = value
227
+ size ||= @scanner.matched_size
228
+
229
+ val.line = @line if val.is_a?(Script::Node)
230
+ Token.new(type, val, @line,
231
+ current_position - size, @scanner.pos - size)
232
+ end
233
+
234
+ def whitespace
235
+ nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
236
+ scan(REGULAR_EXPRESSIONS[:comment]) ||
237
+ scan(REGULAR_EXPRESSIONS[:single_line_comment])
238
+ end
239
+
240
+ def token
241
+ if after_interpolation? && (interp_type = @interpolation_stack.pop)
242
+ return string(interp_type, true)
243
+ end
244
+
245
+ variable || string(:double, false) || string(:single, false) || number || color ||
246
+ string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val || ident_op ||
247
+ ident || op
248
+ end
249
+
250
+ def variable
251
+ _variable(REGULAR_EXPRESSIONS[:variable])
252
+ end
253
+
254
+ def _variable(rx)
255
+ return unless scan(rx)
256
+
257
+ [:const, @scanner[2]]
258
+ end
259
+
260
+ def ident
261
+ return unless scan(REGULAR_EXPRESSIONS[:ident])
262
+ [@scanner[2] ? :funcall : :ident, @scanner[1]]
263
+ end
264
+
265
+ def string(re, open)
266
+ return unless scan(STRING_REGULAR_EXPRESSIONS[re][open])
267
+ if @scanner[2] == '#{' #'
268
+ @scanner.pos -= 2 # Don't actually consume the #{
269
+ @interpolation_stack << re
270
+ end
271
+ str =
272
+ if re == :uri
273
+ Script::String.new("#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}")
274
+ else
275
+ Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
276
+ end
277
+ [:string, str]
278
+ end
279
+
280
+ def number
281
+ return unless scan(REGULAR_EXPRESSIONS[:number])
282
+ value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
283
+ value = -value if @scanner[1]
284
+ [:number, Script::Number.new(value, Array(@scanner[4]))]
285
+ end
286
+
287
+ def color
288
+ return unless s = scan(REGULAR_EXPRESSIONS[:color])
289
+ raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
290
+ Colors must have either three or six digits: '#{s}'
291
+ MESSAGE
292
+ value = s.scan(/^#(..?)(..?)(..?)$/).first.
293
+ map {|num| num.ljust(2, num).to_i(16)}
294
+ [:color, Script::Color.new(value)]
295
+ end
296
+
297
+ def special_fun
298
+ return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
299
+ str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
300
+ c = str2.count("\n")
301
+ old_line = @line
302
+ old_offset = @offset
303
+ @line += c
304
+ @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
305
+ [:special_fun,
306
+ Sass::Util.merge_adjacent_strings(
307
+ [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
308
+ str1.size + str2.size]
309
+ end
310
+
311
+ def special_val
312
+ return unless scan(/!important/i)
313
+ [:string, Script::String.new("!important")]
314
+ end
315
+
316
+ def ident_op
317
+ return unless op = scan(REGULAR_EXPRESSIONS[:ident_op])
318
+ [OPERATORS[op]]
319
+ end
320
+
321
+ def op
322
+ return unless op = scan(REGULAR_EXPRESSIONS[:op])
323
+ @interpolation_stack << nil if op == :begin_interpolation
324
+ [OPERATORS[op]]
325
+ end
326
+
327
+ def raw(rx)
328
+ return unless val = scan(rx)
329
+ [:raw, val]
330
+ end
331
+
332
+ def scan(re)
333
+ return unless str = @scanner.scan(re)
334
+ c = str.count("\n")
335
+ @line += c
336
+ @offset = (c == 0 ? @offset + str.size : str[/\n(.*)/, 1].size)
337
+ str
338
+ end
339
+
340
+ def current_position
341
+ @offset + 1
342
+ end
343
+ end
344
+ end
345
+ 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