sass 3.3.14 → 3.4.0.rc.1
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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +5 -5
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/VERSION_NAME +1 -1
- data/bin/sass +1 -1
- data/bin/scss +1 -1
- data/lib/sass.rb +0 -5
- data/lib/sass/css.rb +1 -3
- data/lib/sass/engine.rb +28 -39
- data/lib/sass/environment.rb +13 -17
- data/lib/sass/error.rb +6 -9
- data/lib/sass/exec.rb +5 -771
- data/lib/sass/exec/base.rb +187 -0
- data/lib/sass/exec/sass_convert.rb +264 -0
- data/lib/sass/exec/sass_scss.rb +419 -0
- data/lib/sass/features.rb +6 -0
- data/lib/sass/importers.rb +0 -1
- data/lib/sass/importers/base.rb +5 -1
- data/lib/sass/importers/filesystem.rb +4 -21
- data/lib/sass/media.rb +1 -4
- data/lib/sass/plugin/compiler.rb +32 -136
- data/lib/sass/script/css_lexer.rb +1 -1
- data/lib/sass/script/functions.rb +363 -39
- data/lib/sass/script/lexer.rb +68 -50
- data/lib/sass/script/parser.rb +29 -14
- data/lib/sass/script/tree.rb +1 -0
- data/lib/sass/script/tree/funcall.rb +1 -1
- data/lib/sass/script/tree/interpolation.rb +19 -1
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/value.rb +0 -1
- data/lib/sass/script/value/bool.rb +0 -5
- data/lib/sass/script/value/color.rb +32 -12
- data/lib/sass/script/value/helpers.rb +107 -0
- data/lib/sass/script/value/list.rb +0 -15
- data/lib/sass/script/value/null.rb +0 -5
- data/lib/sass/script/value/number.rb +60 -14
- data/lib/sass/script/value/string.rb +53 -9
- data/lib/sass/scss/css_parser.rb +8 -2
- data/lib/sass/scss/parser.rb +175 -319
- data/lib/sass/scss/rx.rb +14 -5
- data/lib/sass/scss/static_parser.rb +298 -1
- data/lib/sass/selector.rb +56 -193
- data/lib/sass/selector/abstract_sequence.rb +28 -13
- data/lib/sass/selector/comma_sequence.rb +91 -12
- data/lib/sass/selector/pseudo.rb +256 -0
- data/lib/sass/selector/sequence.rb +99 -31
- data/lib/sass/selector/simple.rb +14 -25
- data/lib/sass/selector/simple_sequence.rb +101 -37
- data/lib/sass/shared.rb +1 -1
- data/lib/sass/source/map.rb +23 -9
- data/lib/sass/stack.rb +0 -6
- data/lib/sass/supports.rb +1 -1
- data/lib/sass/tree/at_root_node.rb +1 -0
- data/lib/sass/tree/directive_node.rb +7 -1
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/prop_node.rb +1 -1
- data/lib/sass/tree/rule_node.rb +11 -6
- data/lib/sass/tree/visitors/check_nesting.rb +3 -4
- data/lib/sass/tree/visitors/convert.rb +8 -17
- data/lib/sass/tree/visitors/cssize.rb +12 -24
- data/lib/sass/tree/visitors/deep_copy.rb +5 -0
- data/lib/sass/tree/visitors/perform.rb +43 -28
- data/lib/sass/tree/visitors/set_options.rb +5 -0
- data/lib/sass/tree/visitors/to_css.rb +14 -13
- data/lib/sass/util.rb +94 -90
- data/test/sass/cache_test.rb +1 -1
- data/test/sass/callbacks_test.rb +1 -1
- data/test/sass/compiler_test.rb +5 -14
- data/test/sass/conversion_test.rb +47 -1
- data/test/sass/css2sass_test.rb +3 -3
- data/test/sass/encoding_test.rb +219 -0
- data/test/sass/engine_test.rb +128 -191
- data/test/sass/exec_test.rb +2 -2
- data/test/sass/extend_test.rb +234 -17
- data/test/sass/functions_test.rb +268 -213
- data/test/sass/importer_test.rb +31 -21
- data/test/sass/logger_test.rb +1 -1
- data/test/sass/more_results/more_import.css +1 -1
- data/test/sass/plugin_test.rb +12 -11
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +4 -4
- data/test/sass/results/expanded.css +1 -1
- data/test/sass/results/import.css +1 -1
- data/test/sass/results/import_charset_ibm866.css +2 -2
- data/test/sass/results/mixins.css +17 -17
- data/test/sass/results/nested.css +1 -1
- data/test/sass/results/parent_ref.css +2 -2
- data/test/sass/results/script.css +3 -3
- data/test/sass/results/scss_import.css +1 -1
- data/test/sass/script_conversion_test.rb +7 -4
- data/test/sass/script_test.rb +202 -79
- data/test/sass/scss/css_test.rb +95 -25
- data/test/sass/scss/rx_test.rb +4 -4
- data/test/sass/scss/scss_test.rb +363 -19
- data/test/sass/source_map_test.rb +48 -41
- data/test/sass/superselector_test.rb +191 -0
- data/test/sass/templates/scss_import.scss +2 -1
- data/test/sass/test_helper.rb +1 -1
- data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
- data/test/sass/util/normalized_map_test.rb +1 -1
- data/test/sass/util/subset_map_test.rb +2 -2
- data/test/sass/util_test.rb +1 -1
- data/test/sass/value_helpers_test.rb +3 -3
- data/test/test_helper.rb +2 -2
- metadata +30 -7
- data/lib/sass/importers/deprecated_path.rb +0 -51
- data/lib/sass/script/value/deprecated_false.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86dadd4faf8e93f96f022369b902622f225c18c8
|
4
|
+
data.tar.gz: da7eab9db6a3ae2c4bb925c5712ed1e6e56aa135
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf8198a80fd2741f7692e61345a96feb2013084f9c72633a69d6d4aa678fa816f779e09e1cbc604f6b7323cee7bbf1fb98da1b5d7e4d5a5d900ae2b428d38da2
|
7
|
+
data.tar.gz: 64a53c27f497151504a188189e70369e1bb57207b7d6f2a3e04b94aa6fc36b29f66ed9151bc8ce175f90bc1b0e893ee7f81dc63d68c4feb6334136649c5cf9f5
|
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2006-
|
1
|
+
Copyright (c) 2006-2013 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -199,11 +199,11 @@ and now occasionally consults on the language issues. Hampton lives in San
|
|
199
199
|
Francisco, California and works as VP of Technology
|
200
200
|
at [Moovweb](http://www.moovweb.com/).
|
201
201
|
|
202
|
-
[
|
203
|
-
Sass.
|
202
|
+
[Nathan Weizenbaum](http://nex-3.com) is the primary developer and architect of
|
203
|
+
Sass. His hard work has kept the project alive by endlessly answering forum
|
204
204
|
posts, fixing bugs, refactoring, finding speed improvements, writing
|
205
205
|
documentation, implementing new features, and getting Hampton coffee (a fitting
|
206
|
-
task for a
|
206
|
+
task for a boy-genius). Nathan lives in Seattle, Washington and works on
|
207
207
|
[Dart](http://dartlang.org) application libraries at Google.
|
208
208
|
|
209
209
|
[Chris Eppstein](http://acts-as-architect.blogspot.com) is a core contributor to
|
@@ -214,8 +214,8 @@ his wife and daughter. He is an Engineer for
|
|
214
214
|
[LinkedIn.com](http://linkedin.com), where one of his responsibilities is to
|
215
215
|
maintain Sass & Compass.
|
216
216
|
|
217
|
-
If you use this software, you must pay Hampton a compliment. And
|
218
|
-
some
|
217
|
+
If you use this software, you must pay Hampton a compliment. And
|
218
|
+
buy Nathan some jelly beans. Maybe pet a kitten. Yeah. Pet that kitty.
|
219
219
|
|
220
220
|
Beyond that, the implementation is licensed under the MIT License.
|
221
221
|
Okay, fine, I guess that means compliments aren't __required__.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.4.0.rc.1
|
data/VERSION_DATE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
12 July 2014 01:42:30 UTC
|
data/VERSION_NAME
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
Selective Steve
|
data/bin/sass
CHANGED
data/bin/scss
CHANGED
data/lib/sass.rb
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
dir = File.dirname(__FILE__)
|
2
2
|
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
3
|
|
4
|
-
# This is necessary to set so that the Haml code that tries to load Sass
|
5
|
-
# knows that Sass is indeed loading,
|
6
|
-
# even if there's some crazy autoload stuff going on.
|
7
|
-
SASS_BEGUN_TO_LOAD = true unless defined?(SASS_BEGUN_TO_LOAD)
|
8
|
-
|
9
4
|
require 'sass/version'
|
10
5
|
|
11
6
|
# The module that contains everything Sass-related:
|
data/lib/sass/css.rb
CHANGED
@@ -67,9 +67,7 @@ module Sass
|
|
67
67
|
def check_encoding!
|
68
68
|
return if @checked_encoding
|
69
69
|
@checked_encoding = true
|
70
|
-
@template, @original_encoding = Sass::Util.check_sass_encoding(@template)
|
71
|
-
raise Sass::SyntaxError.new(msg, :line => line)
|
72
|
-
end
|
70
|
+
@template, @original_encoding = Sass::Util.check_sass_encoding(@template)
|
73
71
|
end
|
74
72
|
|
75
73
|
# Parses the CSS template and applies various transformations
|
data/lib/sass/engine.rb
CHANGED
@@ -30,6 +30,8 @@ require 'sass/tree/warn_node'
|
|
30
30
|
require 'sass/tree/import_node'
|
31
31
|
require 'sass/tree/charset_node'
|
32
32
|
require 'sass/tree/at_root_node'
|
33
|
+
require 'sass/tree/keyframe_rule_node'
|
34
|
+
require 'sass/tree/error_node'
|
33
35
|
require 'sass/tree/visitors/base'
|
34
36
|
require 'sass/tree/visitors/perform'
|
35
37
|
require 'sass/tree/visitors/cssize'
|
@@ -190,22 +192,13 @@ module Sass
|
|
190
192
|
options[:filesystem_importer].new(p.to_s)
|
191
193
|
end
|
192
194
|
|
193
|
-
# Remove any deprecated importers if the location is imported explicitly
|
194
|
-
options[:load_paths].reject! do |importer|
|
195
|
-
importer.is_a?(Sass::Importers::DeprecatedPath) &&
|
196
|
-
options[:load_paths].find do |other_importer|
|
197
|
-
other_importer.is_a?(Sass::Importers::Filesystem) &&
|
198
|
-
other_importer != importer &&
|
199
|
-
other_importer.root == importer.root
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
195
|
# Backwards compatibility
|
204
196
|
options[:property_syntax] ||= options[:attribute_syntax]
|
205
197
|
case options[:property_syntax]
|
206
198
|
when :alternate; options[:property_syntax] = :new
|
207
199
|
when :normal; options[:property_syntax] = :old
|
208
200
|
end
|
201
|
+
options[:sourcemap] = :auto if options[:sourcemap] == true
|
209
202
|
|
210
203
|
options
|
211
204
|
end
|
@@ -271,8 +264,8 @@ module Sass
|
|
271
264
|
# cannot be converted to UTF-8
|
272
265
|
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
273
266
|
def render
|
274
|
-
return
|
275
|
-
Sass::Util.silence_sass_warnings {
|
267
|
+
return _to_tree.render unless @options[:quiet]
|
268
|
+
Sass::Util.silence_sass_warnings {_to_tree.render}
|
276
269
|
end
|
277
270
|
|
278
271
|
# Render the template to CSS and return the source map.
|
@@ -315,7 +308,7 @@ module Sass
|
|
315
308
|
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
316
309
|
def source_encoding
|
317
310
|
check_encoding!
|
318
|
-
@
|
311
|
+
@source_encoding
|
319
312
|
end
|
320
313
|
|
321
314
|
# Gets a set of all the documents
|
@@ -360,7 +353,10 @@ Error generating source map: couldn't determine public URL for "#{filename}".
|
|
360
353
|
Without a public URL, there's nothing for the source map to link to.
|
361
354
|
An importer was not set for this file.
|
362
355
|
ERR
|
363
|
-
elsif Sass::Util.silence_warnings
|
356
|
+
elsif Sass::Util.silence_warnings do
|
357
|
+
sourcemap_dir = nil if @options[:sourcemap] == :file
|
358
|
+
importer.public_url(filename, sourcemap_dir).nil?
|
359
|
+
end
|
364
360
|
raise Sass::SyntaxError.new(<<ERR)
|
365
361
|
Error generating source map: couldn't determine public URL for "#{filename}".
|
366
362
|
Without a public URL, there's nothing for the source map to link to.
|
@@ -375,23 +371,12 @@ ERR
|
|
375
371
|
rendered << "/*# sourceMappingURL="
|
376
372
|
rendered << Sass::Util.escape_uri(sourcemap_uri)
|
377
373
|
rendered << " */\n"
|
378
|
-
rendered = encode_and_set_charset(rendered)
|
379
374
|
return rendered, sourcemap
|
380
375
|
end
|
381
376
|
|
382
|
-
def encode_and_set_charset(rendered)
|
383
|
-
return rendered if Sass::Util.ruby1_8?
|
384
|
-
begin
|
385
|
-
# Try to convert the result to the original encoding,
|
386
|
-
# but if that doesn't work fall back on UTF-8
|
387
|
-
rendered = rendered.encode(source_encoding)
|
388
|
-
rescue EncodingError
|
389
|
-
end
|
390
|
-
rendered.gsub(Regexp.new('\A@charset "(.*?)"'.encode(source_encoding)),
|
391
|
-
"@charset \"#{source_encoding.name}\"".encode(source_encoding))
|
392
|
-
end
|
393
|
-
|
394
377
|
def _to_tree
|
378
|
+
check_encoding!
|
379
|
+
|
395
380
|
if (@options[:cache] || @options[:read_cache]) &&
|
396
381
|
@options[:filename] && @options[:importer]
|
397
382
|
key = sassc_key
|
@@ -403,8 +388,6 @@ ERR
|
|
403
388
|
end
|
404
389
|
end
|
405
390
|
|
406
|
-
check_encoding!
|
407
|
-
|
408
391
|
if @options[:syntax] == :scss
|
409
392
|
root = Sass::SCSS::Parser.new(@template, @options[:filename], @options[:importer]).parse
|
410
393
|
else
|
@@ -436,9 +419,7 @@ ERR
|
|
436
419
|
def check_encoding!
|
437
420
|
return if @checked_encoding
|
438
421
|
@checked_encoding = true
|
439
|
-
@template, @
|
440
|
-
raise Sass::SyntaxError.new(msg, :line => line)
|
441
|
-
end
|
422
|
+
@template, @source_encoding = Sass::Util.check_sass_encoding(@template)
|
442
423
|
end
|
443
424
|
|
444
425
|
def tabulate(string)
|
@@ -446,7 +427,7 @@ ERR
|
|
446
427
|
comment_tab_str = nil
|
447
428
|
first = true
|
448
429
|
lines = []
|
449
|
-
string.
|
430
|
+
string.scan(/^[^\n]*?$/).each_with_index do |line, index|
|
450
431
|
index += (@options[:line] || 1)
|
451
432
|
if line.strip.empty?
|
452
433
|
lines.last.text << "\n" if lines.last && lines.last.comment?
|
@@ -792,7 +773,7 @@ WARNING
|
|
792
773
|
|
793
774
|
DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
|
794
775
|
:each, :while, :if, :else, :extend, :import, :media, :charset, :content,
|
795
|
-
:at_root]
|
776
|
+
:at_root, :error]
|
796
777
|
|
797
778
|
# @comment
|
798
779
|
# rubocop:disable MethodLength
|
@@ -834,6 +815,14 @@ WARNING
|
|
834
815
|
Tree::DebugNode.new(parse_script(value, :offset => offset))
|
835
816
|
end
|
836
817
|
|
818
|
+
def parse_error_directive(parent, line, root, value, offset)
|
819
|
+
raise SyntaxError.new("Invalid error directive '@error': expected expression.") unless value
|
820
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath error directives.",
|
821
|
+
:line => @line + 1) unless line.children.empty?
|
822
|
+
offset = line.offset + line.text.index(value).to_i
|
823
|
+
Tree::ErrorNode.new(parse_script(value, :offset => offset))
|
824
|
+
end
|
825
|
+
|
837
826
|
def parse_extend_directive(parent, line, root, value, offset)
|
838
827
|
raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
|
839
828
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
|
@@ -1027,7 +1016,7 @@ WARNING
|
|
1027
1016
|
return node
|
1028
1017
|
end
|
1029
1018
|
|
1030
|
-
unless (
|
1019
|
+
unless (quoted_val = scanner.scan(Sass::SCSS::RX::STRING))
|
1031
1020
|
scanned = scanner.scan(/[^,;]+/)
|
1032
1021
|
node = Tree::ImportNode.new(scanned)
|
1033
1022
|
start_parser_offset = to_parser_offset(offset)
|
@@ -1039,21 +1028,21 @@ WARNING
|
|
1039
1028
|
end
|
1040
1029
|
|
1041
1030
|
start_offset = offset
|
1042
|
-
offset +=
|
1043
|
-
val = scanner[1] || scanner[2]
|
1031
|
+
offset += scanner.matched.length
|
1032
|
+
val = Sass::Script::Value::String.value(scanner[1] || scanner[2])
|
1044
1033
|
scanned = scanner.scan(/\s*/)
|
1045
1034
|
if !scanner.match?(/[,;]|$/)
|
1046
1035
|
offset += scanned.length if scanned
|
1047
1036
|
media_parser = Sass::SCSS::Parser.new(scanner,
|
1048
1037
|
@options[:filename], @options[:importer], @line, offset)
|
1049
1038
|
media = media_parser.parse_media_query_list
|
1050
|
-
node = Tree::CssImportNode.new(
|
1039
|
+
node = Tree::CssImportNode.new(quoted_val, media.to_a)
|
1051
1040
|
node.source_range = Sass::Source::Range.new(
|
1052
1041
|
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
1053
1042
|
Sass::Source::Position.new(@line, media_parser.offset),
|
1054
1043
|
@options[:filename], @options[:importer])
|
1055
1044
|
elsif val =~ %r{^(https?:)?//}
|
1056
|
-
node = Tree::CssImportNode.new(
|
1045
|
+
node = Tree::CssImportNode.new(quoted_val)
|
1057
1046
|
node.source_range = Sass::Source::Range.new(
|
1058
1047
|
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
1059
1048
|
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
data/lib/sass/environment.rb
CHANGED
@@ -41,7 +41,7 @@ module Sass
|
|
41
41
|
if @#{name}s.include?(name)
|
42
42
|
@#{name}s[name] = value
|
43
43
|
true
|
44
|
-
elsif @parent
|
44
|
+
elsif @parent && !@parent.global?
|
45
45
|
@parent.try_set_#{name}(name, value)
|
46
46
|
else
|
47
47
|
false
|
@@ -53,6 +53,10 @@ module Sass
|
|
53
53
|
@#{name}s ||= {}
|
54
54
|
@#{name}s[name.tr('_', '-')] = value
|
55
55
|
end
|
56
|
+
|
57
|
+
def set_global_#{name}(name, value)
|
58
|
+
global_env.set_#{name}(name, value)
|
59
|
+
end
|
56
60
|
RUBY
|
57
61
|
end
|
58
62
|
end
|
@@ -76,19 +80,6 @@ module Sass
|
|
76
80
|
# Sass::Callable
|
77
81
|
inherited_hash_reader :function
|
78
82
|
|
79
|
-
# Whether a warning has been emitted for assigning to the given
|
80
|
-
# global variable. This is a set of tuples containing the name of
|
81
|
-
# the variable, its filename, and its line number.
|
82
|
-
#
|
83
|
-
# @return [Set<[String, String, int]>]
|
84
|
-
attr_reader :global_warning_given
|
85
|
-
|
86
|
-
# Whether a warning has been emitted for misusing a deprecated false value.
|
87
|
-
# This is a set of tuples containing the filename and its line number.
|
88
|
-
#
|
89
|
-
# @return [Set<[String, int]>]
|
90
|
-
attr_reader :deprecated_false_warning_given
|
91
|
-
|
92
83
|
# @param options [{Symbol => Object}] The options hash. See
|
93
84
|
# {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
94
85
|
# @param parent [Environment] See \{#parent}
|
@@ -96,8 +87,13 @@ module Sass
|
|
96
87
|
@parent = parent
|
97
88
|
@options = options || (parent && parent.options) || {}
|
98
89
|
@stack = Sass::Stack.new if @parent.nil?
|
99
|
-
|
100
|
-
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns whether this is the global environment.
|
93
|
+
#
|
94
|
+
# @return [Boolean]
|
95
|
+
def global?
|
96
|
+
@parent.nil?
|
101
97
|
end
|
102
98
|
|
103
99
|
# The environment of the caller of this environment's mixin or function.
|
@@ -128,7 +124,7 @@ module Sass
|
|
128
124
|
#
|
129
125
|
# @return [Environment]
|
130
126
|
def global_env
|
131
|
-
@global_env ||=
|
127
|
+
@global_env ||= global? ? self : @parent.global_env
|
132
128
|
end
|
133
129
|
|
134
130
|
# The import/mixin stack.
|
data/lib/sass/error.rb
CHANGED
@@ -140,8 +140,8 @@ module Sass
|
|
140
140
|
def sass_backtrace_str(default_filename = "an unknown file")
|
141
141
|
lines = message.split("\n")
|
142
142
|
msg = lines[0] + lines[1..-1].
|
143
|
-
map {|l| "\n" + (" " * "
|
144
|
-
"
|
143
|
+
map {|l| "\n" + (" " * "Error: ".size) + l}.join
|
144
|
+
"Error: #{msg}" +
|
145
145
|
Sass::Util.enum_with_index(sass_backtrace).map do |entry, i|
|
146
146
|
"\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
|
147
147
|
" of #{entry[:filename] || default_filename}" +
|
@@ -153,15 +153,13 @@ module Sass
|
|
153
153
|
# Returns an error report for an exception in CSS format.
|
154
154
|
#
|
155
155
|
# @param e [Exception]
|
156
|
-
# @param
|
156
|
+
# @param line_offset [Fixnum] The number of the first line of the Sass template.
|
157
157
|
# @return [String] The error report
|
158
158
|
# @raise [Exception] `e`, if the
|
159
159
|
# {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option
|
160
160
|
# is set to false.
|
161
|
-
def exception_to_css(e,
|
162
|
-
|
163
|
-
|
164
|
-
header = header_string(e, options)
|
161
|
+
def exception_to_css(e, line_offset = 1)
|
162
|
+
header = header_string(e, line_offset)
|
165
163
|
|
166
164
|
<<END
|
167
165
|
/*
|
@@ -178,12 +176,11 @@ END
|
|
178
176
|
|
179
177
|
private
|
180
178
|
|
181
|
-
def header_string(e,
|
179
|
+
def header_string(e, line_offset)
|
182
180
|
unless e.is_a?(Sass::SyntaxError) && e.sass_line && e.sass_template
|
183
181
|
return "#{e.class}: #{e.message}"
|
184
182
|
end
|
185
183
|
|
186
|
-
line_offset = options[:line] || 1
|
187
184
|
line_num = e.sass_line + 1 - line_offset
|
188
185
|
min = [line_num - 6, 0].max
|
189
186
|
section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
|
data/lib/sass/exec.rb
CHANGED
@@ -1,775 +1,9 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'fileutils'
|
3
|
-
|
4
1
|
module Sass
|
5
|
-
# This module handles the
|
2
|
+
# This module handles the Sass executables (`sass` and `sass-convert`).
|
6
3
|
module Exec
|
7
|
-
# An abstract class that encapsulates the executable code for all three executables.
|
8
|
-
class Generic
|
9
|
-
# @param args [Array<String>] The command-line arguments
|
10
|
-
def initialize(args)
|
11
|
-
@args = args
|
12
|
-
@options = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
# Parses the command-line arguments and runs the executable.
|
16
|
-
# Calls `Kernel#exit` at the end, so it never returns.
|
17
|
-
#
|
18
|
-
# @see #parse
|
19
|
-
def parse!
|
20
|
-
# rubocop:disable RescueException
|
21
|
-
begin
|
22
|
-
parse
|
23
|
-
rescue Exception => e
|
24
|
-
raise e if @options[:trace] || e.is_a?(SystemExit)
|
25
|
-
|
26
|
-
$stderr.print "#{e.class}: " unless e.class == RuntimeError
|
27
|
-
$stderr.puts "#{e.message}"
|
28
|
-
$stderr.puts " Use --trace for backtrace."
|
29
|
-
exit 1
|
30
|
-
end
|
31
|
-
exit 0
|
32
|
-
# rubocop:enable RescueException
|
33
|
-
end
|
34
|
-
|
35
|
-
# Parses the command-line arguments and runs the executable.
|
36
|
-
# This does not handle exceptions or exit the program.
|
37
|
-
#
|
38
|
-
# @see #parse!
|
39
|
-
def parse
|
40
|
-
@opts = OptionParser.new(&method(:set_opts))
|
41
|
-
@opts.parse!(@args)
|
42
|
-
|
43
|
-
process_result
|
44
|
-
|
45
|
-
@options
|
46
|
-
end
|
47
|
-
|
48
|
-
# @return [String] A description of the executable
|
49
|
-
def to_s
|
50
|
-
@opts.to_s
|
51
|
-
end
|
52
|
-
|
53
|
-
protected
|
54
|
-
|
55
|
-
# Finds the line of the source template
|
56
|
-
# on which an exception was raised.
|
57
|
-
#
|
58
|
-
# @param exception [Exception] The exception
|
59
|
-
# @return [String] The line number
|
60
|
-
def get_line(exception)
|
61
|
-
# SyntaxErrors have weird line reporting
|
62
|
-
# when there's trailing whitespace
|
63
|
-
if exception.is_a?(::SyntaxError)
|
64
|
-
return (exception.message.scan(/:(\d+)/).first || ["??"]).first
|
65
|
-
end
|
66
|
-
(exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
|
67
|
-
end
|
68
|
-
|
69
|
-
# Tells optparse how to parse the arguments
|
70
|
-
# available for all executables.
|
71
|
-
#
|
72
|
-
# This is meant to be overridden by subclasses
|
73
|
-
# so they can add their own options.
|
74
|
-
#
|
75
|
-
# @param opts [OptionParser]
|
76
|
-
def set_opts(opts)
|
77
|
-
opts.on('-s', '--stdin', :NONE,
|
78
|
-
'Read input from standard input instead of an input file') do
|
79
|
-
@options[:input] = $stdin
|
80
|
-
end
|
81
|
-
|
82
|
-
opts.on('--trace', :NONE, 'Show a full traceback on error') do
|
83
|
-
@options[:trace] = true
|
84
|
-
end
|
85
|
-
|
86
|
-
opts.on('--unix-newlines', 'Use Unix-style newlines in written files.') do
|
87
|
-
@options[:unix_newlines] = true if ::Sass::Util.windows?
|
88
|
-
end
|
89
|
-
|
90
|
-
opts.on_tail("-?", "-h", "--help", "Show this message") do
|
91
|
-
puts opts
|
92
|
-
exit
|
93
|
-
end
|
94
|
-
|
95
|
-
opts.on_tail("-v", "--version", "Print version") do
|
96
|
-
puts("Sass #{::Sass.version[:string]}")
|
97
|
-
exit
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Processes the options set by the command-line arguments.
|
102
|
-
# In particular, sets `@options[:input]` and `@options[:output]`
|
103
|
-
# (and `@options[:sourcemap]` if one has been specified)
|
104
|
-
# to appropriate IO streams.
|
105
|
-
#
|
106
|
-
# This is meant to be overridden by subclasses
|
107
|
-
# so they can run their respective programs.
|
108
|
-
def process_result
|
109
|
-
input, output = @options[:input], @options[:output]
|
110
|
-
args = @args.dup
|
111
|
-
input ||=
|
112
|
-
begin
|
113
|
-
filename = args.shift
|
114
|
-
@options[:filename] = filename
|
115
|
-
open_file(filename) || $stdin
|
116
|
-
end
|
117
|
-
@options[:output_filename] = args.shift
|
118
|
-
output ||= @options[:output_filename] || $stdout
|
119
|
-
|
120
|
-
if @options[:sourcemap] && @options[:output_filename]
|
121
|
-
@options[:sourcemap_filename] = Util.sourcemap_name(@options[:output_filename])
|
122
|
-
end
|
123
|
-
|
124
|
-
@options[:input], @options[:output] = input, output
|
125
|
-
end
|
126
|
-
|
127
|
-
COLORS = {:red => 31, :green => 32, :yellow => 33}
|
128
|
-
|
129
|
-
# Prints a status message about performing the given action,
|
130
|
-
# colored using the given color (via terminal escapes) if possible.
|
131
|
-
#
|
132
|
-
# @param name [#to_s] A short name for the action being performed.
|
133
|
-
# Shouldn't be longer than 11 characters.
|
134
|
-
# @param color [Symbol] The name of the color to use for this action.
|
135
|
-
# Can be `:red`, `:green`, or `:yellow`.
|
136
|
-
def puts_action(name, color, arg)
|
137
|
-
return if @options[:for_engine][:quiet]
|
138
|
-
printf color(color, "%11s %s\n"), name, arg
|
139
|
-
STDOUT.flush
|
140
|
-
end
|
141
|
-
|
142
|
-
# Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
|
143
|
-
#
|
144
|
-
# @param args [Array] Passed on to `Kernel.puts`
|
145
|
-
def puts(*args)
|
146
|
-
return if @options[:for_engine][:quiet]
|
147
|
-
Kernel.puts(*args)
|
148
|
-
end
|
149
|
-
|
150
|
-
# Wraps the given string in terminal escapes
|
151
|
-
# causing it to have the given color.
|
152
|
-
# If terminal esapes aren't supported on this platform,
|
153
|
-
# just returns the string instead.
|
154
|
-
#
|
155
|
-
# @param color [Symbol] The name of the color to use.
|
156
|
-
# Can be `:red`, `:green`, or `:yellow`.
|
157
|
-
# @param str [String] The string to wrap in the given color.
|
158
|
-
# @return [String] The wrapped string.
|
159
|
-
def color(color, str)
|
160
|
-
raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
|
161
|
-
|
162
|
-
# Almost any real Unix terminal will support color,
|
163
|
-
# so we just filter for Windows terms (which don't set TERM)
|
164
|
-
# and not-real terminals, which aren't ttys.
|
165
|
-
return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
|
166
|
-
"\e[#{COLORS[color]}m#{str}\e[0m"
|
167
|
-
end
|
168
|
-
|
169
|
-
def write_output(text, destination)
|
170
|
-
if destination.is_a?(String)
|
171
|
-
open_file(destination, 'w') {|file| file.write(text)}
|
172
|
-
else
|
173
|
-
destination.write(text)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
def open_file(filename, flag = 'r')
|
180
|
-
return if filename.nil?
|
181
|
-
flag = 'wb' if @options[:unix_newlines] && flag == 'w'
|
182
|
-
file = File.open(filename, flag)
|
183
|
-
return file unless block_given?
|
184
|
-
yield file
|
185
|
-
file.close
|
186
|
-
end
|
187
|
-
|
188
|
-
def handle_load_error(err)
|
189
|
-
dep = err.message[/^no such file to load -- (.*)/, 1]
|
190
|
-
raise err if @options[:trace] || dep.nil? || dep.empty?
|
191
|
-
$stderr.puts <<MESSAGE
|
192
|
-
Required dependency #{dep} not found!
|
193
|
-
Run "gem install #{dep}" to get it.
|
194
|
-
Use --trace for backtrace.
|
195
|
-
MESSAGE
|
196
|
-
exit 1
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
# The `sass` executable.
|
201
|
-
class Sass < Generic
|
202
|
-
attr_reader :default_syntax
|
203
|
-
|
204
|
-
# @param args [Array<String>] The command-line arguments
|
205
|
-
def initialize(args)
|
206
|
-
super
|
207
|
-
@options[:for_engine] = {
|
208
|
-
:load_paths => default_sass_path
|
209
|
-
}
|
210
|
-
@default_syntax = :sass
|
211
|
-
end
|
212
|
-
|
213
|
-
protected
|
214
|
-
|
215
|
-
# Tells optparse how to parse the arguments.
|
216
|
-
#
|
217
|
-
# @param opts [OptionParser]
|
218
|
-
# @comment
|
219
|
-
# rubocop:disable MethodLength
|
220
|
-
def set_opts(opts)
|
221
|
-
super
|
222
|
-
|
223
|
-
opts.banner = <<END
|
224
|
-
Usage: #{default_syntax} [options] [INPUT] [OUTPUT]
|
225
|
-
|
226
|
-
Description:
|
227
|
-
Converts SCSS or Sass files to CSS.
|
228
|
-
|
229
|
-
Options:
|
230
|
-
END
|
231
|
-
|
232
|
-
if @default_syntax == :sass
|
233
|
-
opts.on('--scss',
|
234
|
-
'Use the CSS-superset SCSS syntax.') do
|
235
|
-
@options[:for_engine][:syntax] = :scss
|
236
|
-
end
|
237
|
-
else
|
238
|
-
opts.on('--sass',
|
239
|
-
'Use the Indented syntax.') do
|
240
|
-
@options[:for_engine][:syntax] = :sass
|
241
|
-
end
|
242
|
-
end
|
243
|
-
opts.on('--watch', 'Watch files or directories for changes.',
|
244
|
-
'The location of the generated CSS can be set using a colon:',
|
245
|
-
" #{@default_syntax} --watch input.#{@default_syntax}:output.css",
|
246
|
-
" #{@default_syntax} --watch input-dir:output-dir") do
|
247
|
-
@options[:watch] = true
|
248
|
-
end
|
249
|
-
opts.on('--update', 'Compile files or directories to CSS.',
|
250
|
-
'Locations are set like --watch.') do
|
251
|
-
@options[:update] = true
|
252
|
-
end
|
253
|
-
opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
|
254
|
-
'Only meaningful for --watch and --update.') do
|
255
|
-
@options[:stop_on_error] = true
|
256
|
-
end
|
257
|
-
opts.on('--poll', 'Check for file changes manually, rather than relying on the OS.',
|
258
|
-
'Only meaningful for --watch.') do
|
259
|
-
@options[:poll] = true
|
260
|
-
end
|
261
|
-
opts.on('-f', '--force', 'Recompile all Sass files, even if the CSS file is newer.',
|
262
|
-
'Only meaningful for --update.') do
|
263
|
-
@options[:force] = true
|
264
|
-
end
|
265
|
-
opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
|
266
|
-
require 'stringio'
|
267
|
-
@options[:check_syntax] = true
|
268
|
-
@options[:output] = StringIO.new
|
269
|
-
end
|
270
|
-
style_desc = 'Output style. Can be nested (default), compact, compressed, or expanded.'
|
271
|
-
opts.on('-t', '--style NAME', style_desc) do |name|
|
272
|
-
@options[:for_engine][:style] = name.to_sym
|
273
|
-
end
|
274
|
-
opts.on('--precision NUMBER_OF_DIGITS', Integer,
|
275
|
-
"How many digits of precision to use when outputting decimal numbers." +
|
276
|
-
"Defaults to #{::Sass::Script::Value::Number.precision}.") do |precision|
|
277
|
-
::Sass::Script::Value::Number.precision = precision
|
278
|
-
end
|
279
|
-
opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
|
280
|
-
@options[:for_engine][:quiet] = true
|
281
|
-
end
|
282
|
-
opts.on('--compass', 'Make Compass imports available and load project configuration.') do
|
283
|
-
@options[:compass] = true
|
284
|
-
end
|
285
|
-
opts.on('-g', '--debug-info',
|
286
|
-
'Emit output that can be used by the FireSass Firebug plugin.') do
|
287
|
-
@options[:for_engine][:debug_info] = true
|
288
|
-
end
|
289
|
-
opts.on('-l', '--line-numbers', '--line-comments',
|
290
|
-
'Emit comments in the generated CSS indicating the corresponding source line.') do
|
291
|
-
@options[:for_engine][:line_numbers] = true
|
292
|
-
end
|
293
|
-
opts.on('-i', '--interactive',
|
294
|
-
'Run an interactive SassScript shell.') do
|
295
|
-
@options[:interactive] = true
|
296
|
-
end
|
297
|
-
opts.on('-I', '--load-path PATH', 'Add a sass import path.') do |path|
|
298
|
-
@options[:for_engine][:load_paths] << path
|
299
|
-
end
|
300
|
-
opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
|
301
|
-
require lib
|
302
|
-
end
|
303
|
-
opts.on('--cache-location PATH',
|
304
|
-
'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
|
305
|
-
@options[:for_engine][:cache_location] = loc
|
306
|
-
end
|
307
|
-
opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
|
308
|
-
@options[:for_engine][:cache] = false
|
309
|
-
end
|
310
|
-
opts.on('--sourcemap', 'Create sourcemap files next to the generated CSS files.') do
|
311
|
-
@options[:sourcemap] = true
|
312
|
-
end
|
313
|
-
|
314
|
-
encoding_desc = if ::Sass::Util.ruby1_8?
|
315
|
-
'Does not work in ruby 1.8.'
|
316
|
-
else
|
317
|
-
'Specify the default encoding for Sass files.'
|
318
|
-
end
|
319
|
-
opts.on('-E', '--default-encoding ENCODING', encoding_desc) do |encoding|
|
320
|
-
if ::Sass::Util.ruby1_8?
|
321
|
-
$stderr.puts "Specifying the encoding is not supported in ruby 1.8."
|
322
|
-
exit 1
|
323
|
-
else
|
324
|
-
Encoding.default_external = encoding
|
325
|
-
end
|
326
|
-
end
|
327
|
-
end
|
328
|
-
# @comment
|
329
|
-
# rubocop:enable MethodLength
|
330
|
-
|
331
|
-
# Processes the options set by the command-line arguments,
|
332
|
-
# and runs the Sass compiler appropriately.
|
333
|
-
def process_result
|
334
|
-
require 'sass'
|
335
|
-
|
336
|
-
if !@options[:update] && !@options[:watch] &&
|
337
|
-
@args.first && colon_path?(@args.first)
|
338
|
-
if @args.size == 1
|
339
|
-
@args = split_colon_path(@args.first)
|
340
|
-
else
|
341
|
-
@options[:update] = true
|
342
|
-
end
|
343
|
-
end
|
344
|
-
load_compass if @options[:compass]
|
345
|
-
return interactive if @options[:interactive]
|
346
|
-
return watch_or_update if @options[:watch] || @options[:update]
|
347
|
-
super
|
348
|
-
@options[:for_engine][:filename] = @options[:filename]
|
349
|
-
@options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
|
350
|
-
@options[:for_engine][:sourcemap_filename] = @options[:sourcemap_filename]
|
351
|
-
|
352
|
-
begin
|
353
|
-
input = @options[:input]
|
354
|
-
output = @options[:output]
|
355
|
-
|
356
|
-
@options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
|
357
|
-
@options[:for_engine][:syntax] ||= @default_syntax
|
358
|
-
engine =
|
359
|
-
if input.is_a?(File) && !@options[:check_syntax]
|
360
|
-
::Sass::Engine.for_file(input.path, @options[:for_engine])
|
361
|
-
else
|
362
|
-
# We don't need to do any special handling of @options[:check_syntax] here,
|
363
|
-
# because the Sass syntax checking happens alongside evaluation
|
364
|
-
# and evaluation doesn't actually evaluate any code anyway.
|
365
|
-
::Sass::Engine.new(input.read, @options[:for_engine])
|
366
|
-
end
|
367
|
-
|
368
|
-
input.close if input.is_a?(File)
|
369
|
-
|
370
|
-
if @options[:sourcemap]
|
371
|
-
unless @options[:sourcemap_filename]
|
372
|
-
raise "Can't generate a sourcemap for an input without a path."
|
373
|
-
end
|
374
|
-
|
375
|
-
relative_sourcemap_path = ::Sass::Util.pathname(@options[:sourcemap_filename]).
|
376
|
-
relative_path_from(::Sass::Util.pathname(@options[:output_filename]).dirname)
|
377
|
-
rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s)
|
378
|
-
write_output(rendered, output)
|
379
|
-
write_output(mapping.to_json(
|
380
|
-
:css_path => @options[:output_filename],
|
381
|
-
:sourcemap_path => @options[:sourcemap_filename]) + "\n",
|
382
|
-
@options[:sourcemap_filename])
|
383
|
-
else
|
384
|
-
write_output(engine.render, output)
|
385
|
-
end
|
386
|
-
rescue ::Sass::SyntaxError => e
|
387
|
-
raise e if @options[:trace]
|
388
|
-
raise e.sass_backtrace_str("standard input")
|
389
|
-
ensure
|
390
|
-
output.close if output.is_a? File
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
private
|
395
|
-
|
396
|
-
def load_compass
|
397
|
-
begin
|
398
|
-
require 'compass'
|
399
|
-
rescue LoadError
|
400
|
-
require 'rubygems'
|
401
|
-
begin
|
402
|
-
require 'compass'
|
403
|
-
rescue LoadError
|
404
|
-
puts "ERROR: Cannot load compass."
|
405
|
-
exit 1
|
406
|
-
end
|
407
|
-
end
|
408
|
-
Compass.add_project_configuration
|
409
|
-
Compass.configuration.project_path ||= Dir.pwd
|
410
|
-
@options[:for_engine][:load_paths] += Compass.configuration.sass_load_paths
|
411
|
-
end
|
412
|
-
|
413
|
-
def interactive
|
414
|
-
require 'sass/repl'
|
415
|
-
::Sass::Repl.new(@options).run
|
416
|
-
end
|
417
|
-
|
418
|
-
# @comment
|
419
|
-
# rubocop:disable MethodLength
|
420
|
-
def watch_or_update
|
421
|
-
require 'sass/plugin'
|
422
|
-
::Sass::Plugin.options.merge! @options[:for_engine]
|
423
|
-
::Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
|
424
|
-
::Sass::Plugin.options[:poll] = @options[:poll]
|
425
|
-
::Sass::Plugin.options[:sourcemap] = @options[:sourcemap]
|
426
|
-
|
427
|
-
if @options[:force]
|
428
|
-
raise "The --force flag may only be used with --update." unless @options[:update]
|
429
|
-
::Sass::Plugin.options[:always_update] = true
|
430
|
-
end
|
431
|
-
|
432
|
-
raise <<MSG if @args.empty?
|
433
|
-
What files should I watch? Did you mean something like:
|
434
|
-
#{@default_syntax} --watch input.#{@default_syntax}:output.css
|
435
|
-
#{@default_syntax} --watch input-dir:output-dir
|
436
|
-
MSG
|
437
|
-
|
438
|
-
if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
|
439
|
-
flag = @options[:update] ? "--update" : "--watch"
|
440
|
-
err =
|
441
|
-
if !File.exist?(@args[1])
|
442
|
-
"doesn't exist"
|
443
|
-
elsif @args[1] =~ /\.css$/
|
444
|
-
"is a CSS file"
|
445
|
-
end
|
446
|
-
raise <<MSG if err
|
447
|
-
File #{@args[1]} #{err}.
|
448
|
-
Did you mean: #{@default_syntax} #{flag} #{@args[0]}:#{@args[1]}
|
449
|
-
MSG
|
450
|
-
end
|
451
|
-
|
452
|
-
dirs, files = @args.map {|name| split_colon_path(name)}.
|
453
|
-
partition {|i, _| File.directory? i}
|
454
|
-
files.map! do |from, to|
|
455
|
-
to ||= from.gsub(/\.[^.]*?$/, '.css')
|
456
|
-
sourcemap = Util.sourcemap_name(to) if @options[:sourcemap]
|
457
|
-
[from, to, sourcemap]
|
458
|
-
end
|
459
|
-
dirs.map! {|from, to| [from, to || from]}
|
460
|
-
::Sass::Plugin.options[:template_location] = dirs
|
461
|
-
|
462
|
-
::Sass::Plugin.on_updated_stylesheet do |_, css, sourcemap|
|
463
|
-
[css, sourcemap].each do |file|
|
464
|
-
next unless file
|
465
|
-
puts_action :write, :green, file
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
had_error = false
|
470
|
-
::Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
|
471
|
-
::Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
|
472
|
-
::Sass::Plugin.on_deleting_sourcemap {|filename| puts_action :delete, :yellow, filename}
|
473
|
-
::Sass::Plugin.on_compilation_error do |error, _, _|
|
474
|
-
if error.is_a?(SystemCallError) && !@options[:stop_on_error]
|
475
|
-
had_error = true
|
476
|
-
puts_action :error, :red, error.message
|
477
|
-
STDOUT.flush
|
478
|
-
next
|
479
|
-
end
|
480
|
-
|
481
|
-
raise error unless error.is_a?(::Sass::SyntaxError) && !@options[:stop_on_error]
|
482
|
-
had_error = true
|
483
|
-
puts_action :error, :red,
|
484
|
-
"#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
|
485
|
-
STDOUT.flush
|
486
|
-
end
|
487
|
-
|
488
|
-
if @options[:update]
|
489
|
-
::Sass::Plugin.update_stylesheets(files)
|
490
|
-
exit 1 if had_error
|
491
|
-
return
|
492
|
-
end
|
493
|
-
|
494
|
-
puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
|
495
|
-
|
496
|
-
::Sass::Plugin.on_template_modified do |template|
|
497
|
-
puts ">>> Change detected to: #{template}"
|
498
|
-
STDOUT.flush
|
499
|
-
end
|
500
|
-
::Sass::Plugin.on_template_created do |template|
|
501
|
-
puts ">>> New template detected: #{template}"
|
502
|
-
STDOUT.flush
|
503
|
-
end
|
504
|
-
::Sass::Plugin.on_template_deleted do |template|
|
505
|
-
puts ">>> Deleted template detected: #{template}"
|
506
|
-
STDOUT.flush
|
507
|
-
end
|
508
|
-
|
509
|
-
::Sass::Plugin.watch(files)
|
510
|
-
end
|
511
|
-
# @comment
|
512
|
-
# rubocop:enable MethodLength
|
513
|
-
|
514
|
-
def colon_path?(path)
|
515
|
-
!split_colon_path(path)[1].nil?
|
516
|
-
end
|
517
|
-
|
518
|
-
def split_colon_path(path)
|
519
|
-
one, two = path.split(':', 2)
|
520
|
-
if one && two && ::Sass::Util.windows? &&
|
521
|
-
one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
|
522
|
-
# If we're on Windows and we were passed a drive letter path,
|
523
|
-
# don't split on that colon.
|
524
|
-
one2, two = two.split(':', 2)
|
525
|
-
one = one + ':' + one2
|
526
|
-
end
|
527
|
-
return one, two
|
528
|
-
end
|
529
|
-
|
530
|
-
# Whether path is likely to be meant as the destination
|
531
|
-
# in a source:dest pair.
|
532
|
-
def probably_dest_dir?(path)
|
533
|
-
return false unless path
|
534
|
-
return false if colon_path?(path)
|
535
|
-
::Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
|
536
|
-
end
|
537
|
-
|
538
|
-
def default_sass_path
|
539
|
-
if ENV['SASS_PATH']
|
540
|
-
# The select here prevents errors when the environment's
|
541
|
-
# load paths specified do not exist.
|
542
|
-
ENV['SASS_PATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)}
|
543
|
-
else
|
544
|
-
[::Sass::Importers::DeprecatedPath.new(".")]
|
545
|
-
end
|
546
|
-
end
|
547
|
-
end
|
548
|
-
|
549
|
-
class Scss < Sass
|
550
|
-
# @param args [Array<String>] The command-line arguments
|
551
|
-
def initialize(args)
|
552
|
-
super
|
553
|
-
@default_syntax = :scss
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
# The `sass-convert` executable.
|
558
|
-
class SassConvert < Generic
|
559
|
-
# @param args [Array<String>] The command-line arguments
|
560
|
-
def initialize(args)
|
561
|
-
super
|
562
|
-
require 'sass'
|
563
|
-
@options[:for_tree] = {}
|
564
|
-
@options[:for_engine] = {:cache => false, :read_cache => true}
|
565
|
-
end
|
566
|
-
|
567
|
-
# Tells optparse how to parse the arguments.
|
568
|
-
#
|
569
|
-
# @param opts [OptionParser]
|
570
|
-
# @comment
|
571
|
-
# rubocop:disable MethodLength
|
572
|
-
def set_opts(opts)
|
573
|
-
opts.banner = <<END
|
574
|
-
Usage: sass-convert [options] [INPUT] [OUTPUT]
|
575
|
-
|
576
|
-
Description:
|
577
|
-
Converts between CSS, Sass, and SCSS files.
|
578
|
-
E.g. converts from SCSS to Sass,
|
579
|
-
or converts from CSS to SCSS (adding appropriate nesting).
|
580
|
-
|
581
|
-
Options:
|
582
|
-
END
|
583
|
-
|
584
|
-
opts.on('-F', '--from FORMAT',
|
585
|
-
'The format to convert from. Can be css, scss, sass.',
|
586
|
-
'By default, this is inferred from the input filename.',
|
587
|
-
'If there is none, defaults to css.') do |name|
|
588
|
-
@options[:from] = name.downcase.to_sym
|
589
|
-
raise "sass-convert no longer supports LessCSS." if @options[:from] == :less
|
590
|
-
unless [:css, :scss, :sass].include?(@options[:from])
|
591
|
-
raise "Unknown format for sass-convert --from: #{name}"
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
opts.on('-T', '--to FORMAT',
|
596
|
-
'The format to convert to. Can be scss or sass.',
|
597
|
-
'By default, this is inferred from the output filename.',
|
598
|
-
'If there is none, defaults to sass.') do |name|
|
599
|
-
@options[:to] = name.downcase.to_sym
|
600
|
-
unless [:scss, :sass].include?(@options[:to])
|
601
|
-
raise "Unknown format for sass-convert --to: #{name}"
|
602
|
-
end
|
603
|
-
end
|
604
|
-
|
605
|
-
opts.on('-R', '--recursive',
|
606
|
-
'Convert all the files in a directory. Requires --from and --to.') do
|
607
|
-
@options[:recursive] = true
|
608
|
-
end
|
609
|
-
|
610
|
-
opts.on('-i', '--in-place',
|
611
|
-
'Convert a file to its own syntax.',
|
612
|
-
'This can be used to update some deprecated syntax.') do
|
613
|
-
@options[:in_place] = true
|
614
|
-
end
|
615
|
-
|
616
|
-
opts.on('--dasherize', 'Convert underscores to dashes') do
|
617
|
-
@options[:for_tree][:dasherize] = true
|
618
|
-
end
|
619
|
-
|
620
|
-
opts.on('--indent NUM',
|
621
|
-
'How many spaces to use for each level of indentation. Defaults to 2.',
|
622
|
-
'"t" means use hard tabs.') do |indent|
|
623
|
-
|
624
|
-
if indent == 't'
|
625
|
-
@options[:for_tree][:indent] = "\t"
|
626
|
-
else
|
627
|
-
@options[:for_tree][:indent] = " " * indent.to_i
|
628
|
-
end
|
629
|
-
end
|
630
|
-
|
631
|
-
opts.on('--old', 'Output the old-style ":prop val" property syntax.',
|
632
|
-
'Only meaningful when generating Sass.') do
|
633
|
-
@options[:for_tree][:old] = true
|
634
|
-
end
|
635
|
-
|
636
|
-
opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
|
637
|
-
@options[:for_engine][:read_cache] = false
|
638
|
-
end
|
639
|
-
|
640
|
-
unless ::Sass::Util.ruby1_8?
|
641
|
-
opts.on('-E encoding',
|
642
|
-
'Specify the default encoding for Sass and CSS files.') do |encoding|
|
643
|
-
Encoding.default_external = encoding
|
644
|
-
end
|
645
|
-
end
|
646
|
-
|
647
|
-
super
|
648
|
-
end
|
649
|
-
# @comment
|
650
|
-
# rubocop:enable MethodLength
|
651
|
-
|
652
|
-
# Processes the options set by the command-line arguments,
|
653
|
-
# and runs the CSS compiler appropriately.
|
654
|
-
def process_result
|
655
|
-
require 'sass'
|
656
|
-
|
657
|
-
if @options[:recursive]
|
658
|
-
process_directory
|
659
|
-
return
|
660
|
-
end
|
661
|
-
|
662
|
-
super
|
663
|
-
input = @options[:input]
|
664
|
-
if File.directory?(input)
|
665
|
-
raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)"
|
666
|
-
end
|
667
|
-
output = @options[:output]
|
668
|
-
output = input if @options[:in_place]
|
669
|
-
process_file(input, output)
|
670
|
-
end
|
671
|
-
|
672
|
-
private
|
673
|
-
|
674
|
-
def process_directory
|
675
|
-
unless (input = @options[:input] = @args.shift)
|
676
|
-
raise "Error: directory required when using --recursive."
|
677
|
-
end
|
678
|
-
|
679
|
-
output = @options[:output] = @args.shift
|
680
|
-
raise "Error: --from required when using --recursive." unless @options[:from]
|
681
|
-
raise "Error: --to required when using --recursive." unless @options[:to]
|
682
|
-
unless File.directory?(@options[:input])
|
683
|
-
raise "Error: '#{@options[:input]}' is not a directory"
|
684
|
-
end
|
685
|
-
if @options[:output] && File.exist?(@options[:output]) &&
|
686
|
-
!File.directory?(@options[:output])
|
687
|
-
raise "Error: '#{@options[:output]}' is not a directory"
|
688
|
-
end
|
689
|
-
@options[:output] ||= @options[:input]
|
690
|
-
|
691
|
-
if @options[:to] == @options[:from] && !@options[:in_place]
|
692
|
-
fmt = @options[:from]
|
693
|
-
raise "Error: converting from #{fmt} to #{fmt} without --in-place"
|
694
|
-
end
|
695
|
-
|
696
|
-
ext = @options[:from]
|
697
|
-
::Sass::Util.glob("#{@options[:input]}/**/*.#{ext}") do |f|
|
698
|
-
output =
|
699
|
-
if @options[:in_place]
|
700
|
-
f
|
701
|
-
elsif @options[:output]
|
702
|
-
output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
|
703
|
-
output_name[0...@options[:input].size] = @options[:output]
|
704
|
-
output_name
|
705
|
-
else
|
706
|
-
f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
|
707
|
-
end
|
708
|
-
|
709
|
-
unless File.directory?(File.dirname(output))
|
710
|
-
puts_action :directory, :green, File.dirname(output)
|
711
|
-
FileUtils.mkdir_p(File.dirname(output))
|
712
|
-
end
|
713
|
-
puts_action :convert, :green, f
|
714
|
-
if File.exist?(output)
|
715
|
-
puts_action :overwrite, :yellow, output
|
716
|
-
else
|
717
|
-
puts_action :create, :green, output
|
718
|
-
end
|
719
|
-
|
720
|
-
input = open_file(f)
|
721
|
-
process_file(input, output)
|
722
|
-
end
|
723
|
-
end
|
724
|
-
|
725
|
-
def process_file(input, output)
|
726
|
-
if input.is_a?(File)
|
727
|
-
@options[:from] ||=
|
728
|
-
case input.path
|
729
|
-
when /\.scss$/; :scss
|
730
|
-
when /\.sass$/; :sass
|
731
|
-
when /\.less$/; raise "sass-convert no longer supports LessCSS."
|
732
|
-
when /\.css$/; :css
|
733
|
-
end
|
734
|
-
elsif @options[:in_place]
|
735
|
-
raise "Error: the --in-place option requires a filename."
|
736
|
-
end
|
737
|
-
|
738
|
-
if output.is_a?(File)
|
739
|
-
@options[:to] ||=
|
740
|
-
case output.path
|
741
|
-
when /\.scss$/; :scss
|
742
|
-
when /\.sass$/; :sass
|
743
|
-
end
|
744
|
-
end
|
745
|
-
|
746
|
-
@options[:from] ||= :css
|
747
|
-
@options[:to] ||= :sass
|
748
|
-
@options[:for_engine][:syntax] = @options[:from]
|
749
|
-
|
750
|
-
out =
|
751
|
-
::Sass::Util.silence_sass_warnings do
|
752
|
-
if @options[:from] == :css
|
753
|
-
require 'sass/css'
|
754
|
-
::Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
|
755
|
-
else
|
756
|
-
if input.is_a?(File)
|
757
|
-
::Sass::Engine.for_file(input.path, @options[:for_engine])
|
758
|
-
else
|
759
|
-
::Sass::Engine.new(input.read, @options[:for_engine])
|
760
|
-
end.to_tree.send("to_#{@options[:to]}", @options[:for_tree])
|
761
|
-
end
|
762
|
-
end
|
763
|
-
|
764
|
-
output = input.path if @options[:in_place]
|
765
|
-
write_output(out, output)
|
766
|
-
rescue ::Sass::SyntaxError => e
|
767
|
-
raise e if @options[:trace]
|
768
|
-
file = " of #{e.sass_filename}" if e.sass_filename
|
769
|
-
raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
|
770
|
-
rescue LoadError => err
|
771
|
-
handle_load_error(err)
|
772
|
-
end
|
773
|
-
end
|
774
4
|
end
|
775
5
|
end
|
6
|
+
|
7
|
+
require 'sass/exec/base'
|
8
|
+
require 'sass/exec/sass_scss'
|
9
|
+
require 'sass/exec/sass_convert'
|