sass 3.3.14 → 3.4.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +0 -5
  10. data/lib/sass/css.rb +1 -3
  11. data/lib/sass/engine.rb +28 -39
  12. data/lib/sass/environment.rb +13 -17
  13. data/lib/sass/error.rb +6 -9
  14. data/lib/sass/exec.rb +5 -771
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +419 -0
  18. data/lib/sass/features.rb +6 -0
  19. data/lib/sass/importers.rb +0 -1
  20. data/lib/sass/importers/base.rb +5 -1
  21. data/lib/sass/importers/filesystem.rb +4 -21
  22. data/lib/sass/media.rb +1 -4
  23. data/lib/sass/plugin/compiler.rb +32 -136
  24. data/lib/sass/script/css_lexer.rb +1 -1
  25. data/lib/sass/script/functions.rb +363 -39
  26. data/lib/sass/script/lexer.rb +68 -50
  27. data/lib/sass/script/parser.rb +29 -14
  28. data/lib/sass/script/tree.rb +1 -0
  29. data/lib/sass/script/tree/funcall.rb +1 -1
  30. data/lib/sass/script/tree/interpolation.rb +19 -1
  31. data/lib/sass/script/tree/selector.rb +26 -0
  32. data/lib/sass/script/value.rb +0 -1
  33. data/lib/sass/script/value/bool.rb +0 -5
  34. data/lib/sass/script/value/color.rb +32 -12
  35. data/lib/sass/script/value/helpers.rb +107 -0
  36. data/lib/sass/script/value/list.rb +0 -15
  37. data/lib/sass/script/value/null.rb +0 -5
  38. data/lib/sass/script/value/number.rb +60 -14
  39. data/lib/sass/script/value/string.rb +53 -9
  40. data/lib/sass/scss/css_parser.rb +8 -2
  41. data/lib/sass/scss/parser.rb +175 -319
  42. data/lib/sass/scss/rx.rb +14 -5
  43. data/lib/sass/scss/static_parser.rb +298 -1
  44. data/lib/sass/selector.rb +56 -193
  45. data/lib/sass/selector/abstract_sequence.rb +28 -13
  46. data/lib/sass/selector/comma_sequence.rb +91 -12
  47. data/lib/sass/selector/pseudo.rb +256 -0
  48. data/lib/sass/selector/sequence.rb +99 -31
  49. data/lib/sass/selector/simple.rb +14 -25
  50. data/lib/sass/selector/simple_sequence.rb +101 -37
  51. data/lib/sass/shared.rb +1 -1
  52. data/lib/sass/source/map.rb +23 -9
  53. data/lib/sass/stack.rb +0 -6
  54. data/lib/sass/supports.rb +1 -1
  55. data/lib/sass/tree/at_root_node.rb +1 -0
  56. data/lib/sass/tree/directive_node.rb +7 -1
  57. data/lib/sass/tree/error_node.rb +18 -0
  58. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  59. data/lib/sass/tree/prop_node.rb +1 -1
  60. data/lib/sass/tree/rule_node.rb +11 -6
  61. data/lib/sass/tree/visitors/check_nesting.rb +3 -4
  62. data/lib/sass/tree/visitors/convert.rb +8 -17
  63. data/lib/sass/tree/visitors/cssize.rb +12 -24
  64. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  65. data/lib/sass/tree/visitors/perform.rb +43 -28
  66. data/lib/sass/tree/visitors/set_options.rb +5 -0
  67. data/lib/sass/tree/visitors/to_css.rb +14 -13
  68. data/lib/sass/util.rb +94 -90
  69. data/test/sass/cache_test.rb +1 -1
  70. data/test/sass/callbacks_test.rb +1 -1
  71. data/test/sass/compiler_test.rb +5 -14
  72. data/test/sass/conversion_test.rb +47 -1
  73. data/test/sass/css2sass_test.rb +3 -3
  74. data/test/sass/encoding_test.rb +219 -0
  75. data/test/sass/engine_test.rb +128 -191
  76. data/test/sass/exec_test.rb +2 -2
  77. data/test/sass/extend_test.rb +234 -17
  78. data/test/sass/functions_test.rb +268 -213
  79. data/test/sass/importer_test.rb +31 -21
  80. data/test/sass/logger_test.rb +1 -1
  81. data/test/sass/more_results/more_import.css +1 -1
  82. data/test/sass/plugin_test.rb +12 -11
  83. data/test/sass/results/compact.css +1 -1
  84. data/test/sass/results/complex.css +4 -4
  85. data/test/sass/results/expanded.css +1 -1
  86. data/test/sass/results/import.css +1 -1
  87. data/test/sass/results/import_charset_ibm866.css +2 -2
  88. data/test/sass/results/mixins.css +17 -17
  89. data/test/sass/results/nested.css +1 -1
  90. data/test/sass/results/parent_ref.css +2 -2
  91. data/test/sass/results/script.css +3 -3
  92. data/test/sass/results/scss_import.css +1 -1
  93. data/test/sass/script_conversion_test.rb +7 -4
  94. data/test/sass/script_test.rb +202 -79
  95. data/test/sass/scss/css_test.rb +95 -25
  96. data/test/sass/scss/rx_test.rb +4 -4
  97. data/test/sass/scss/scss_test.rb +363 -19
  98. data/test/sass/source_map_test.rb +48 -41
  99. data/test/sass/superselector_test.rb +191 -0
  100. data/test/sass/templates/scss_import.scss +2 -1
  101. data/test/sass/test_helper.rb +1 -1
  102. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  103. data/test/sass/util/normalized_map_test.rb +1 -1
  104. data/test/sass/util/subset_map_test.rb +2 -2
  105. data/test/sass/util_test.rb +1 -1
  106. data/test/sass/value_helpers_test.rb +3 -3
  107. data/test/test_helper.rb +2 -2
  108. metadata +30 -7
  109. data/lib/sass/importers/deprecated_path.rb +0 -51
  110. data/lib/sass/script/value/deprecated_false.rb +0 -55
