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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 503f195bf2c3fc4bac4decaf569d625239681016
4
- data.tar.gz: d00a8da887a958af2fb5a985cb29b8bf23e9cc12
3
+ metadata.gz: 86dadd4faf8e93f96f022369b902622f225c18c8
4
+ data.tar.gz: da7eab9db6a3ae2c4bb925c5712ed1e6e56aa135
5
5
  SHA512:
6
- metadata.gz: 89e6b4e3fd304bdf2c9078b8e331b026cb0e74b1dfc07f1b6b4a4e08d2408301ad2c170eb72d91af9bd0ba9979e2b863b975904b2a15d6b221aa5ffe87374f69
7
- data.tar.gz: fd12d1fbfc85b9e5e15b2683873e935dd38117e101f8f74402228c0238f3f60b9672c4bba8d6ce0761b167171fc799548dc53623b2f7b9296f5588a2a7bb49c9
6
+ metadata.gz: bf8198a80fd2741f7692e61345a96feb2013084f9c72633a69d6d4aa678fa816f779e09e1cbc604f6b7323cee7bbf1fb98da1b5d7e4d5a5d900ae2b428d38da2
7
+ data.tar.gz: 64a53c27f497151504a188189e70369e1bb57207b7d6f2a3e04b94aa6fc36b29f66ed9151bc8ce175f90bc1b0e893ee7f81dc63d68c4feb6334136649c5cf9f5
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2014 Hampton Catlin, Natalie Weizenbaum, and Chris Eppstein
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
- [Natalie Weizenbaum](http://nex-3.com) is the primary developer and architect of
203
- Sass. Her hard work has kept the project alive by endlessly answering forum
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 girl genius). Natalie lives in Seattle, Washington and works on
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 buy Natalie
218
- some candy. Maybe pet a kitten. Yeah. Pet that kitty.
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.3.14
1
+ 3.4.0.rc.1
@@ -1 +1 @@
1
- 02 August 2014 01:35:06 UTC
1
+ 12 July 2014 01:42:30 UTC
@@ -1 +1 @@
1
- Maptastic Maple
1
+ Selective Steve
data/bin/sass CHANGED
@@ -9,5 +9,5 @@ rescue LoadError
9
9
  end
10
10
  require 'sass/exec'
11
11
 
12
- opts = Sass::Exec::Sass.new(ARGV)
12
+ opts = Sass::Exec::SassScss.new(ARGV, :sass)
13
13
  opts.parse!
data/bin/scss CHANGED
@@ -9,5 +9,5 @@ rescue LoadError
9
9
  end
10
10
  require 'sass/exec'
11
11
 
12
- opts = Sass::Exec::Scss.new(ARGV)
12
+ opts = Sass::Exec::SassScss.new(ARGV, :scss)
13
13
  opts.parse!
@@ -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:
@@ -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) do |msg, line|
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
@@ -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 encode_and_set_charset(_to_tree.render) unless @options[:quiet]
275
- Sass::Util.silence_sass_warnings {encode_and_set_charset(_to_tree.render)}
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
- @original_encoding
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 {importer.public_url(filename, sourcemap_dir).nil?}
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, @original_encoding = Sass::Util.check_sass_encoding(@template) do |msg, line|
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.gsub(/\r\n|\r|\n/, "\n").scan(/^[^\n]*?$/).each_with_index do |line, index|
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 (str = scanner.scan(Sass::SCSS::RX::STRING))
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 += str.length
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(str || uri, media.to_a)
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("url(#{val})")
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)),
@@ -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
- @global_warning_given = Set.new
100
- @deprecated_false_warning_given = Set.new
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 ||= @parent.nil? ? self : @parent.global_env
127
+ @global_env ||= global? ? self : @parent.global_env
132
128
  end
133
129
 
134
130
  # The import/mixin stack.
@@ -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" + (" " * "Syntax error: ".size) + l}.join
144
- "Syntax error: #{msg}" +
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 options [{Symbol => Object}] The options passed to {Sass::Engine#initialize}
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, options)
162
- raise e unless options[:full_exception]
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, options)
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]
@@ -1,775 +1,9 @@
1
- require 'optparse'
2
- require 'fileutils'
3
-
4
1
  module Sass
5
- # This module handles the various Sass executables (`sass` and `sass-convert`).
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'