sass 3.2.7 → 3.3.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.
Files changed (184) hide show
  1. data/MIT-LICENSE +2 -2
  2. data/README.md +14 -2
  3. data/Rakefile +25 -1
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/lib/sass/cache_stores/base.rb +4 -2
  8. data/lib/sass/cache_stores/chain.rb +2 -1
  9. data/lib/sass/cache_stores/filesystem.rb +2 -6
  10. data/lib/sass/cache_stores/memory.rb +1 -1
  11. data/lib/sass/cache_stores/null.rb +2 -2
  12. data/lib/sass/callbacks.rb +1 -0
  13. data/lib/sass/css.rb +10 -10
  14. data/lib/sass/engine.rb +403 -150
  15. data/lib/sass/environment.rb +136 -57
  16. data/lib/sass/error.rb +7 -7
  17. data/lib/sass/exec.rb +123 -39
  18. data/lib/sass/features.rb +41 -0
  19. data/lib/sass/importers/base.rb +33 -2
  20. data/lib/sass/importers/deprecated_path.rb +45 -0
  21. data/lib/sass/importers/filesystem.rb +25 -14
  22. data/lib/sass/importers.rb +1 -0
  23. data/lib/sass/logger/base.rb +3 -3
  24. data/lib/sass/logger/log_level.rb +4 -6
  25. data/lib/sass/media.rb +19 -19
  26. data/lib/sass/plugin/compiler.rb +141 -101
  27. data/lib/sass/plugin/configuration.rb +18 -22
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/staleness_checker.rb +24 -8
  30. data/lib/sass/plugin.rb +4 -2
  31. data/lib/sass/repl.rb +3 -3
  32. data/lib/sass/script/css_lexer.rb +9 -4
  33. data/lib/sass/script/css_parser.rb +6 -2
  34. data/lib/sass/script/functions.rb +1343 -590
  35. data/lib/sass/script/lexer.rb +84 -52
  36. data/lib/sass/script/parser.rb +217 -97
  37. data/lib/sass/script/tree/funcall.rb +290 -0
  38. data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +34 -13
  39. data/lib/sass/script/tree/list_literal.rb +80 -0
  40. data/lib/sass/script/tree/literal.rb +47 -0
  41. data/lib/sass/script/tree/map_literal.rb +64 -0
  42. data/lib/sass/script/{node.rb → tree/node.rb} +22 -12
  43. data/lib/sass/script/{operation.rb → tree/operation.rb} +17 -25
  44. data/lib/sass/script/tree/selector.rb +30 -0
  45. data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +5 -4
  46. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +14 -9
  47. data/lib/sass/script/tree/variable.rb +57 -0
  48. data/lib/sass/script/tree.rb +16 -0
  49. data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +4 -24
  50. data/lib/sass/script/value/base.rb +248 -0
  51. data/lib/sass/script/value/bool.rb +36 -0
  52. data/lib/sass/script/{color.rb → value/color.rb} +239 -195
  53. data/lib/sass/script/value/helpers.rb +155 -0
  54. data/lib/sass/script/value/list.rb +119 -0
  55. data/lib/sass/script/value/map.rb +70 -0
  56. data/lib/sass/script/value/null.rb +45 -0
  57. data/lib/sass/script/{number.rb → value/number.rb} +91 -65
  58. data/lib/sass/script/{string.rb → value/string.rb} +9 -11
  59. data/lib/sass/script/value.rb +11 -0
  60. data/lib/sass/script.rb +35 -8
  61. data/lib/sass/scss/css_parser.rb +2 -1
  62. data/lib/sass/scss/parser.rb +338 -170
  63. data/lib/sass/scss/rx.rb +5 -6
  64. data/lib/sass/scss/script_lexer.rb +1 -0
  65. data/lib/sass/scss/script_parser.rb +1 -0
  66. data/lib/sass/scss/static_parser.rb +23 -6
  67. data/lib/sass/selector/abstract_sequence.rb +2 -2
  68. data/lib/sass/selector/comma_sequence.rb +21 -16
  69. data/lib/sass/selector/sequence.rb +60 -34
  70. data/lib/sass/selector/simple.rb +11 -12
  71. data/lib/sass/selector/simple_sequence.rb +55 -33
  72. data/lib/sass/selector.rb +52 -48
  73. data/lib/sass/source/map.rb +211 -0
  74. data/lib/sass/source/position.rb +39 -0
  75. data/lib/sass/source/range.rb +41 -0
  76. data/lib/sass/stack.rb +120 -0
  77. data/lib/sass/supports.rb +12 -13
  78. data/lib/sass/tree/at_root_node.rb +82 -0
  79. data/lib/sass/tree/comment_node.rb +3 -3
  80. data/lib/sass/tree/css_import_node.rb +11 -11
  81. data/lib/sass/tree/debug_node.rb +2 -2
  82. data/lib/sass/tree/directive_node.rb +13 -2
  83. data/lib/sass/tree/each_node.rb +8 -8
  84. data/lib/sass/tree/extend_node.rb +13 -6
  85. data/lib/sass/tree/for_node.rb +4 -4
  86. data/lib/sass/tree/function_node.rb +5 -4
  87. data/lib/sass/tree/if_node.rb +1 -1
  88. data/lib/sass/tree/import_node.rb +4 -5
  89. data/lib/sass/tree/media_node.rb +4 -14
  90. data/lib/sass/tree/mixin_def_node.rb +4 -4
  91. data/lib/sass/tree/mixin_node.rb +21 -8
  92. data/lib/sass/tree/node.rb +29 -12
  93. data/lib/sass/tree/prop_node.rb +38 -18
  94. data/lib/sass/tree/return_node.rb +3 -2
  95. data/lib/sass/tree/root_node.rb +19 -3
  96. data/lib/sass/tree/rule_node.rb +25 -17
  97. data/lib/sass/tree/supports_node.rb +0 -13
  98. data/lib/sass/tree/trace_node.rb +2 -1
  99. data/lib/sass/tree/variable_node.rb +9 -3
  100. data/lib/sass/tree/visitors/base.rb +6 -6
  101. data/lib/sass/tree/visitors/check_nesting.rb +12 -9
  102. data/lib/sass/tree/visitors/convert.rb +63 -38
  103. data/lib/sass/tree/visitors/cssize.rb +63 -23
  104. data/lib/sass/tree/visitors/deep_copy.rb +6 -5
  105. data/lib/sass/tree/visitors/extend.rb +7 -7
  106. data/lib/sass/tree/visitors/perform.rb +256 -151
  107. data/lib/sass/tree/visitors/set_options.rb +6 -6
  108. data/lib/sass/tree/visitors/to_css.rb +231 -81
  109. data/lib/sass/tree/warn_node.rb +2 -2
  110. data/lib/sass/tree/while_node.rb +2 -2
  111. data/lib/sass/util/multibyte_string_scanner.rb +2 -0
  112. data/lib/sass/util/normalized_map.rb +65 -0
  113. data/lib/sass/util/ordered_hash.rb +188 -0
  114. data/lib/sass/util/subset_map.rb +3 -2
  115. data/lib/sass/util/test.rb +9 -0
  116. data/lib/sass/util.rb +220 -34
  117. data/lib/sass/version.rb +9 -9
  118. data/lib/sass.rb +14 -7
  119. data/test/sass/compiler_test.rb +213 -0
  120. data/test/sass/conversion_test.rb +235 -9
  121. data/test/sass/engine_test.rb +230 -60
  122. data/test/sass/exec_test.rb +86 -0
  123. data/test/sass/extend_test.rb +215 -147
  124. data/test/sass/functions_test.rb +584 -99
  125. data/test/sass/importer_test.rb +165 -17
  126. data/test/sass/plugin_test.rb +19 -13
  127. data/test/sass/script_conversion_test.rb +40 -0
  128. data/test/sass/script_test.rb +231 -21
  129. data/test/sass/scss/css_test.rb +14 -5
  130. data/test/sass/scss/scss_test.rb +1266 -66
  131. data/test/sass/source_map_test.rb +879 -0
  132. data/test/sass/templates/bork5.sass +3 -0
  133. data/test/sass/util/normalized_map_test.rb +30 -0
  134. data/test/sass/util_test.rb +90 -0
  135. data/test/sass/value_helpers_test.rb +181 -0
  136. data/test/test_helper.rb +7 -2
  137. metadata +316 -291
  138. data/lib/sass/script/bool.rb +0 -18
  139. data/lib/sass/script/funcall.rb +0 -231
  140. data/lib/sass/script/list.rb +0 -84
  141. data/lib/sass/script/literal.rb +0 -239
  142. data/lib/sass/script/null.rb +0 -34
  143. data/lib/sass/script/variable.rb +0 -58
  144. data/test/Gemfile +0 -3
  145. data/vendor/listen/CHANGELOG.md +0 -221
  146. data/vendor/listen/CONTRIBUTING.md +0 -38
  147. data/vendor/listen/Gemfile +0 -30
  148. data/vendor/listen/Guardfile +0 -8
  149. data/vendor/listen/LICENSE +0 -20
  150. data/vendor/listen/README.md +0 -315
  151. data/vendor/listen/Rakefile +0 -47
  152. data/vendor/listen/Vagrantfile +0 -96
  153. data/vendor/listen/lib/listen/adapter.rb +0 -214
  154. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -112
  155. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -85
  156. data/vendor/listen/lib/listen/adapters/linux.rb +0 -113
  157. data/vendor/listen/lib/listen/adapters/polling.rb +0 -67
  158. data/vendor/listen/lib/listen/adapters/windows.rb +0 -87
  159. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  160. data/vendor/listen/lib/listen/directory_record.rb +0 -371
  161. data/vendor/listen/lib/listen/listener.rb +0 -225
  162. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  163. data/vendor/listen/lib/listen/turnstile.rb +0 -28
  164. data/vendor/listen/lib/listen/version.rb +0 -3
  165. data/vendor/listen/lib/listen.rb +0 -40
  166. data/vendor/listen/listen.gemspec +0 -22
  167. data/vendor/listen/spec/listen/adapter_spec.rb +0 -183
  168. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  169. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  170. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  171. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  172. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  173. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  174. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1225
  175. data/vendor/listen/spec/listen/listener_spec.rb +0 -169
  176. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
  177. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  178. data/vendor/listen/spec/listen_spec.rb +0 -73
  179. data/vendor/listen/spec/spec_helper.rb +0 -21
  180. data/vendor/listen/spec/support/adapter_helper.rb +0 -629
  181. data/vendor/listen/spec/support/directory_record_helper.rb +0 -55
  182. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  183. data/vendor/listen/spec/support/listeners_helper.rb +0 -156
  184. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -1,60 +1,20 @@
