sass 3.3.14 → 3.4.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +0 -5
  10. data/lib/sass/css.rb +1 -3
  11. data/lib/sass/engine.rb +28 -39
  12. data/lib/sass/environment.rb +13 -17
  13. data/lib/sass/error.rb +6 -9
  14. data/lib/sass/exec.rb +5 -771
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +419 -0
  18. data/lib/sass/features.rb +6 -0
  19. data/lib/sass/importers.rb +0 -1
  20. data/lib/sass/importers/base.rb +5 -1
  21. data/lib/sass/importers/filesystem.rb +4 -21
  22. data/lib/sass/media.rb +1 -4
  23. data/lib/sass/plugin/compiler.rb +32 -136
  24. data/lib/sass/script/css_lexer.rb +1 -1
  25. data/lib/sass/script/functions.rb +363 -39
  26. data/lib/sass/script/lexer.rb +68 -50
  27. data/lib/sass/script/parser.rb +29 -14
  28. data/lib/sass/script/tree.rb +1 -0
  29. data/lib/sass/script/tree/funcall.rb +1 -1
  30. data/lib/sass/script/tree/interpolation.rb +19 -1
  31. data/lib/sass/script/tree/selector.rb +26 -0
  32. data/lib/sass/script/value.rb +0 -1
  33. data/lib/sass/script/value/bool.rb +0 -5
  34. data/lib/sass/script/value/color.rb +32 -12
  35. data/lib/sass/script/value/helpers.rb +107 -0
  36. data/lib/sass/script/value/list.rb +0 -15
  37. data/lib/sass/script/value/null.rb +0 -5
  38. data/lib/sass/script/value/number.rb +60 -14
  39. data/lib/sass/script/value/string.rb +53 -9
  40. data/lib/sass/scss/css_parser.rb +8 -2
  41. data/lib/sass/scss/parser.rb +175 -319
  42. data/lib/sass/scss/rx.rb +14 -5
  43. data/lib/sass/scss/static_parser.rb +298 -1
  44. data/lib/sass/selector.rb +56 -193
  45. data/lib/sass/selector/abstract_sequence.rb +28 -13
  46. data/lib/sass/selector/comma_sequence.rb +91 -12
  47. data/lib/sass/selector/pseudo.rb +256 -0
  48. data/lib/sass/selector/sequence.rb +99 -31
  49. data/lib/sass/selector/simple.rb +14 -25
  50. data/lib/sass/selector/simple_sequence.rb +101 -37
  51. data/lib/sass/shared.rb +1 -1
  52. data/lib/sass/source/map.rb +23 -9
  53. data/lib/sass/stack.rb +0 -6
  54. data/lib/sass/supports.rb +1 -1
  55. data/lib/sass/tree/at_root_node.rb +1 -0
  56. data/lib/sass/tree/directive_node.rb +7 -1
  57. data/lib/sass/tree/error_node.rb +18 -0
  58. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  59. data/lib/sass/tree/prop_node.rb +1 -1
  60. data/lib/sass/tree/rule_node.rb +11 -6
  61. data/lib/sass/tree/visitors/check_nesting.rb +3 -4
  62. data/lib/sass/tree/visitors/convert.rb +8 -17
  63. data/lib/sass/tree/visitors/cssize.rb +12 -24
  64. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  65. data/lib/sass/tree/visitors/perform.rb +43 -28
  66. data/lib/sass/tree/visitors/set_options.rb +5 -0
  67. data/lib/sass/tree/visitors/to_css.rb +14 -13
  68. data/lib/sass/util.rb +94 -90
  69. data/test/sass/cache_test.rb +1 -1
  70. data/test/sass/callbacks_test.rb +1 -1
  71. data/test/sass/compiler_test.rb +5 -14
  72. data/test/sass/conversion_test.rb +47 -1
  73. data/test/sass/css2sass_test.rb +3 -3
  74. data/test/sass/encoding_test.rb +219 -0
  75. data/test/sass/engine_test.rb +128 -191
  76. data/test/sass/exec_test.rb +2 -2
  77. data/test/sass/extend_test.rb +234 -17
  78. data/test/sass/functions_test.rb +268 -213
  79. data/test/sass/importer_test.rb +31 -21
  80. data/test/sass/logger_test.rb +1 -1
  81. data/test/sass/more_results/more_import.css +1 -1
  82. data/test/sass/plugin_test.rb +12 -11
  83. data/test/sass/results/compact.css +1 -1
  84. data/test/sass/results/complex.css +4 -4
  85. data/test/sass/results/expanded.css +1 -1
  86. data/test/sass/results/import.css +1 -1
  87. data/test/sass/results/import_charset_ibm866.css +2 -2
  88. data/test/sass/results/mixins.css +17 -17
  89. data/test/sass/results/nested.css +1 -1
  90. data/test/sass/results/parent_ref.css +2 -2
  91. data/test/sass/results/script.css +3 -3
  92. data/test/sass/results/scss_import.css +1 -1
  93. data/test/sass/script_conversion_test.rb +7 -4
  94. data/test/sass/script_test.rb +202 -79
  95. data/test/sass/scss/css_test.rb +95 -25
  96. data/test/sass/scss/rx_test.rb +4 -4
  97. data/test/sass/scss/scss_test.rb +363 -19
  98. data/test/sass/source_map_test.rb +48 -41
  99. data/test/sass/superselector_test.rb +191 -0
  100. data/test/sass/templates/scss_import.scss +2 -1
  101. data/test/sass/test_helper.rb +1 -1
  102. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  103. data/test/sass/util/normalized_map_test.rb +1 -1
  104. data/test/sass/util/subset_map_test.rb +2 -2
  105. data/test/sass/util_test.rb +1 -1
  106. data/test/sass/value_helpers_test.rb +3 -3
  107. data/test/test_helper.rb +2 -2
  108. metadata +30 -7
  109. data/lib/sass/importers/deprecated_path.rb +0 -51
  110. data/lib/sass/script/value/deprecated_false.rb +0 -55