@@ -0,0 +1,419 @@
1
+ module Sass::Exec
2
+ # The `sass` and `scss` executables.
3
+ class SassScss < Base
4
+ attr_reader :default_syntax
5
+
6
+ # @param args [Array<String>] The command-line arguments
7
+ def initialize(args, default_syntax)
8
+ super(args)
9
+ @options[:sourcemap] = :auto
10
+ @options[:for_engine] = {
11
+ :load_paths => default_sass_path
12
+ }
13
+ @default_syntax = default_syntax
14
+ end
15
+
16
+ protected
17
+
18
+ # Tells optparse how to parse the arguments.
19
+ #
20
+ # @param opts [OptionParser]
21
+ def set_opts(opts)
22
+ opts.banner = <<END
23
+ Usage: #{default_syntax} [options] [INPUT] [OUTPUT]
24
+
25
+ Description:
26
+ Converts SCSS or Sass files to CSS.
27
+ END
28
+
29
+ common_options(opts)
30
+ watching_and_updating(opts)
31
+ input_and_output(opts)
32
+ miscellaneous(opts)
33
+ end
34
+
35
+ # Processes the options set by the command-line arguments,
36
+ # and runs the Sass compiler appropriately.
37
+ def process_result
38
+ require 'sass'
39
+
40
+ if !@options[:update] && !@options[:watch] &&
41
+ @args.first && colon_path?(@args.first)
42
+ if @args.size == 1
43
+ @args = split_colon_path(@args.first)
44
+ else
45
+ @options[:update] = true
46
+ end
47
+ end
48
+ load_compass if @options[:compass]
49
+ return interactive if @options[:interactive]
50
+ return watch_or_update if @options[:watch] || @options[:update]
51
+ super
52
+
53
+ if @options[:sourcemap] != :none && @options[:output_filename]
54
+ @options[:sourcemap_filename] = Sass::Util.sourcemap_name(@options[:output_filename])
55
+ end
56
+
57
+ @options[:for_engine][:filename] = @options[:filename]
58
+ @options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
59
+ @options[:for_engine][:sourcemap_filename] = @options[:sourcemap_filename]
60
+ @options[:for_engine][:sourcemap] = @options[:sourcemap]
61
+
62
+ run
63
+ end
64
+
65
+ private
66
+
67
+ def common_options(opts)
68
+ opts.separator ''
69
+ opts.separator 'Common Options:'
70
+
71
+ opts.on('-I', '--load-path PATH', 'Specify a Sass import path.') do |path|
72
+ @options[:for_engine][:load_paths] << path
73
+ end
74
+
75
+ opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
76
+ require lib
77
+ end
78
+
79
+ opts.on('--compass', 'Make Compass imports available and load project configuration.') do
80
+ @options[:compass] = true
81
+ end
82
+
83
+ opts.on('-t', '--style NAME', 'Output style. Can be nested (default), compact, ' \
84
+ 'compressed, or expanded.') do |name|
85
+ @options[:for_engine][:style] = name.to_sym
86
+ end
87
+
88
+ opts.on("-?", "-h", "--help", "Show this help message.") do
89
+ puts opts
90
+ exit
91
+ end
92
+
93
+ opts.on("-v", "--version", "Print the Sass version.") do
94
+ puts("Sass #{Sass.version[:string]}")
95
+ exit
96
+ end
97
+ end
98
+
99
+ def watching_and_updating(opts)
100
+ opts.separator ''
101
+ opts.separator 'Watching and Updating:'
102
+
103
+ opts.on('--watch', 'Watch files or directories for changes.',
104
+ 'The location of the generated CSS can be set using a colon:',
105
+ " #{@default_syntax} --watch input.#{@default_syntax}:output.css",
106
+ " #{@default_syntax} --watch input-dir:output-dir") do
107
+ @options[:watch] = true
108
+ end
109
+
110
+ # Polling is used by default on Windows.
111
+ unless Sass::Util.windows?
112
+ opts.on('--poll', 'Check for file changes manually, rather than relying on the OS.',
113
+ 'Only meaningful for --watch.') do
114
+ @options[:poll] = true
115
+ end
116
+ end
117
+
118
+ opts.on('--update', 'Compile files or directories to CSS.',
119
+ 'Locations are set like --watch.') do
120
+ @options[:update] = true
121
+ end
122
+
123
+ opts.on('-f', '--force', 'Recompile every Sass file, even if the CSS file is newer.',
124
+ 'Only meaningful for --update.') do
125
+ @options[:force] = true
126
+ end
127
+
128
+ opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
129
+ 'Only meaningful for --watch and --update.') do
130
+ @options[:stop_on_error] = true
131
+ end
132
+ end
133
+
134
+ def input_and_output(opts)
135
+ opts.separator ''
136
+ opts.separator 'Input and Output:'
137
+
138
+ if @default_syntax == :sass
139
+ opts.on('--scss',
140
+ 'Use the CSS-superset SCSS syntax.') do
141
+ @options[:for_engine][:syntax] = :scss
142
+ end
143
+ else
144
+ opts.on('--sass',
145
+ 'Use the indented Sass syntax.') do
146
+ @options[:for_engine][:syntax] = :sass
147
+ end
148
+ end
149
+
150
+ # This is optional for backwards-compatibility with Sass 3.3, which didn't
151
+ # enable sourcemaps by default and instead used "--sourcemap" to do so.
152
+ opts.on(:OPTIONAL, '--sourcemap=TYPE',
153
+ 'How link generated output to the source files.',
154
+ ' auto (default): relative paths where possible, file URIs elsewhere',
155
+ ' file: always absolute file URIs',
156
+ ' inline: include the source text in the sourcemap',
157
+ ' none: no sourcemaps') do |type|
158
+ if type && !%w[auto file inline none].include?(type)
159
+ $stderr.puts "Unknown sourcemap type #{type}.\n\n"
160
+ $stderr.puts opts
161
+ exit
162
+ end
163
+
164
+ @options[:sourcemap] = (type || :auto).to_sym
165
+ end
166
+
167
+ opts.on('-s', '--stdin', :NONE,
168
+ 'Read input from standard input instead of an input file.',
169
+ 'This is the default if no input file is specified.') do
170
+ @options[:input] = $stdin
171
+ end
172
+
173
+ encoding_option(opts)
174
+
175
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.',
176
+ ('Always true on Unix.' unless Sass::Util.windows?)) do
177
+ @options[:unix_newlines] = true if Sass::Util.windows?
178
+ end
179
+
180
+ opts.on('-g', '--debug-info',
181
+ 'Emit output that can be used by the FireSass Firebug plugin.') do
182
+ @options[:for_engine][:debug_info] = true
183
+ end
184
+
185
+ opts.on('-l', '--line-numbers', '--line-comments',
186
+ 'Emit comments in the generated CSS indicating the corresponding source line.') do
187
+ @options[:for_engine][:line_numbers] = true
188
+ end
189
+ end
190
+
191
+ def miscellaneous(opts)
192
+ opts.separator ''
193
+ opts.separator 'Miscellaneous:'
194
+
195
+ opts.on('-i', '--interactive',
196
+ 'Run an interactive SassScript shell.') do
197
+ @options[:interactive] = true
198
+ end
199
+
200
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
201
+ require 'stringio'
202
+ @options[:check_syntax] = true
203
+ @options[:output] = StringIO.new
204
+ end
205
+
206
+ opts.on('--precision NUMBER_OF_DIGITS', Integer,
207
+ "How many digits of precision to use when outputting decimal numbers.",
208
+ "Defaults to #{Sass::Script::Value::Number.precision}.") do |precision|
209
+ Sass::Script::Value::Number.precision = precision
210
+ end
211
+
212
+ opts.on('--cache-location PATH',
213
+ 'The path to save parsed Sass files. Defaults to .sass-cache.') do |loc|
214
+ @options[:for_engine][:cache_location] = loc
215
+ end
216
+
217
+ opts.on('-C', '--no-cache', "Don't cache parsed Sass files.") do
218
+ @options[:for_engine][:cache] = false
219
+ end
220
+
221
+ opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error.') do
222
+ @options[:trace] = true
223
+ end
224
+
225
+ opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
226
+ @options[:for_engine][:quiet] = true
227
+ end
228
+ end
229
+
230
+ def load_compass
231
+ begin
232
+ require 'compass'
233
+ rescue LoadError
234
+ require 'rubygems'
235
+ begin
236
+ require 'compass'
237
+ rescue LoadError
238
+ puts "ERROR: Cannot load compass."
239
+ exit 1
240
+ end
241
+ end
242
+ Compass.add_project_configuration
243
+ Compass.configuration.project_path ||= Dir.pwd
244
+ @options[:for_engine][:load_paths] ||= []
245
+ @options[:for_engine][:load_paths] += Compass.configuration.sass_load_paths
246
+ end
247
+
248
+ def interactive
249
+ require 'sass/repl'
250
+ Sass::Repl.new(@options).run
251
+ end
252
+
253
+ # @comment
254
+ # rubocop:disable MethodLength
255
+ def watch_or_update
256
+ require 'sass/plugin'
257
+ Sass::Plugin.options.merge! @options[:for_engine]
258
+ Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
259
+ Sass::Plugin.options[:poll] = @options[:poll]
260
+ Sass::Plugin.options[:sourcemap] = @options[:sourcemap]
261
+
262
+ if @options[:force]
263
+ raise "The --force flag may only be used with --update." unless @options[:update]
264
+ Sass::Plugin.options[:always_update] = true
265
+ end
266
+
267
+ raise <<MSG if @args.empty?
268
+ What files should I watch? Did you mean something like:
269
+ #{@default_syntax} --watch input.#{@default_syntax}:output.css
270
+ #{@default_syntax} --watch input-dir:output-dir
271
+ MSG
272
+
273
+ if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
274
+ flag = @options[:update] ? "--update" : "--watch"
275
+ err =
276
+ if !File.exist?(@args[1])
277
+ "doesn't exist"
278
+ elsif @args[1] =~ /\.css$/
279
+ "is a CSS file"
280
+ end
281
+ raise <<MSG if err
282
+ File #{@args[1]} #{err}.
283
+ Did you mean: #{@default_syntax} #{flag} #{@args[0]}:#{@args[1]}
284
+ MSG
285
+ end
286
+
287
+ dirs, files = @args.map {|name| split_colon_path(name)}.
288
+ partition {|i, _| File.directory? i}
289
+ files.map! do |from, to|
290
+ to ||= from.gsub(/\.[^.]*?$/, '.css')
291
+ sourcemap = Sass::Util.sourcemap_name(to) if @options[:sourcemap]
292
+ [from, to, sourcemap]
293
+ end
294
+ dirs.map! {|from, to| [from, to || from]}
295
+ Sass::Plugin.options[:template_location] = dirs
296
+
297
+ Sass::Plugin.on_updated_stylesheet do |_, css, sourcemap|
298
+ [css, sourcemap].each do |file|
299
+ next unless file
300
+ puts_action :write, :green, file
301
+ end
302
+ end
303
+
304
+ had_error = false
305
+ Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
306
+ Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
307
+ Sass::Plugin.on_deleting_sourcemap {|filename| puts_action :delete, :yellow, filename}
308
+ Sass::Plugin.on_compilation_error do |error, _, _|
309
+ if error.is_a?(SystemCallError) && !@options[:stop_on_error]
310
+ had_error = true
311
+ puts_action :error, :red, error.message
312
+ STDOUT.flush
313
+ next
314
+ end
315
+
316
+ raise error unless error.is_a?(Sass::SyntaxError) && !@options[:stop_on_error]
317
+ had_error = true
318
+ puts_action :error, :red,
319
+ "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
320
+ STDOUT.flush
321
+ end
322
+
323
+ if @options[:update]
324
+ Sass::Plugin.update_stylesheets(files)
325
+ exit 1 if had_error
326
+ return
327
+ end
328
+
329
+ puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
330
+
331
+ Sass::Plugin.on_template_modified do |template|
332
+ puts ">>> Change detected to: #{template}"
333
+ STDOUT.flush
334
+ end
335
+ Sass::Plugin.on_template_created do |template|
336
+ puts ">>> New template detected: #{template}"
337
+ STDOUT.flush
338
+ end
339
+ Sass::Plugin.on_template_deleted do |template|
340
+ puts ">>> Deleted template detected: #{template}"
341
+ STDOUT.flush
342
+ end
343
+
344
+ Sass::Plugin.watch(files)
345
+ end
346
+ # @comment
347
+ # rubocop:enable MethodLength
348
+
349
+ def run
350
+ input = @options[:input]
351
+ output = @options[:output]
352
+
353
+ @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
354
+ @options[:for_engine][:syntax] ||= @default_syntax
355
+ engine =
356
+ if input.is_a?(File) && !@options[:check_syntax]
357
+ Sass::Engine.for_file(input.path, @options[:for_engine])
358
+ else
359
+ # We don't need to do any special handling of @options[:check_syntax] here,
360
+ # because the Sass syntax checking happens alongside evaluation
361
+ # and evaluation doesn't actually evaluate any code anyway.
362
+ Sass::Engine.new(input.read, @options[:for_engine])
363
+ end
364
+
365
+ input.close if input.is_a?(File)
366
+
367
+ if @options[:sourcemap] != :none && @options[:sourcemap_filename]
368
+ relative_sourcemap_path = Sass::Util.pathname(@options[:sourcemap_filename]).
369
+ relative_path_from(Sass::Util.pathname(@options[:output_filename]).dirname)
370
+ rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s)
371
+ write_output(rendered, output)
372
+ write_output(mapping.to_json(
373
+ :type => @options[:sourcemap],
374
+ :css_path => @options[:output_filename],
375
+ :sourcemap_path => @options[:sourcemap_filename]) + "\n",
376
+ @options[:sourcemap_filename])
377
+ else
378
+ write_output(engine.render, output)
379
+ end
380
+ rescue Sass::SyntaxError => e
381
+ write_output(Sass::SyntaxError.exception_to_css(e), output) if output.is_a?(String)
382
+ raise e if @options[:trace]
383
+ raise e.sass_backtrace_str("standard input")
384
+ ensure
385
+ output.close if output.is_a? File
386
+ end
387
+
388
+ def colon_path?(path)
389
+ !split_colon_path(path)[1].nil?
390
+ end
391
+
392
+ def split_colon_path(path)
393
+ one, two = path.split(':', 2)
394
+ if one && two && Sass::Util.windows? &&
395
+ one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
396
+ # If we're on Windows and we were passed a drive letter path,
397
+ # don't split on that colon.
398
+ one2, two = two.split(':', 2)
399
+ one = one + ':' + one2
400
+ end
401
+ return one, two
402
+ end
403
+
404
+ # Whether path is likely to be meant as the destination
405
+ # in a source:dest pair.
406
+ def probably_dest_dir?(path)
407
+ return false unless path
408
+ return false if colon_path?(path)
409
+ Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
410
+ end
411
+
412
+ def default_sass_path
413
+ return unless ENV['SASS_PATH']
414
+ # The select here prevents errors when the environment's
415
+ # load paths specified do not exist.
416
+ ENV['SASS_PATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)}
417
+ end
418
+ end
419
+ end
@@ -4,7 +4,13 @@ module Sass
4
4
  # by providing a feature name.
5
5
  module Features
6
6
  # This is the set of features that can be detected.
7
+ #
8
+ # When this is updated, the documentation of `feature-exists()` should be
9
+ # updated as well.
7
10
  KNOWN_FEATURES = Set[*%w{
11
+ global-variable-shadowing
12
+ extend-selector-pseudoclass
13
+ units-level-3
8
14
  }]
9
15
 
10
16
  # Check if a feature exists by name. This is used to implement
@@ -20,4 +20,3 @@ end
20
20
 
21
21
  require 'sass/importers/base'
22
22
  require 'sass/importers/filesystem'
23
- require 'sass/importers/deprecated_path'
@@ -124,6 +124,10 @@ module Sass
124
124
  # sourcemap generation to fail if any CSS is generated from files imported
125
125
  # from this importer.
126
126
  #
127
+ # If an absolute "file:" URI can be produced for an imported file, that
128
+ # should be preferred to returning `nil`. However, a URL relative to
129
+ # `sourcemap_directory` should be preferred over an absolute "file:" URI.
130
+ #
127
131
  # @param uri [String] A URI known to be valid for this importer.
128
132
  # @param sourcemap_directory [String, NilClass] The absolute path to a
129
133
  # directory on disk where the sourcemap will be saved. If uri refers to
@@ -133,7 +137,7 @@ module Sass
133
137
  # @return [String?] The publicly-visible URL for this file, or `nil`
134
138
  # indicating that no publicly-visible URL exists. This should be
135
139
  # appropriately URL-escaped.
136
- def public_url(uri, sourcemap_directory = nil)
140
+ def public_url(uri, sourcemap_directory)
137
141
  return if @public_url_warning_issued
138
142
  @public_url_warning_issued = true
139
143
  Sass::Util.sass_warn <<WARNING