1
1
  require 'set'
2
2
 
3
3
  module Sass
4
- # The lexical environment for SassScript.
5
- # This keeps track of variable, mixin, and function definitions.
6
- #
7
- # A new environment is created for each level of Sass nesting.
8
- # This allows variables to be lexically scoped.
9
- # The new environment refers to the environment in the upper scope,
10
- # so it has access to variables defined in enclosing scopes,
11
- # but new variables are defined locally.
12
- #
13
- # Environment also keeps track of the {Engine} options
14
- # so that they can be made available to {Sass::Script::Functions}.
15
- class Environment
16
- # The enclosing environment,
17
- # or nil if this is the global environment.
18
- #
19
- # @return [Environment]
20
- attr_reader :parent
21
- attr_reader :options
22
- attr_writer :caller
23
- attr_writer :content
24
-
25
- # @param options [{Symbol => Object}] The options hash. See
26
- # {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
27
- # @param parent [Environment] See \{#parent}
28
- def initialize(parent = nil, options = nil)
29
- @parent = parent
30
- @options = options || (parent && parent.options) || {}
31
- end
32
-
33
- # The environment of the caller of this environment's mixin or function.
34
- # @return {Environment?}
35
- def caller
36
- @caller || (@parent && @parent.caller)
37
- end
38
-
39
- # The content passed to this environmnet. This is naturally only set
40
- # for mixin body environments with content passed in.
41
- # @return {Environment?}
42
- def content
43
- @content || (@parent && @parent.content)
44
- end
45
-
46
- private
47
-
4
+ # The abstract base class for lexical environments for SassScript.
5
+ class BaseEnvironment
48
6
  class << self