@@ -0,0 +1,187 @@
1
+ require 'optparse'
2
+
3
+ module Sass::Exec
4
+ # The abstract base class for Sass executables.
5
+ class Base
6
+ # @param args [Array<String>] The command-line arguments
7
+ def initialize(args)
8
+ @args = args
9
+ @options = {}
10
+ end
11
+
12
+ # Parses the command-line arguments and runs the executable.
13
+ # Calls `Kernel#exit` at the end, so it never returns.
14
+ #
15
+ # @see #parse
16
+ def parse!
17
+ # rubocop:disable RescueException
18
+ begin
19
+ parse
20
+ rescue Exception => e
21
+ raise e if @options[:trace] || e.is_a?(SystemExit)
22
+
23
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
24
+ $stderr.puts "#{e.message}"
25
+ $stderr.puts " Use --trace for backtrace."
26
+ exit 1
27
+ end
28
+ exit 0
29
+ # rubocop:enable RescueException
30
+ end
31
+
32
+ # Parses the command-line arguments and runs the executable.
33
+ # This does not handle exceptions or exit the program.
34
+ #
35
+ # @see #parse!
36
+ def parse
37
+ @opts = OptionParser.new(&method(:set_opts))
38
+ @opts.parse!(@args)
39
+
40
+ process_result
41
+
42
+ @options
43
+ end
44
+
45
+ # @return [String] A description of the executable
46
+ def to_s
47
+ @opts.to_s
48
+ end
49
+
50
+ protected
51
+
52
+ # Finds the line of the source template
53
+ # on which an exception was raised.
54
+ #
55
+ # @param exception [Exception] The exception
56
+ # @return [String] The line number
57
+ def get_line(exception)
58
+ # SyntaxErrors have weird line reporting
59
+ # when there's trailing whitespace
60
+ if exception.is_a?(::SyntaxError)
61
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first
62
+ end
63
+ (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
64
+ end
65
+
66
+ # Tells optparse how to parse the arguments
67
+ # available for all executables.
68
+ #
69
+ # This is meant to be overridden by subclasses
70
+ # so they can add their own options.
71
+ #
72
+ # @param opts [OptionParser]
73
+ def set_opts(opts)
74
+ Sass::Util.abstract(this)
75
+ end
76
+
77
+ # Set an option for specifying `Encoding.default_external`.
78
+ #
79
+ # @param opts [OptionParser]
80
+ def encoding_option(opts)
81
+ encoding_desc = if Sass::Util.ruby1_8?
82
+ 'Does not work in Ruby 1.8.'
83
+ else
84
+ 'Specify the default encoding for input files.'
85
+ end
86
+ opts.on('-E', '--default-encoding ENCODING', encoding_desc) do |encoding|
87
+ if Sass::Util.ruby1_8?
88
+ $stderr.puts "Specifying the encoding is not supported in ruby 1.8."
89
+ exit 1
90
+ else
91
+ Encoding.default_external = encoding
92
+ end
93
+ end
94
+ end
95
+
96
+ # Processes the options set by the command-line arguments. In particular,
97
+ # sets `@options[:input]` and `@options[:output]` to appropriate IO streams.
98
+ #
99
+ # This is meant to be overridden by subclasses
100
+ # so they can run their respective programs.
101
+ def process_result
102
+ input, output = @options[:input], @options[:output]
103
+ args = @args.dup
104
+ input ||=
105
+ begin
106
+ filename = args.shift
107
+ @options[:filename] = filename
108
+ open_file(filename) || $stdin
109
+ end
110
+ @options[:output_filename] = args.shift
111
+ output ||= @options[:output_filename] || $stdout
112
+ @options[:input], @options[:output] = input, output
113
+ end
114
+
115
+ COLORS = {:red => 31, :green => 32, :yellow => 33}
116
+
117
+ # Prints a status message about performing the given action,
118
+ # colored using the given color (via terminal escapes) if possible.
119
+ #
120
+ # @param name [#to_s] A short name for the action being performed.
121
+ # Shouldn't be longer than 11 characters.
122
+ # @param color [Symbol] The name of the color to use for this action.
123
+ # Can be `:red`, `:green`, or `:yellow`.
124
+ def puts_action(name, color, arg)
125
+ return if @options[:for_engine][:quiet]
126
+ printf color(color, "%11s %s\n"), name, arg
127
+ STDOUT.flush
128
+ end
129
+
130
+ # Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
131
+ #
132
+ # @param args [Array] Passed on to `Kernel.puts`
133
+ def puts(*args)
134
+ return if @options[:for_engine][:quiet]
135
+ Kernel.puts(*args)
136
+ end
137
+
138
+ # Wraps the given string in terminal escapes
139
+ # causing it to have the given color.
140
+ # If terminal esapes aren't supported on this platform,
141
+ # just returns the string instead.
142
+ #
143
+ # @param color [Symbol] The name of the color to use.
144
+ # Can be `:red`, `:green`, or `:yellow`.
145
+ # @param str [String] The string to wrap in the given color.
146
+ # @return [String] The wrapped string.
147
+ def color(color, str)
148
+ raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
149
+
150
+ # Almost any real Unix terminal will support color,
151
+ # so we just filter for Windows terms (which don't set TERM)
152
+ # and not-real terminals, which aren't ttys.
153
+ return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
154
+ "\e[#{COLORS[color]}m#{str}\e[0m"
155
+ end
156
+
157
+ def write_output(text, destination)
158
+ if destination.is_a?(String)
159
+ open_file(destination, 'w') {|file| file.write(text)}
160
+ else
161
+ destination.write(text)
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def open_file(filename, flag = 'r')
168
+ return if filename.nil?
169
+ flag = 'wb' if @options[:unix_newlines] && flag == 'w'
170
+ file = File.open(filename, flag)
171
+ return file unless block_given?
172
+ yield file
173
+ file.close
174
+ end
175
+
176
+ def handle_load_error(err)
177
+ dep = err.message[/^no such file to load -- (.*)/, 1]
178
+ raise err if @options[:trace] || dep.nil? || dep.empty?
179
+ $stderr.puts <<MESSAGE
180
+ Required dependency #{dep} not found!
181
+ Run "gem install #{dep}" to get it.
182
+ Use --trace for backtrace.
183
+ MESSAGE
184
+ exit 1
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,264 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+
4
+ module Sass::Exec
5
+ # The `sass-convert` executable.
6
+ class SassConvert < Base
7
+ # @param args [Array<String>] The command-line arguments
8
+ def initialize(args)
9
+ super
10
+ require 'sass'
11
+ @options[:for_tree] = {}
12
+ @options[:for_engine] = {:cache => false, :read_cache => true}
13
+ end
14
+
15
+ # Tells optparse how to parse the arguments.
16
+ #
17
+ # @param opts [OptionParser]
18
+ def set_opts(opts)
19
+ opts.banner = <<END
20
+ Usage: sass-convert [options] [INPUT] [OUTPUT]
21
+
22
+ Description:
23
+ Converts between CSS, indented syntax, and SCSS files. For example,
24
+ this can convert from the indented syntax to SCSS, or from CSS to
25
+ SCSS (adding appropriate nesting).
26
+ END
27
+
28
+ common_options(opts)
29
+ style(opts)
30
+ input_and_output(opts)
31
+ miscellaneous(opts)
32
+ end
33
+
34
+ # Processes the options set by the command-line arguments,
35
+ # and runs the CSS compiler appropriately.
36
+ def process_result
37
+ require 'sass'
38
+
39
+ if @options[:recursive]
40
+ process_directory
41
+ return
42
+ end
43
+
44
+ super
45
+ input = @options[:input]
46
+ if File.directory?(input)
47
+ raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)"
48
+ end
49
+ output = @options[:output]
50
+ output = input if @options[:in_place]
51
+ process_file(input, output)
52
+ end
53
+
54
+ private
55
+
56
+ def common_options(opts)
57
+ opts.separator ''
58
+ opts.separator 'Common Options:'
59
+
60
+ opts.on('-F', '--from FORMAT',
61
+ 'The format to convert from. Can be css, scss, sass.',
62
+ 'By default, this is inferred from the input filename.',
63
+ 'If there is none, defaults to css.') do |name|
64
+ @options[:from] = name.downcase.to_sym
65
+ raise "sass-convert no longer supports LessCSS." if @options[:from] == :less
66
+ unless [:css, :scss, :sass].include?(@options[:from])
67
+ raise "Unknown format for sass-convert --from: #{name}"
68
+ end
69
+ end
70
+
71
+ opts.on('-T', '--to FORMAT',
72
+ 'The format to convert to. Can be scss or sass.',
73
+ 'By default, this is inferred from the output filename.',
74
+ 'If there is none, defaults to sass.') do |name|
75
+ @options[:to] = name.downcase.to_sym
76
+ unless [:scss, :sass].include?(@options[:to])
77
+ raise "Unknown format for sass-convert --to: #{name}"
78
+ end
79
+ end
80
+
81
+ opts.on('-i', '--in-place',
82
+ 'Convert a file to its own syntax.',
83
+ 'This can be used to update some deprecated syntax.') do
84
+ @options[:in_place] = true
85
+ end
86
+
87
+ opts.on('-R', '--recursive',
88
+ 'Convert all the files in a directory. Requires --from and --to.') do
89
+ @options[:recursive] = true
90
+ end
91
+
92
+ opts.on("-?", "-h", "--help", "Show this help message.") do
93
+ puts opts
94
+ exit
95
+ end
96
+
97
+ opts.on("-v", "--version", "Print the Sass version.") do
98
+ puts("Sass #{Sass.version[:string]}")
99
+ exit
100
+ end
101
+ end
102
+
103
+ def style(opts)
104
+ opts.separator ''
105
+ opts.separator 'Style:'
106
+
107
+ opts.on('--dasherize', 'Convert underscores to dashes.') do
108
+ @options[:for_tree][:dasherize] = true
109
+ end
110
+
111
+ opts.on('--indent NUM',
112
+ 'How many spaces to use for each level of indentation. Defaults to 2.',
113
+ '"t" means use hard tabs.') do |indent|
114
+
115
+ if indent == 't'
116
+ @options[:for_tree][:indent] = "\t"
117
+ else
118
+ @options[:for_tree][:indent] = " " * indent.to_i
119
+ end
120
+ end
121
+
122
+ opts.on('--old', 'Output the old-style ":prop val" property syntax.',
123
+ 'Only meaningful when generating Sass.') do
124
+ @options[:for_tree][:old] = true
125
+ end
126
+ end
127
+
128
+ def input_and_output(opts)
129
+ opts.separator ''
130
+ opts.separator 'Input and Output:'
131
+
132
+ opts.on('-s', '--stdin', :NONE,
133
+ 'Read input from standard input instead of an input file.',
134
+ 'This is the default if no input file is specified. Requires --from.') do
135
+ @options[:input] = $stdin
136
+ end
137
+
138
+ encoding_option(opts)
139
+
140
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.',
141
+ ('Always true on Unix.' unless Sass::Util.windows?)) do
142
+ @options[:unix_newlines] = true if Sass::Util.windows?
143
+ end
144
+ end
145
+
146
+ def miscellaneous(opts)
147
+ opts.separator ''
148
+ opts.separator 'Miscellaneous:'
149
+
150
+ opts.on('--cache-location PATH',
151
+ 'The path to save parsed Sass files. Defaults to .sass-cache.') do |loc|
152
+ @options[:for_engine][:cache_location] = loc
153
+ end
154
+
155
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
156
+ @options[:for_engine][:read_cache] = false
157
+ end
158
+
159
+ opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error') do
160
+ @options[:trace] = true
161
+ end
162
+ end
163
+
164
+ def process_directory
165
+ unless (input = @options[:input] = @args.shift)
166
+ raise "Error: directory required when using --recursive."
167
+ end
168
+
169
+ output = @options[:output] = @args.shift
170
+ raise "Error: --from required when using --recursive." unless @options[:from]
171
+ raise "Error: --to required when using --recursive." unless @options[:to]
172
+ unless File.directory?(@options[:input])
173
+ raise "Error: '#{@options[:input]}' is not a directory"
174
+ end
175
+ if @options[:output] && File.exist?(@options[:output]) &&
176
+ !File.directory?(@options[:output])
177
+ raise "Error: '#{@options[:output]}' is not a directory"
178
+ end
179
+ @options[:output] ||= @options[:input]
180
+
181
+ if @options[:to] == @options[:from] && !@options[:in_place]
182
+ fmt = @options[:from]
183
+ raise "Error: converting from #{fmt} to #{fmt} without --in-place"
184
+ end
185
+
186
+ ext = @options[:from]
187
+ Sass::Util.glob("#{@options[:input]}/**/*.#{ext}") do |f|
188
+ output =
189
+ if @options[:in_place]
190
+ f
191
+ elsif @options[:output]
192
+ output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
193
+ output_name[0...@options[:input].size] = @options[:output]
194
+ output_name
195
+ else
196
+ f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
197
+ end
198
+
199
+ unless File.directory?(File.dirname(output))
200
+ puts_action :directory, :green, File.dirname(output)
201
+ FileUtils.mkdir_p(File.dirname(output))
202
+ end
203
+ puts_action :convert, :green, f
204
+ if File.exist?(output)
205
+ puts_action :overwrite, :yellow, output
206
+ else
207
+ puts_action :create, :green, output
208
+ end
209
+
210
+ input = open_file(f)
211
+ process_file(input, output)
212
+ end
213
+ end
214
+
215
+ def process_file(input, output)
216
+ if input.is_a?(File)
217
+ @options[:from] ||=
218
+ case input.path
219
+ when /\.scss$/; :scss
220
+ when /\.sass$/; :sass
221
+ when /\.less$/; raise "sass-convert no longer supports LessCSS."
222
+ when /\.css$/; :css
223
+ end
224
+ elsif @options[:in_place]
225
+ raise "Error: the --in-place option requires a filename."
226
+ end
227
+
228
+ if output.is_a?(File)
229
+ @options[:to] ||=
230
+ case output.path
231
+ when /\.scss$/; :scss
232
+ when /\.sass$/; :sass
233
+ end
234
+ end
235
+
236
+ @options[:from] ||= :css
237
+ @options[:to] ||= :sass
238
+ @options[:for_engine][:syntax] = @options[:from]
239
+
240
+ out =
241
+ Sass::Util.silence_sass_warnings do
242
+ if @options[:from] == :css
243
+ require 'sass/css'
244
+ Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
245
+ else
246
+ if input.is_a?(File)
247
+ Sass::Engine.for_file(input.path, @options[:for_engine])
248
+ else
249
+ Sass::Engine.new(input.read, @options[:for_engine])
250
+ end.to_tree.send("to_#{@options[:to]}", @options[:for_tree])
251
+ end
252
+ end
253
+
254
+ output = input.path if @options[:in_place]
255
+ write_output(out, output)
256
+ rescue Sass::SyntaxError => e
257
+ raise e if @options[:trace]
258
+ file = " of #{e.sass_filename}" if e.sass_filename
259
+ raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
260
+ rescue LoadError => err
261
+ handle_load_error(err)
262
+ end
263
+ end
264
+ end