49
- private
50
- UNDERSCORE, DASH = '_', '-'
51
-
52
7
  # Note: when updating this,
53
8
  # update sass/yard/inherited_hash.rb as well.
54
- def inherited_hash(name)
55
- class_eval <<RUBY, __FILE__, __LINE__ + 1
9
+ def inherited_hash_accessor(name)
10
+ inherited_hash_reader(name)
11
+ inherited_hash_writer(name)
12
+ end
13
+
14
+ def inherited_hash_reader(name)
15
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
56
16
  def #{name}(name)
57
- _#{name}(name.tr(UNDERSCORE, DASH))
17
+ _#{name}(name.tr('_', '-'))
58
18
  end
59
19
 
60
20
  def _#{name}(name)
@@ -62,8 +22,17 @@ module Sass
62
22
  end
63
23
  protected :_#{name}
64
24
 
25
+ def is_#{name}_global?(name)
26
+ return !@parent if @#{name}s && @#{name}s.has_key?(name)
27
+ @parent && @parent.is_#{name}_global?(name)
28
+ end
29
+ RUBY
30
+ end
31
+
32
+ def inherited_hash_writer(name)
33
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
65
34
  def set_#{name}(name, value)
66
- name = name.tr(UNDERSCORE, DASH)
35
+ name = name.tr('_', '-')
67
36
  @#{name}s[name] = value unless try_set_#{name}(name, value)
68
37
  end
69
38
 
@@ -82,20 +51,130 @@ module Sass
82
51
 
83
52
  def set_local_#{name}(name, value)
84
53
  @#{name}s ||= {}
85
- @#{name}s[name.tr(UNDERSCORE, DASH)] = value
54
+ @#{name}s[name.tr('_', '-')] = value
86
55
  end
87
- RUBY
56
+ RUBY
88
57
  end
89
58
  end
90
59
 
60
+ # The options passed to the Sass Engine.
61
+ attr_reader :options
62
+
63
+ attr_writer :caller
64
+ attr_writer :content
65
+ attr_writer :selector
66
+
91
67
  # variable
92
- # Script::Literal
93
- inherited_hash :var
68
+ # Script::Value
69
+ inherited_hash_reader :var
70
+
94
71
  # mixin
95
72
  # Sass::Callable
96
- inherited_hash :mixin
73
+ inherited_hash_reader :mixin
74
+
97
75
  # function
98
76
  # Sass::Callable
99
- inherited_hash :function
77
+ inherited_hash_reader :function
78
+
79
+ # @param options [{Symbol => Object}] The options hash. See
80
+ # {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
81
+ # @param parent [Environment] See \{#parent}
82
+ def initialize(parent = nil, options = nil)
83
+ @parent = parent
84
+ @options = options || (parent && parent.options) || {}
85
+ @stack = Sass::Stack.new if @parent.nil?
86
+ end
87
+
88
+ # The environment of the caller of this environment's mixin or function.
89
+ # @return {Environment?}
90
+ def caller
91
+ @caller || (@parent && @parent.caller)
92
+ end
93
+
94
+ # The content passed to this environment. This is naturally only set
95
+ # for mixin body environments with content passed in.
96
+ #
97
+ # @return {[Array<Sass::Tree::Node>, Environment]?} The content nodes and
98
+ # the lexical environment of the content block.
99
+ def content
100
+ @content || (@parent && @parent.content)
101
+ end
102
+
103
+ # The selector for the current CSS rule, or nil if there is no
104
+ # current CSS rule.
105
+ #
106
+ # @return [Selector::CommaSequence?] The current selector, with any
107
+ # nesting fully resolved.
108
+ def selector
109
+ @selector || (@caller && @caller.selector) || (@parent && @parent.selector)
110
+ end
111
+
112
+ # The top-level Environment object.
113
+ #
114
+ # @return [Environment]
115
+ def global_env
116
+ @global_env ||= @parent.nil? ? self : @parent.global_env
117
+ end
118
+
119
+ # The import/mixin stack.
120
+ #
121
+ # @return [Sass::Stack]
122
+ def stack
123
+ @stack || global_env.stack
124
+ end
125
+ end
126
+
127
+ # The lexical environment for SassScript.
128
+ # This keeps track of variable, mixin, and function definitions.
129
+ #
130
+ # A new environment is created for each level of Sass nesting.
131
+ # This allows variables to be lexically scoped.
132
+ # The new environment refers to the environment in the upper scope,
133
+ # so it has access to variables defined in enclosing scopes,
134
+ # but new variables are defined locally.
135
+ #
136
+ # Environment also keeps track of the {Engine} options
137
+ # so that they can be made available to {Sass::Script::Functions}.
138
+ class Environment < BaseEnvironment
139
+ # The enclosing environment,
140
+ # or nil if this is the global environment.
141
+ #
142
+ # @return [Environment]
143
+ attr_reader :parent
144
+
145
+ # variable
146
+ # Script::Value
147
+ inherited_hash_writer :var
148
+
149
+ # mixin
150
+ # Sass::Callable
151
+ inherited_hash_writer :mixin
152
+
153
+ # function
154
+ # Sass::Callable
155
+ inherited_hash_writer :function
156
+ end
157
+
158
+ # A read-only wrapper for a lexical environment for SassScript.
159
+ class ReadOnlyEnvironment < BaseEnvironment
160
+ # The read-only environment of the caller of this environment's mixin or function.
161
+ #
162
+ # @see BaseEnvironment#caller
163
+ # @return {ReadOnlyEnvironment}
164
+ def caller
165
+ return @caller if @caller
166
+ env = super
167
+ @caller ||= env.is_a?(ReadOnlyEnvironment) ? env : ReadOnlyEnvironment.new(env, env.options)
168
+ end
169
+
170
+ # The read-only content passed to this environment.
171
+ #
172
+ # @see BaseEnvironment#content
173
+ # @return {ReadOnlyEnvironment}
174
+ def content
175
+ return @content if @content
176
+ env = super
177
+ @content ||= env.is_a?(ReadOnlyEnvironment) ? env : ReadOnlyEnvironment.new(env, env.options)
178
+ end
100
179
  end
101
180
  end
data/lib/sass/error.rb CHANGED
@@ -138,15 +138,15 @@ module Sass
138
138
  # @see #sass_backtrace
139
139
  # @return [String]
140
140
  def sass_backtrace_str(default_filename = "an unknown file")
141
- lines = self.message.split("\n")
141
+ lines = message.split("\n")
142
142
  msg = lines[0] + lines[1..-1].
143
143
  map {|l| "\n" + (" " * "Syntax error: ".size) + l}.join
144
144
  "Syntax error: #{msg}" +
145
145
  Sass::Util.enum_with_index(sass_backtrace).map do |entry, i|
146
- "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
147
- " of #{entry[:filename] || default_filename}" +
148
- (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
149
- end.join
146
+ "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
147
+ " of #{entry[:filename] || default_filename}" +
148
+ (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
149
+ end.join
150
150
  end
151
151
 
152
152
  class << self
@@ -165,9 +165,9 @@ module Sass
165
165
 
166
166
  <<END
167
167
  /*
168
- #{header}
168
+ #{header.gsub("*/", "*\\/")}
169
169
 
170
- Backtrace:\n#{e.backtrace.join("\n")}
170
+ Backtrace:\n#{e.backtrace.join("\n").gsub("*/", "*\\/")}
171
171
  */
172
172
  body:before {
173
173
  white-space: pre;
data/lib/sass/exec.rb CHANGED
@@ -17,6 +17,7 @@ module Sass
17
17
  #
18
18
  # @see #parse
19
19
  def parse!
20
+ # rubocop:disable RescueException
20
21
  begin
21
22
  parse
22
23
  rescue Exception => e
@@ -28,6 +29,7 @@ module Sass
28
29
  exit 1
29
30
  end
30
31
  exit 0
32
+ # rubocop:enable RescueException
31
33
  end
32
34
 
33
35
  # Parses the command-line arguments and runs the executable.
@@ -58,7 +60,9 @@ module Sass
58
60
  def get_line(exception)
59
61
  # SyntaxErrors have weird line reporting
60
62
  # when there's trailing whitespace
61
- return (exception.message.scan(/:(\d+)/).first || ["??"]).first if exception.is_a?(::SyntaxError)
63
+ if exception.is_a?(::SyntaxError)
64
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first
65
+ end
62
66
  (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
63
67
  end
64
68
 
@@ -70,7 +74,8 @@ module Sass
70
74
  #
71
75
  # @param opts [OptionParser]
72
76
  def set_opts(opts)
73
- opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
77
+ opts.on('-s', '--stdin', :NONE,
78
+ 'Read input from standard input instead of an input file') do
74
79
  @options[:input] = $stdin
75
80
  end
76
81
 
@@ -95,6 +100,7 @@ module Sass
95
100
 
96
101
  # Processes the options set by the command-line arguments.
97
102
  # In particular, sets `@options[:input]` and `@options[:output]`
103
+ # (and `@options[:sourcemap]` if one has been specified)
98
104
  # to appropriate IO streams.
99
105
  #
100
106
  # This is meant to be overridden by subclasses
@@ -108,12 +114,17 @@ module Sass
108
114
  @options[:filename] = filename
109
115
  open_file(filename) || $stdin
110
116
  end
111
- output ||= open_file(args.shift, 'w') || $stdout
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
112
123
 
113
124
  @options[:input], @options[:output] = input, output
114
125
  end
115
126
 
116
- COLORS = { :red => 31, :green => 32, :yellow => 33 }
127
+ COLORS = {:red => 31, :green => 32, :yellow => 33}
117
128
 
118
129
  # Prints a status message about performing the given action,
119
130
  # colored using the given color (via terminal escapes) if possible.
@@ -128,9 +139,9 @@ module Sass
128
139
  STDOUT.flush
129
140
  end
130
141
 
131
- # Same as \{Kernel.puts}, but doesn't print anything if the `--quiet` option is set.
142
+ # Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
132
143
  #
133
- # @param args [Array] Passed on to \{Kernel.puts}
144
+ # @param args [Array] Passed on to `Kernel.puts`
134
145
  def puts(*args)
135
146
  return if @options[:for_engine][:quiet]
136
147
  Kernel.puts(*args)
@@ -152,7 +163,15 @@ module Sass
152
163
  # so we just filter for Windows terms (which don't set TERM)
153
164
  # and not-real terminals, which aren't ttys.
154
165
  return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
155
- return "\e[#{COLORS[color]}m#{str}\e[0m"
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
156
175
  end
157
176
 
158
177
  private
@@ -160,7 +179,10 @@ module Sass
160
179
  def open_file(filename, flag = 'r')
161
180
  return if filename.nil?
162
181
  flag = 'wb' if @options[:unix_newlines] && flag == 'w'
163
- File.open(filename, flag)
182
+ file = File.open(filename, flag)
183
+ return file unless block_given?
184
+ yield file
185
+ file.close
164
186
  end
165
187
 
166
188
  def handle_load_error(err)
@@ -183,7 +205,7 @@ MESSAGE
183
205
  def initialize(args)
184
206
  super
185
207
  @options[:for_engine] = {
186
- :load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
208
+ :load_paths => default_sass_path
187
209
  }
188
210
  @default_syntax = :sass
189
211
  end
@@ -193,6 +215,8 @@ MESSAGE
193
215
  # Tells optparse how to parse the arguments.
194
216
  #
195
217
  # @param opts [OptionParser]
218
+ # @comment
219
+ # rubocop:disable MethodLength
196
220
  def set_opts(opts)
197
221
  super
198
222
 
@@ -243,13 +267,14 @@ END
243
267
  @options[:check_syntax] = true
244
268
  @options[:output] = StringIO.new
245
269
  end
246
- opts.on('-t', '--style NAME',
247
- 'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
270
+ style_desc = 'Output style. Can be nested (default), compact, compressed, or expanded.'
271
+ opts.on('-t', '--style NAME', style_desc) do |name|
248
272
  @options[:for_engine][:style] = name.to_sym
249
273
  end
250
274
  opts.on('--precision NUMBER_OF_DIGITS', Integer,
251
- 'How many digits of precision to use when outputting decimal numbers. Defaults to 3.') do |precision|
252
- ::Sass::Script::Number.precision = precision
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
253
278
  end
254
279
  opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
255
280
  @options[:for_engine][:quiet] = true
@@ -258,7 +283,7 @@ END
258
283
  @options[:compass] = true
259
284
  end
260
285
  opts.on('-g', '--debug-info',
261
- 'Emit extra information in the generated CSS that can be used by the FireSass Firebug plugin.') do
286
+ 'Emit output that can be used by the FireSass Firebug plugin.') do
262
287
  @options[:for_engine][:debug_info] = true
263
288
  end
264
289
  opts.on('-l', '--line-numbers', '--line-comments',
@@ -275,19 +300,33 @@ END
275
300
  opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
276
301
  require lib
277
302
  end
278
- opts.on('--cache-location PATH', 'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
303
+ opts.on('--cache-location PATH',
304
+ 'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
279
305
  @options[:for_engine][:cache_location] = loc
280
306
  end
281
307
  opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
282
308
  @options[:for_engine][:cache] = false
283
309
  end
310
+ opts.on('--sourcemap', 'Create sourcemap files next to the generated CSS files.') do
311
+ @options[:sourcemap] = true
312
+ end
284
313
 
285
- unless ::Sass::Util.ruby1_8?
286
- opts.on('-E encoding', 'Specify the default encoding for Sass files.') do |encoding|
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 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
287
324
  Encoding.default_external = encoding
288
325
  end
289
326
  end
290
327
  end
328
+ # @comment
329
+ # rubocop:enable MethodLength
291
330
 
292
331
  # Processes the options set by the command-line arguments,
293
332
  # and runs the Sass compiler appropriately.
@@ -307,7 +346,7 @@ END
307
346
  return watch_or_update if @options[:watch] || @options[:update]
308
347
  super
309
348
  @options[:for_engine][:filename] = @options[:filename]
310
- @options[:for_engine][:css_filename] = @options[:output].path if @options[:output].is_a?(File)
349
+ @options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
311
350
 
312
351
  begin
313
352
  input = @options[:input]
@@ -322,16 +361,32 @@ END
322
361
  # We don't need to do any special handling of @options[:check_syntax] here,
323
362
  # because the Sass syntax checking happens alongside evaluation
324
363
  # and evaluation doesn't actually evaluate any code anyway.
325
- ::Sass::Engine.new(input.read(), @options[:for_engine])
364
+ ::Sass::Engine.new(input.read, @options[:for_engine])
326
365
  end
327
366
 
328
- input.close() if input.is_a?(File)
367
+ input.close if input.is_a?(File)
368
+
369
+ if @options[:sourcemap]
370
+ unless @options[:sourcemap_filename]
371
+ raise "Can't generate a sourcemap for an input without a path."
372
+ end
329
373
 
330
- output.write(engine.render)
331
- output.close() if output.is_a? File
374
+ relative_sourcemap_path = Pathname.new(@options[:sourcemap_filename]).
375
+ relative_path_from(Pathname.new(@options[:output_filename]).dirname)
376
+ rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s)
377
+ write_output(rendered, output)
378
+ write_output(mapping.to_json(
379
+ :css_path => @options[:output_filename],
380
+ :sourcemap_path => @options[:sourcemap_filename]) + "\n",
381
+ @options[:sourcemap_filename])
382
+ else
383
+ write_output(engine.render, output)
384
+ end
332
385
  rescue ::Sass::SyntaxError => e
333
386
  raise e if @options[:trace]
334
387
  raise e.sass_backtrace_str("standard input")
388
+ ensure
389
+ output.close if output.is_a? File
335
390
  end
336
391
  end
337
392
 
@@ -359,11 +414,14 @@ END
359
414
  ::Sass::Repl.new(@options).run
360
415
  end
361
416
 
417
+ # @comment
418
+ # rubocop:disable MethodLength
362
419
  def watch_or_update
363
420
  require 'sass/plugin'
364
421
  ::Sass::Plugin.options.merge! @options[:for_engine]
365
422
  ::Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
366
423
  ::Sass::Plugin.options[:poll] = @options[:poll]
424
+ ::Sass::Plugin.options[:sourcemap] = @options[:sourcemap]
367
425
 
368
426
  if @options[:force]
369
427
  raise "The --force flag may only be used with --update." unless @options[:update]
@@ -392,21 +450,25 @@ MSG
392
450
 
393
451
  dirs, files = @args.map {|name| split_colon_path(name)}.
394
452
  partition {|i, _| File.directory? i}
395
- files.map! {|from, to| [from, to || from.gsub(/\.[^.]*?$/, '.css')]}
453
+ files.map! do |from, to|
454
+ to ||= from.gsub(/\.[^.]*?$/, '.css')
455
+ sourcemap = Util.sourcemap_name(to) if @options[:sourcemap]
456
+ [from, to, sourcemap]
457
+ end
396
458
  dirs.map! {|from, to| [from, to || from]}
397
459
  ::Sass::Plugin.options[:template_location] = dirs
398
460
 
399
- ::Sass::Plugin.on_updated_stylesheet do |_, css|
400
- if File.exists? css
401
- puts_action :overwrite, :yellow, css
402
- else
403
- puts_action :create, :green, css
461
+ ::Sass::Plugin.on_updated_stylesheet do |_, css, sourcemap|
462
+ [css, sourcemap].each do |file|
463
+ next unless file
464
+ puts_action :write, :green, file
404
465
  end
405
466
  end
406
467
 
407
468
  had_error = false
408
469
  ::Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
409
470
  ::Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
471
+ ::Sass::Plugin.on_deleting_sourcemap {|filename| puts_action :delete, :yellow, filename}
410
472
  ::Sass::Plugin.on_compilation_error do |error, _, _|
411
473
  if error.is_a?(SystemCallError) && !@options[:stop_on_error]
412
474
  had_error = true
@@ -417,7 +479,8 @@ MSG
417
479
 
418
480
  raise error unless error.is_a?(::Sass::SyntaxError) && !@options[:stop_on_error]
419
481
  had_error = true
420
- puts_action :error, :red, "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
482
+ puts_action :error, :red,
483
+ "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
421
484
  STDOUT.flush
422
485
  end
423
486
 
@@ -444,6 +507,8 @@ MSG
444
507
 
445
508
  ::Sass::Plugin.watch(files)
446
509
  end
510
+ # @comment
511
+ # rubocop:enable MethodLength
447
512
 
448
513
  def colon_path?(path)
449
514
  !split_colon_path(path)[1].nil?
@@ -466,8 +531,19 @@ MSG
466
531
  def probably_dest_dir?(path)
467
532
  return false unless path
468
533
  return false if colon_path?(path)
469
- return ::Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
534
+ ::Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
535
+ end
536
+
537
+ def default_sass_path
538
+ if ENV['SASSPATH']
539
+ # The select here prevents errors when the environment's
540
+ # load paths specified do not exist.
541
+ ENV['SASSPATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)}
542
+ else
543
+ [::Sass::Importers::DeprecatedPath.new(".")]
544
+ end
470
545
  end
546
+
471
547
  end
472
548
 
473
549
  class Scss < Sass
@@ -491,6 +567,8 @@ MSG
491
567
  # Tells optparse how to parse the arguments.
492
568
  #
493
569
  # @param opts [OptionParser]
570
+ # @comment
571
+ # rubocop:disable MethodLength
494
572
  def set_opts(opts)
495
573
  opts.banner = <<END
496
574
  Usage: sass-convert [options] [INPUT] [OUTPUT]
@@ -560,13 +638,16 @@ END
560
638
  end
561
639
 
562
640
  unless ::Sass::Util.ruby1_8?
563
- opts.on('-E encoding', 'Specify the default encoding for Sass and CSS files.') do |encoding|
641
+ opts.on('-E encoding',
642
+ 'Specify the default encoding for Sass and CSS files.') do |encoding|
564
643
  Encoding.default_external = encoding
565
644
  end
566
645
  end
567
646
 
568
647
  super
569
648
  end
649
+ # @comment
650
+ # rubocop:enable MethodLength
570
651
 
571
652
  # Processes the options set by the command-line arguments,
572
653
  # and runs the CSS compiler appropriately.
@@ -580,7 +661,9 @@ END
580
661
 
581
662
  super
582
663
  input = @options[:input]
583
- raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)" if File.directory?(input)
664
+ if File.directory?(input)
665
+ raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)"
666
+ end
584
667
  output = @options[:output]
585
668
  output = input if @options[:in_place]
586
669
  process_file(input, output)
@@ -589,20 +672,22 @@ END
589
672
  private
590
673
 
591
674
  def process_directory
592
- unless input = @options[:input] = @args.shift
675
+ unless (input = @options[:input] = @args.shift)
593
676
  raise "Error: directory required when using --recursive."
594
677
  end
595
678
 
596
679
  output = @options[:output] = @args.shift
597
680
  raise "Error: --from required when using --recursive." unless @options[:from]
598
681
  raise "Error: --to required when using --recursive." unless @options[:to]
599
- raise "Error: '#{@options[:input]}' is not a directory" unless File.directory?(@options[:input])
600
- if @options[:output] && File.exists?(@options[:output]) && !File.directory?(@options[:output])
682
+ unless File.directory?(@options[:input])
683
+ raise "Error: '#{@options[:input]}' is not a directory"
684
+ end
685
+ if @options[:output] && File.exists?(@options[:output]) &&
686
+ !File.directory?(@options[:output])
601
687
  raise "Error: '#{@options[:output]}' is not a directory"
602
688
  end
603
689
  @options[:output] ||= @options[:input]
604
690
 
605
- from = @options[:from]
606
691
  if @options[:to] == @options[:from] && !@options[:in_place]
607
692
  fmt = @options[:from]
608
693
  raise "Error: converting from #{fmt} to #{fmt} without --in-place"
@@ -633,7 +718,6 @@ END
633
718
  end
634
719
 
635
720
  input = open_file(f)
636
- output = @options[:in_place] ? input : open_file(output, "w")
637
721
  process_file(input, output)
638
722
  end
639
723
  end
@@ -677,8 +761,8 @@ END
677
761
  end
678
762
  end
679
763
 
680
- output = File.open(input.path, 'w') if @options[:in_place]
681
- output.write(out)
764
+ output = input.path if @options[:in_place]
765
+ write_output(out, output)
682
766
  rescue ::Sass::SyntaxError => e
683
767
  raise e if @options[:trace]
684
768
  file = " of #{e.sass_filename}" if e.sass_filename