sassmagic 0.1.0
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.
- data/Changelog.md +0 -0
- data/Readme.md +0 -0
- data/bin/sassmagic +53 -0
- data/lib/sassmagic/reset.rb +1594 -0
- data/lib/sassmagic/utils.rb +428 -0
- data/lib/sassmagic.rb +20 -0
- data/stylesheets/sassmagic/_+.scss +10 -0
- data/stylesheets/sassmagic/functions/_list.scss +27 -0
- data/stylesheets/sassmagic/functions/_support.scss +14 -0
- data/stylesheets/sassmagic/mixins/_clearfix.scss +19 -0
- data/stylesheets/sassmagic/mixins/_ellipsis-overflow.scss +7 -0
- data/stylesheets/sassmagic/mixins/_float.scss +8 -0
- data/stylesheets/sassmagic/mixins/_font-face.scss +20 -0
- data/stylesheets/sassmagic/mixins/_image.scss +19 -0
- data/stylesheets/sassmagic/mixins/_inline-block.scss +8 -0
- data/stylesheets/sassmagic/mixins/_placeholder-wrapper.scss +15 -0
- metadata +85 -0
@@ -0,0 +1,1594 @@
|
|
1
|
+
# sass文件重写,主要修改了属性解析函数,编译文件函数,文件涉及:sass.rb,to_css.rb,base.rb,sass_convert.rb,sass_scss.rb
|
2
|
+
module Sass::Script::Functions::UserFunctions
|
3
|
+
def option(name)
|
4
|
+
Sass::Script::Value::String.new(@options[name.value.to_sym].to_s)
|
5
|
+
end
|
6
|
+
|
7
|
+
def set_a_variable(name, value)
|
8
|
+
environment.set_var(name.value, value)
|
9
|
+
return Sass::Script::Value::Null.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_a_global_variable(name, value)
|
13
|
+
environment.set_global_var(name.value, value)
|
14
|
+
return Sass::Script::Value::Null.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_a_variable(name)
|
18
|
+
environment.var(name.value) || Sass::Script::Value::String.new("undefined")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Sass::Script::Functions
|
23
|
+
include Sass::Script::Functions::UserFunctions
|
24
|
+
def reverse(string)
|
25
|
+
assert_type string, :String
|
26
|
+
Sass::Script::Value::String.new(string.value.reverse)
|
27
|
+
end
|
28
|
+
declare :reverse, [:string]
|
29
|
+
def pxtorem(string)
|
30
|
+
assert_type string, :String
|
31
|
+
Sass::Script::Value::String.new(string+'rem')
|
32
|
+
end
|
33
|
+
declare :pxtorem, [:string]
|
34
|
+
# Dynamically calls a function. This can call user-defined
|
35
|
+
# functions, built-in functions, or plain CSS functions. It will
|
36
|
+
# pass along all arguments, including keyword arguments, to the
|
37
|
+
# called function.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# call(rgb, 10, 100, 255) => #0a64ff
|
41
|
+
# call(scale-color, #0a64ff, $lightness: -10%) => #0058ef
|
42
|
+
#
|
43
|
+
# $fn: nth;
|
44
|
+
# call($fn, (a b c), 2) => b
|
45
|
+
#
|
46
|
+
# @overload call($name, $args...)
|
47
|
+
# @param $name [String] The name of the function to call.
|
48
|
+
def call(name, *args)
|
49
|
+
assert_type name, :String, :name
|
50
|
+
kwargs = args.last.is_a?(Hash) ? args.pop : {}
|
51
|
+
funcall = Sass::Script::Tree::Funcall.new(
|
52
|
+
name.value,
|
53
|
+
args.map {|a| Sass::Script::Tree::Literal.new(a)},
|
54
|
+
Sass::Util.map_vals(kwargs) {|v| Sass::Script::Tree::Literal.new(v)},
|
55
|
+
nil,
|
56
|
+
nil)
|
57
|
+
funcall.options = options
|
58
|
+
perform(funcall)
|
59
|
+
end
|
60
|
+
declare :call, [:name], :var_args => true, :var_kwargs => true
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
# 重写编译
|
66
|
+
module Sass
|
67
|
+
class << self
|
68
|
+
# @private
|
69
|
+
attr_accessor :tests_running
|
70
|
+
end
|
71
|
+
|
72
|
+
# The global load paths for Sass files. This is meant for plugins and
|
73
|
+
# libraries to register the paths to their Sass stylesheets to that they may
|
74
|
+
# be `@imported`. This load path is used by every instance of {Sass::Engine}.
|
75
|
+
# They are lower-precedence than any load paths passed in via the
|
76
|
+
# {file:SASS_REFERENCE.md#load_paths-option `:load_paths` option}.
|
77
|
+
#
|
78
|
+
# If the `SASS_PATH` environment variable is set,
|
79
|
+
# the initial value of `load_paths` will be initialized based on that.
|
80
|
+
# The variable should be a colon-separated list of path names
|
81
|
+
# (semicolon-separated on Windows).
|
82
|
+
#
|
83
|
+
# Note that files on the global load path are never compiled to CSS
|
84
|
+
# themselves, even if they aren't partials. They exist only to be imported.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# Sass.load_paths << File.dirname(__FILE__ + '/sass')
|
88
|
+
# @return [Array<String, Pathname, Sass::Importers::Base>]
|
89
|
+
def self.load_paths
|
90
|
+
@load_paths ||= if ENV['SASS_PATH']
|
91
|
+
ENV['SASS_PATH'].split(Sass::Util.windows? ? ';' : ':')
|
92
|
+
else
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Compile a Sass or SCSS string to CSS.
|
98
|
+
# Defaults to SCSS.
|
99
|
+
#
|
100
|
+
# @param contents [String] The contents of the Sass file.
|
101
|
+
# @param options [{Symbol => Object}] An options hash;
|
102
|
+
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
103
|
+
# @raise [Sass::SyntaxError] if there's an error in the document
|
104
|
+
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
105
|
+
# cannot be converted to UTF-8
|
106
|
+
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
107
|
+
def self.compile(contents, options = {})
|
108
|
+
options[:syntax] ||= :scss
|
109
|
+
Engine.new(contents, options).to_css
|
110
|
+
end
|
111
|
+
|
112
|
+
# Compile a file on disk to CSS.
|
113
|
+
#
|
114
|
+
# @raise [Sass::SyntaxError] if there's an error in the document
|
115
|
+
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
116
|
+
# cannot be converted to UTF-8
|
117
|
+
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
118
|
+
#
|
119
|
+
# @overload compile_file(filename, options = {})
|
120
|
+
# Return the compiled CSS rather than writing it to a file.
|
121
|
+
#
|
122
|
+
# @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
|
123
|
+
# @param options [{Symbol => Object}] An options hash;
|
124
|
+
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
125
|
+
# @return [String] The compiled CSS.
|
126
|
+
#
|
127
|
+
# @overload compile_file(filename, css_filename, options = {})
|
128
|
+
# Write the compiled CSS to a file.
|
129
|
+
#
|
130
|
+
# @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
|
131
|
+
# @param options [{Symbol => Object}] An options hash;
|
132
|
+
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
|
133
|
+
# @param css_filename [String] The location to which to write the compiled CSS.
|
134
|
+
def self.compile_file(filename, *args)
|
135
|
+
# debugger
|
136
|
+
# puts filename
|
137
|
+
# puts args
|
138
|
+
# Sass.logger(filename, args)
|
139
|
+
ctx = Sass::Script::Functions::EvaluationContext.new(Sass::Environment.new(nil, {}))
|
140
|
+
|
141
|
+
# print 'star compile file:'
|
142
|
+
#加载json
|
143
|
+
# 获取设置
|
144
|
+
$configHash = ctx.load_json(File.expand_path("#{File.dirname(filename)}/sassmagic.json")) || {}
|
145
|
+
# puts $configHash
|
146
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
147
|
+
options = options.merge $configHash
|
148
|
+
css_filename = args.shift
|
149
|
+
# debugger
|
150
|
+
#是否需要额外输出样式表
|
151
|
+
if options.has_key?"outputExtra"
|
152
|
+
options["outputExtra"] ||= []
|
153
|
+
options["outputExtra"].each {|v|
|
154
|
+
|
155
|
+
extra_filename = css_filename + ''
|
156
|
+
#替换成1x 2x 3x
|
157
|
+
extra_filename.gsub!(/\.css$/ , '.'+v+'.css')
|
158
|
+
if extra_filename
|
159
|
+
options[:css_filename] = extra_filename
|
160
|
+
options["multiple"] = v
|
161
|
+
result = Sass::Engine.for_file(filename, options).render
|
162
|
+
# open(css_filename, "w") {|css_file| css_file.write(result)}
|
163
|
+
File.open(extra_filename, 'w') {|css_file| css_file.write(result)}
|
164
|
+
nil
|
165
|
+
else
|
166
|
+
result = Sass::Engine.for_file(filename, options).render
|
167
|
+
result
|
168
|
+
end
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
options.delete("multiple")
|
173
|
+
result = Sass::Engine.for_file(filename, options).render
|
174
|
+
if css_filename
|
175
|
+
options[:css_filename] ||= css_filename
|
176
|
+
open(css_filename, "w") {|css_file| css_file.write(result)}
|
177
|
+
nil
|
178
|
+
else
|
179
|
+
result
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
#重写属性解析to_css.rb
|
188
|
+
# A visitor for converting a Sass tree into CSS.
|
189
|
+
class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
|
190
|
+
# The source mapping for the generated CSS file. This is only set if
|
191
|
+
# `build_source_mapping` is passed to the constructor and \{Sass::Engine#render} has been
|
192
|
+
# run.
|
193
|
+
attr_reader :source_mapping
|
194
|
+
|
195
|
+
# @param build_source_mapping [Boolean] Whether to build a
|
196
|
+
# \{Sass::Source::Map} while creating the CSS output. The mapping will
|
197
|
+
# be available from \{#source\_mapping} after the visitor has completed.
|
198
|
+
def initialize(build_source_mapping = false)
|
199
|
+
@tabs = 0
|
200
|
+
@line = 1
|
201
|
+
@offset = 1
|
202
|
+
@result = ""
|
203
|
+
@source_mapping = Sass::Source::Map.new if build_source_mapping
|
204
|
+
end
|
205
|
+
|
206
|
+
# Runs the visitor on `node`.
|
207
|
+
#
|
208
|
+
# @param node [Sass::Tree::Node] The root node of the tree to convert to CSS>
|
209
|
+
# @return [String] The CSS output.
|
210
|
+
def visit(node)
|
211
|
+
super
|
212
|
+
rescue Sass::SyntaxError => e
|
213
|
+
e.modify_backtrace(:filename => node.filename, :line => node.line)
|
214
|
+
raise e
|
215
|
+
end
|
216
|
+
|
217
|
+
protected
|
218
|
+
|
219
|
+
def with_tabs(tabs)
|
220
|
+
old_tabs, @tabs = @tabs, tabs
|
221
|
+
yield
|
222
|
+
ensure
|
223
|
+
@tabs = old_tabs
|
224
|
+
end
|
225
|
+
|
226
|
+
# Associate all output produced in a block with a given node. Used for source
|
227
|
+
# mapping.
|
228
|
+
def for_node(node, attr_prefix = nil)
|
229
|
+
return yield unless @source_mapping
|
230
|
+
start_pos = Sass::Source::Position.new(@line, @offset)
|
231
|
+
yield
|
232
|
+
|
233
|
+
range_attr = attr_prefix ? :"#{attr_prefix}_source_range" : :source_range
|
234
|
+
return if node.invisible? || !node.send(range_attr)
|
235
|
+
source_range = node.send(range_attr)
|
236
|
+
target_end_pos = Sass::Source::Position.new(@line, @offset)
|
237
|
+
target_range = Sass::Source::Range.new(start_pos, target_end_pos, nil)
|
238
|
+
@source_mapping.add(source_range, target_range)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Move the output cursor back `chars` characters.
|
242
|
+
def erase!(chars)
|
243
|
+
return if chars == 0
|
244
|
+
str = @result.slice!(-chars..-1)
|
245
|
+
newlines = str.count("\n")
|
246
|
+
if newlines > 0
|
247
|
+
@line -= newlines
|
248
|
+
@offset = @result[@result.rindex("\n") || 0..-1].size
|
249
|
+
else
|
250
|
+
@offset -= chars
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Avoid allocating lots of new strings for `#output`. This is important
|
255
|
+
# because `#output` is called all the time.
|
256
|
+
NEWLINE = "\n"
|
257
|
+
|
258
|
+
# Add `s` to the output string and update the line and offset information
|
259
|
+
# accordingly.
|
260
|
+
def output(s)
|
261
|
+
if @lstrip
|
262
|
+
s = s.gsub(/\A\s+/, "")
|
263
|
+
@lstrip = false
|
264
|
+
end
|
265
|
+
|
266
|
+
newlines = s.count(NEWLINE)
|
267
|
+
if newlines > 0
|
268
|
+
@line += newlines
|
269
|
+
@offset = s[s.rindex(NEWLINE)..-1].size
|
270
|
+
else
|
271
|
+
@offset += s.size
|
272
|
+
end
|
273
|
+
|
274
|
+
@result << s
|
275
|
+
end
|
276
|
+
|
277
|
+
# Strip all trailing whitespace from the output string.
|
278
|
+
def rstrip!
|
279
|
+
erase! @result.length - 1 - (@result.rindex(/[^\s]/) || -1)
|
280
|
+
end
|
281
|
+
|
282
|
+
# lstrip the first output in the given block.
|
283
|
+
def lstrip
|
284
|
+
old_lstrip = @lstrip
|
285
|
+
@lstrip = true
|
286
|
+
yield
|
287
|
+
ensure
|
288
|
+
@lstrip = @lstrip && old_lstrip
|
289
|
+
end
|
290
|
+
|
291
|
+
# Prepend `prefix` to the output string.
|
292
|
+
def prepend!(prefix)
|
293
|
+
@result.insert 0, prefix
|
294
|
+
return unless @source_mapping
|
295
|
+
|
296
|
+
line_delta = prefix.count("\n")
|
297
|
+
offset_delta = prefix.gsub(/.*\n/, '').size
|
298
|
+
@source_mapping.shift_output_offsets(offset_delta)
|
299
|
+
@source_mapping.shift_output_lines(line_delta)
|
300
|
+
end
|
301
|
+
|
302
|
+
def visit_root(node)
|
303
|
+
#输出样式表
|
304
|
+
# debugger
|
305
|
+
node.children.each do |child|
|
306
|
+
next if child.invisible?
|
307
|
+
visit(child)
|
308
|
+
unless node.style == :compressed
|
309
|
+
output "\n"
|
310
|
+
if child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles?
|
311
|
+
output "\n"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
rstrip!
|
316
|
+
return "" if @result.empty?
|
317
|
+
|
318
|
+
output "\n"
|
319
|
+
|
320
|
+
unless Sass::Util.ruby1_8? || @result.ascii_only?
|
321
|
+
if node.style == :compressed
|
322
|
+
# A byte order mark is sufficient to tell browsers that this
|
323
|
+
# file is UTF-8 encoded, and will override any other detection
|
324
|
+
# methods as per http://encoding.spec.whatwg.org/#decode-and-encode.
|
325
|
+
prepend! "\uFEFF"
|
326
|
+
else
|
327
|
+
prepend! "@charset \"UTF-8\";\n"
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
@result
|
332
|
+
rescue Sass::SyntaxError => e
|
333
|
+
e.sass_template ||= node.template
|
334
|
+
raise e
|
335
|
+
end
|
336
|
+
|
337
|
+
def visit_charset(node)
|
338
|
+
for_node(node) {output("@charset \"#{node.name}\";")}
|
339
|
+
end
|
340
|
+
|
341
|
+
def visit_comment(node)
|
342
|
+
return if node.invisible?
|
343
|
+
spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
|
344
|
+
|
345
|
+
content = node.resolved_value.gsub(/^/, spaces)
|
346
|
+
if node.type == :silent
|
347
|
+
content.gsub!(%r{^(\s*)//(.*)$}) {|md| "#{$1}/*#{$2} */"}
|
348
|
+
end
|
349
|
+
if (node.style == :compact || node.style == :compressed) && node.type != :loud
|
350
|
+
content.gsub!(/\n +(\* *(?!\/))?/, ' ')
|
351
|
+
end
|
352
|
+
for_node(node) {output(content)}
|
353
|
+
end
|
354
|
+
|
355
|
+
# @comment
|
356
|
+
# rubocop:disable MethodLength
|
357
|
+
def visit_directive(node)
|
358
|
+
was_in_directive = @in_directive
|
359
|
+
tab_str = ' ' * @tabs
|
360
|
+
if !node.has_children || node.children.empty?
|
361
|
+
output(tab_str)
|
362
|
+
for_node(node) {output(node.resolved_value)}
|
363
|
+
output(!node.has_children ? ";" : " {}")
|
364
|
+
return
|
365
|
+
end
|
366
|
+
|
367
|
+
@in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
|
368
|
+
output(tab_str) if node.style != :compressed
|
369
|
+
for_node(node) {output(node.resolved_value)}
|
370
|
+
output(node.style == :compressed ? "{" : " {")
|
371
|
+
output(node.style == :compact ? ' ' : "\n") if node.style != :compressed
|
372
|
+
|
373
|
+
was_prop = false
|
374
|
+
first = true
|
375
|
+
node.children.each do |child|
|
376
|
+
next if child.invisible?
|
377
|
+
if node.style == :compact
|
378
|
+
if child.is_a?(Sass::Tree::PropNode)
|
379
|
+
with_tabs(first || was_prop ? 0 : @tabs + 1) do
|
380
|
+
visit(child)
|
381
|
+
output(' ')
|
382
|
+
end
|
383
|
+
else
|
384
|
+
if was_prop
|
385
|
+
erase! 1
|
386
|
+
output "\n"
|
387
|
+
end
|
388
|
+
|
389
|
+
if first
|
390
|
+
lstrip {with_tabs(@tabs + 1) {visit(child)}}
|
391
|
+
else
|
392
|
+
with_tabs(@tabs + 1) {visit(child)}
|
393
|
+
end
|
394
|
+
|
395
|
+
rstrip!
|
396
|
+
output "\n"
|
397
|
+
end
|
398
|
+
was_prop = child.is_a?(Sass::Tree::PropNode)
|
399
|
+
first = false
|
400
|
+
elsif node.style == :compressed
|
401
|
+
output(was_prop ? ";" : "")
|
402
|
+
with_tabs(0) {visit(child)}
|
403
|
+
was_prop = child.is_a?(Sass::Tree::PropNode)
|
404
|
+
else
|
405
|
+
with_tabs(@tabs + 1) {visit(child)}
|
406
|
+
output "\n"
|
407
|
+
end
|
408
|
+
end
|
409
|
+
rstrip!
|
410
|
+
if node.style == :expanded
|
411
|
+
output("\n#{tab_str}")
|
412
|
+
elsif node.style != :compressed
|
413
|
+
output(" ")
|
414
|
+
end
|
415
|
+
output("}")
|
416
|
+
ensure
|
417
|
+
@in_directive = was_in_directive
|
418
|
+
end
|
419
|
+
# @comment
|
420
|
+
# rubocop:enable MethodLength
|
421
|
+
|
422
|
+
def visit_media(node)
|
423
|
+
with_tabs(@tabs + node.tabs) {visit_directive(node)}
|
424
|
+
output("\n") if node.style != :compressed && node.group_end
|
425
|
+
end
|
426
|
+
|
427
|
+
def visit_supports(node)
|
428
|
+
visit_media(node)
|
429
|
+
end
|
430
|
+
|
431
|
+
def visit_cssimport(node)
|
432
|
+
visit_directive(node)
|
433
|
+
end
|
434
|
+
#属性重写开始
|
435
|
+
def visit_prop(node)
|
436
|
+
opt = node.options
|
437
|
+
if $configHash == nil || $configHash == {}
|
438
|
+
#读取配置文件
|
439
|
+
ctx = Sass::Script::Functions::EvaluationContext.new(Sass::Environment.new(nil, {}))
|
440
|
+
#加载json
|
441
|
+
# 获取设置
|
442
|
+
filename = opt[:filename] || ''
|
443
|
+
$configHash = ctx.load_json(File.expand_path("#{File.dirname(filename)}/sassmagic.json")) || {}
|
444
|
+
opt = opt.merge $configHash
|
445
|
+
else
|
446
|
+
opt = opt.merge $configHash
|
447
|
+
end
|
448
|
+
|
449
|
+
multiple = opt["multiple"] || false
|
450
|
+
devicePixelRatio = opt["devicePixelRatio"] || 1
|
451
|
+
# debugger
|
452
|
+
#干预输出
|
453
|
+
if multiple
|
454
|
+
# 过滤掉不需要处理的name
|
455
|
+
isNeedChangeMultiple = true;
|
456
|
+
opt["ignore"].each{|v|
|
457
|
+
# debugger
|
458
|
+
if $classignore
|
459
|
+
isNeedChangeMultiple = false
|
460
|
+
end
|
461
|
+
}
|
462
|
+
if isNeedChangeMultiple
|
463
|
+
treated_prop = pxttodpr(node.resolved_value,devicePixelRatio,multiple)
|
464
|
+
else
|
465
|
+
treated_prop = node.resolved_value
|
466
|
+
end
|
467
|
+
|
468
|
+
else
|
469
|
+
if opt['isNeedPxToRem']
|
470
|
+
if opt["browserDefaultFontSize"].to_i != 0
|
471
|
+
configRem = opt["browserDefaultFontSize"].to_i
|
472
|
+
else
|
473
|
+
configRem = 75
|
474
|
+
end
|
475
|
+
treated_prop = pxtorem(node,node.resolved_value,configRem,opt["ignore"])
|
476
|
+
else
|
477
|
+
treated_prop = node.resolved_value
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
|
483
|
+
#输出属性resolved_name和值resolved_value
|
484
|
+
return if node.resolved_value.empty?
|
485
|
+
tab_str = ' ' * (@tabs + node.tabs)
|
486
|
+
output(tab_str)
|
487
|
+
for_node(node, :name) {output(node.resolved_name)}
|
488
|
+
if node.style == :compressed
|
489
|
+
output(":")
|
490
|
+
for_node(node, :value) {output(treated_prop)}
|
491
|
+
else
|
492
|
+
output(": ")
|
493
|
+
for_node(node, :value) {output(treated_prop)}
|
494
|
+
output(";")
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def pxtorem(node,str,rem,ignore)
|
499
|
+
ret = str
|
500
|
+
ignore = ignore || []
|
501
|
+
# 过滤掉不需要处理的name
|
502
|
+
isNeedChangeValue = true;
|
503
|
+
ignore.each{|v|
|
504
|
+
# debugger
|
505
|
+
if $classignore && (node.resolved_name.include? v)
|
506
|
+
isNeedChangeValue = false
|
507
|
+
end
|
508
|
+
}
|
509
|
+
if isNeedChangeValue
|
510
|
+
#字符串替换px
|
511
|
+
#'0 1px.jpg 1px 2px;3pxabc 4px'.scan(/([\.\d]+)+(px)+([;\s]+|$)/)
|
512
|
+
#=> [["1", "px", " "], ["2", "px", ";"], ["4", "px", ""]]
|
513
|
+
str.scan(/([\.\d]+)+(px)+([;\s]+|$)/i){ |c,v|
|
514
|
+
if !(ignore.include? c+v) && (c != '1')
|
515
|
+
ret = ret.gsub(c.to_s,(format("%.3f",c.to_f/rem).to_f).to_s).gsub(v,'rem')
|
516
|
+
end
|
517
|
+
}
|
518
|
+
end
|
519
|
+
return ret
|
520
|
+
end
|
521
|
+
|
522
|
+
def pxttodpr(str,dpr,multiple)
|
523
|
+
ret = str
|
524
|
+
#字符串替换px
|
525
|
+
#'0 1px.jpg 1px 2px;3pxabc 4px'.scan(/([\.\d]+)+(px)+([;\s]+|$)/)
|
526
|
+
#=> [["1", "px", " "], ["2", "px", ";"], ["4", "px", ""]]
|
527
|
+
str.scan(/([\.\d]+)+(px)+([;\s]+|$)/i){ |c,v|
|
528
|
+
if c != '1'
|
529
|
+
ret = ret.gsub(c.to_s,((c.to_i/dpr.to_i) * multiple.to_i).to_s)
|
530
|
+
end
|
531
|
+
}
|
532
|
+
return ret
|
533
|
+
end
|
534
|
+
|
535
|
+
#属性重写结束
|
536
|
+
# @comment
|
537
|
+
# rubocop:disable MethodLength
|
538
|
+
def visit_rule(node)
|
539
|
+
# debugger
|
540
|
+
opt = node.options
|
541
|
+
if $configHash == nil || $configHash == {}
|
542
|
+
#读取配置文件
|
543
|
+
ctx = Sass::Script::Functions::EvaluationContext.new(Sass::Environment.new(nil, {}))
|
544
|
+
#加载json
|
545
|
+
# 获取设置
|
546
|
+
filename = opt[:filename] || ''
|
547
|
+
$configHash = ctx.load_json(File.expand_path("#{File.dirname(filename)}/sassmagic.json")) || {}
|
548
|
+
opt = opt.merge $configHash
|
549
|
+
else
|
550
|
+
opt = opt.merge $configHash
|
551
|
+
end
|
552
|
+
# 判断是否不需要rem进行自动处理,通过class
|
553
|
+
$classignore = false
|
554
|
+
opt["ignore"].each{|v|
|
555
|
+
# debugger
|
556
|
+
if (node.resolved_rules.to_s).include? v
|
557
|
+
$classignore = true
|
558
|
+
end
|
559
|
+
}
|
560
|
+
|
561
|
+
with_tabs(@tabs + node.tabs) do
|
562
|
+
rule_separator = node.style == :compressed ? ',' : ', '
|
563
|
+
line_separator =
|
564
|
+
case node.style
|
565
|
+
when :nested, :expanded; "\n"
|
566
|
+
when :compressed; ""
|
567
|
+
else; " "
|
568
|
+
end
|
569
|
+
rule_indent = ' ' * @tabs
|
570
|
+
per_rule_indent, total_indent = if [:nested, :expanded].include?(node.style)
|
571
|
+
[rule_indent, '']
|
572
|
+
else
|
573
|
+
['', rule_indent]
|
574
|
+
end
|
575
|
+
|
576
|
+
joined_rules = node.resolved_rules.members.map do |seq|
|
577
|
+
next if seq.has_placeholder?
|
578
|
+
rule_part = seq.to_s
|
579
|
+
if node.style == :compressed
|
580
|
+
rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
|
581
|
+
rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
|
582
|
+
rule_part.strip!
|
583
|
+
end
|
584
|
+
rule_part
|
585
|
+
end.compact.join(rule_separator)
|
586
|
+
|
587
|
+
joined_rules.lstrip!
|
588
|
+
joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}")
|
589
|
+
|
590
|
+
old_spaces = ' ' * @tabs
|
591
|
+
if node.style != :compressed
|
592
|
+
if node.options[:debug_info] && !@in_directive
|
593
|
+
visit(debug_info_rule(node.debug_info, node.options))
|
594
|
+
output "\n"
|
595
|
+
elsif node.options[:trace_selectors]
|
596
|
+
output("#{old_spaces}/* ")
|
597
|
+
output(node.stack_trace.gsub("\n", "\n #{old_spaces}"))
|
598
|
+
output(" */\n")
|
599
|
+
elsif node.options[:line_comments]
|
600
|
+
output("#{old_spaces}/* line #{node.line}")
|
601
|
+
|
602
|
+
if node.filename
|
603
|
+
relative_filename =
|
604
|
+
if node.options[:css_filename]
|
605
|
+
begin
|
606
|
+
Sass::Util.relative_path_from(
|
607
|
+
node.filename, File.dirname(node.options[:css_filename])).to_s
|
608
|
+
rescue ArgumentError
|
609
|
+
nil
|
610
|
+
end
|
611
|
+
end
|
612
|
+
relative_filename ||= node.filename
|
613
|
+
output(", #{relative_filename}")
|
614
|
+
end
|
615
|
+
|
616
|
+
output(" */\n")
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
end_props, trailer, tabs = '', '', 0
|
621
|
+
if node.style == :compact
|
622
|
+
separator, end_props, bracket = ' ', ' ', ' { '
|
623
|
+
trailer = "\n" if node.group_end
|
624
|
+
elsif node.style == :compressed
|
625
|
+
separator, bracket = ';', '{'
|
626
|
+
else
|
627
|
+
tabs = @tabs + 1
|
628
|
+
separator, bracket = "\n", " {\n"
|
629
|
+
trailer = "\n" if node.group_end
|
630
|
+
end_props = (node.style == :expanded ? "\n" + old_spaces : ' ')
|
631
|
+
end
|
632
|
+
output(total_indent + per_rule_indent)
|
633
|
+
for_node(node, :selector) {output(joined_rules)}
|
634
|
+
output(bracket)
|
635
|
+
|
636
|
+
with_tabs(tabs) do
|
637
|
+
node.children.each_with_index do |child, i|
|
638
|
+
output(separator) if i > 0
|
639
|
+
visit(child)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
output(end_props)
|
644
|
+
output("}" + trailer)
|
645
|
+
end
|
646
|
+
end
|
647
|
+
# @comment
|
648
|
+
# rubocop:enable MethodLength
|
649
|
+
|
650
|
+
def visit_keyframerule(node)
|
651
|
+
visit_directive(node)
|
652
|
+
end
|
653
|
+
|
654
|
+
private
|
655
|
+
|
656
|
+
def debug_info_rule(debug_info, options)
|
657
|
+
node = Sass::Tree::DirectiveNode.resolved("@media -sass-debug-info")
|
658
|
+
Sass::Util.hash_to_a(debug_info.map {|k, v| [k.to_s, v.to_s]}).each do |k, v|
|
659
|
+
rule = Sass::Tree::RuleNode.new([""])
|
660
|
+
rule.resolved_rules = Sass::Selector::CommaSequence.new(
|
661
|
+
[Sass::Selector::Sequence.new(
|
662
|
+
[Sass::Selector::SimpleSequence.new(
|
663
|
+
[Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)],
|
664
|
+
false)
|
665
|
+
])
|
666
|
+
])
|
667
|
+
prop = Sass::Tree::PropNode.new([""], Sass::Script::Value::String.new(''), :new)
|
668
|
+
prop.resolved_name = "font-family"
|
669
|
+
prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s)
|
670
|
+
rule << prop
|
671
|
+
node << rule
|
672
|
+
end
|
673
|
+
node.options = options.merge(:debug_info => false,
|
674
|
+
:line_comments => false,
|
675
|
+
:style => :compressed)
|
676
|
+
node
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
require 'optparse'
|
681
|
+
|
682
|
+
# 重写base.rb 去掉require
|
683
|
+
module Sass::Exec
|
684
|
+
# The abstract base class for Sass executables.
|
685
|
+
class Base
|
686
|
+
# @param args [Array<String>] The command-line arguments
|
687
|
+
def initialize(args)
|
688
|
+
@args = args
|
689
|
+
@options = {}
|
690
|
+
end
|
691
|
+
|
692
|
+
# Parses the command-line arguments and runs the executable.
|
693
|
+
# Calls `Kernel#exit` at the end, so it never returns.
|
694
|
+
#
|
695
|
+
# @see #parse
|
696
|
+
def parse!
|
697
|
+
# rubocop:disable RescueException
|
698
|
+
begin
|
699
|
+
parse
|
700
|
+
rescue Exception => e
|
701
|
+
# Exit code 65 indicates invalid data per
|
702
|
+
# http://www.freebsd.org/cgi/man.cgi?query=sysexits. Setting it via
|
703
|
+
# at_exit is a bit of a hack, but it allows us to rethrow when --trace
|
704
|
+
# is active and get both the built-in exception formatting and the
|
705
|
+
# correct exit code.
|
706
|
+
at_exit {exit 65} if e.is_a?(Sass::SyntaxError)
|
707
|
+
|
708
|
+
raise e if @options[:trace] || e.is_a?(SystemExit)
|
709
|
+
|
710
|
+
if e.is_a?(Sass::SyntaxError)
|
711
|
+
$stderr.puts e.sass_backtrace_str("standard input")
|
712
|
+
else
|
713
|
+
$stderr.print "#{e.class}: " unless e.class == RuntimeError
|
714
|
+
$stderr.puts e.message.to_s
|
715
|
+
end
|
716
|
+
$stderr.puts " Use --trace for backtrace."
|
717
|
+
|
718
|
+
exit 1
|
719
|
+
end
|
720
|
+
exit 0
|
721
|
+
# rubocop:enable RescueException
|
722
|
+
end
|
723
|
+
|
724
|
+
# Parses the command-line arguments and runs the executable.
|
725
|
+
# This does not handle exceptions or exit the program.
|
726
|
+
#
|
727
|
+
# @see #parse!
|
728
|
+
def parse
|
729
|
+
@opts = OptionParser.new(&method(:set_opts))
|
730
|
+
|
731
|
+
@opts.parse!(@args)
|
732
|
+
|
733
|
+
process_result
|
734
|
+
|
735
|
+
@options
|
736
|
+
end
|
737
|
+
|
738
|
+
# @return [String] A description of the executable
|
739
|
+
def to_s
|
740
|
+
@opts.to_s
|
741
|
+
end
|
742
|
+
|
743
|
+
protected
|
744
|
+
|
745
|
+
# Finds the line of the source template
|
746
|
+
# on which an exception was raised.
|
747
|
+
#
|
748
|
+
# @param exception [Exception] The exception
|
749
|
+
# @return [String] The line number
|
750
|
+
def get_line(exception)
|
751
|
+
# SyntaxErrors have weird line reporting
|
752
|
+
# when there's trailing whitespace
|
753
|
+
if exception.is_a?(::SyntaxError)
|
754
|
+
return (exception.message.scan(/:(\d+)/).first || ["??"]).first
|
755
|
+
end
|
756
|
+
(exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
|
757
|
+
end
|
758
|
+
|
759
|
+
# Tells optparse how to parse the arguments
|
760
|
+
# available for all executables.
|
761
|
+
#
|
762
|
+
# This is meant to be overridden by subclasses
|
763
|
+
# so they can add their own options.
|
764
|
+
#
|
765
|
+
# @param opts [OptionParser]
|
766
|
+
def set_opts(opts)
|
767
|
+
Sass::Util.abstract(this)
|
768
|
+
end
|
769
|
+
|
770
|
+
# Set an option for specifying `Encoding.default_external`.
|
771
|
+
#
|
772
|
+
# @param opts [OptionParser]
|
773
|
+
def encoding_option(opts)
|
774
|
+
encoding_desc = if Sass::Util.ruby1_8?
|
775
|
+
'Does not work in Ruby 1.8.'
|
776
|
+
else
|
777
|
+
'Specify the default encoding for input files.'
|
778
|
+
end
|
779
|
+
opts.on('-E', '--default-encoding ENCODING', encoding_desc) do |encoding|
|
780
|
+
if Sass::Util.ruby1_8?
|
781
|
+
$stderr.puts "Specifying the encoding is not supported in ruby 1.8."
|
782
|
+
exit 1
|
783
|
+
else
|
784
|
+
Encoding.default_external = encoding
|
785
|
+
end
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
789
|
+
# Processes the options set by the command-line arguments. In particular,
|
790
|
+
# sets `@options[:input]` and `@options[:output]` to appropriate IO streams.
|
791
|
+
#
|
792
|
+
# This is meant to be overridden by subclasses
|
793
|
+
# so they can run their respective programs.
|
794
|
+
def process_result
|
795
|
+
input, output = @options[:input], @options[:output]
|
796
|
+
args = @args.dup
|
797
|
+
input ||=
|
798
|
+
begin
|
799
|
+
filename = args.shift
|
800
|
+
@options[:filename] = filename
|
801
|
+
open_file(filename) || $stdin
|
802
|
+
end
|
803
|
+
@options[:output_filename] = args.shift
|
804
|
+
output ||= @options[:output_filename] || $stdout
|
805
|
+
@options[:input], @options[:output] = input, output
|
806
|
+
end
|
807
|
+
|
808
|
+
COLORS = {:red => 31, :green => 32, :yellow => 33}
|
809
|
+
|
810
|
+
# Prints a status message about performing the given action,
|
811
|
+
# colored using the given color (via terminal escapes) if possible.
|
812
|
+
#
|
813
|
+
# @param name [#to_s] A short name for the action being performed.
|
814
|
+
# Shouldn't be longer than 11 characters.
|
815
|
+
# @param color [Symbol] The name of the color to use for this action.
|
816
|
+
# Can be `:red`, `:green`, or `:yellow`.
|
817
|
+
def puts_action(name, color, arg)
|
818
|
+
return if @options[:for_engine][:quiet]
|
819
|
+
printf color(color, "%11s %s\n"), name, arg
|
820
|
+
STDOUT.flush
|
821
|
+
end
|
822
|
+
|
823
|
+
# Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
|
824
|
+
#
|
825
|
+
# @param args [Array] Passed on to `Kernel.puts`
|
826
|
+
def puts(*args)
|
827
|
+
return if @options[:for_engine][:quiet]
|
828
|
+
Kernel.puts(*args)
|
829
|
+
end
|
830
|
+
|
831
|
+
# Wraps the given string in terminal escapes
|
832
|
+
# causing it to have the given color.
|
833
|
+
# If terminal esapes aren't supported on this platform,
|
834
|
+
# just returns the string instead.
|
835
|
+
#
|
836
|
+
# @param color [Symbol] The name of the color to use.
|
837
|
+
# Can be `:red`, `:green`, or `:yellow`.
|
838
|
+
# @param str [String] The string to wrap in the given color.
|
839
|
+
# @return [String] The wrapped string.
|
840
|
+
def color(color, str)
|
841
|
+
raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
|
842
|
+
|
843
|
+
# Almost any real Unix terminal will support color,
|
844
|
+
# so we just filter for Windows terms (which don't set TERM)
|
845
|
+
# and not-real terminals, which aren't ttys.
|
846
|
+
return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
|
847
|
+
"\e[#{COLORS[color]}m#{str}\e[0m"
|
848
|
+
end
|
849
|
+
|
850
|
+
def write_output(text, destination)
|
851
|
+
if destination.is_a?(String)
|
852
|
+
open_file(destination, 'w') {|file| file.write(text)}
|
853
|
+
else
|
854
|
+
destination.write(text)
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
private
|
859
|
+
|
860
|
+
def open_file(filename, flag = 'r')
|
861
|
+
return if filename.nil?
|
862
|
+
flag = 'wb' if @options[:unix_newlines] && flag == 'w'
|
863
|
+
file = File.open(filename, flag)
|
864
|
+
return file unless block_given?
|
865
|
+
yield file
|
866
|
+
file.close
|
867
|
+
end
|
868
|
+
|
869
|
+
def handle_load_error(err)
|
870
|
+
dep = err.message[/^no such file to load -- (.*)/, 1]
|
871
|
+
raise err if @options[:trace] || dep.nil? || dep.empty?
|
872
|
+
$stderr.puts <<MESSAGE
|
873
|
+
Required dependency #{dep} not found!
|
874
|
+
Run "gem install #{dep}" to get it.
|
875
|
+
Use --trace for backtrace.
|
876
|
+
MESSAGE
|
877
|
+
exit 1
|
878
|
+
end
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
# 重写sass_convert.rb 去掉require
|
883
|
+
module Sass::Exec
|
884
|
+
# The `sass-convert` executable.
|
885
|
+
class SassConvert < Base
|
886
|
+
# @param args [Array<String>] The command-line arguments
|
887
|
+
def initialize(args)
|
888
|
+
super
|
889
|
+
# require 'sass'
|
890
|
+
@options[:for_tree] = {}
|
891
|
+
@options[:for_engine] = {:cache => false, :read_cache => true}
|
892
|
+
end
|
893
|
+
|
894
|
+
# Tells optparse how to parse the arguments.
|
895
|
+
#
|
896
|
+
# @param opts [OptionParser]
|
897
|
+
def set_opts(opts)
|
898
|
+
opts.banner = <<END
|
899
|
+
Usage: sass-convert [options] [INPUT] [OUTPUT]
|
900
|
+
|
901
|
+
Description:
|
902
|
+
Converts between CSS, indented syntax, and SCSS files. For example,
|
903
|
+
this can convert from the indented syntax to SCSS, or from CSS to
|
904
|
+
SCSS (adding appropriate nesting).
|
905
|
+
END
|
906
|
+
|
907
|
+
common_options(opts)
|
908
|
+
style(opts)
|
909
|
+
input_and_output(opts)
|
910
|
+
miscellaneous(opts)
|
911
|
+
end
|
912
|
+
|
913
|
+
# Processes the options set by the command-line arguments,
|
914
|
+
# and runs the CSS compiler appropriately.
|
915
|
+
def process_result
|
916
|
+
# require 'sass'
|
917
|
+
|
918
|
+
if @options[:recursive]
|
919
|
+
process_directory
|
920
|
+
return
|
921
|
+
end
|
922
|
+
|
923
|
+
super
|
924
|
+
input = @options[:input]
|
925
|
+
if File.directory?(input)
|
926
|
+
raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)"
|
927
|
+
end
|
928
|
+
output = @options[:output]
|
929
|
+
output = input if @options[:in_place]
|
930
|
+
process_file(input, output)
|
931
|
+
end
|
932
|
+
|
933
|
+
private
|
934
|
+
|
935
|
+
def common_options(opts)
|
936
|
+
opts.separator ''
|
937
|
+
opts.separator 'Common Options:'
|
938
|
+
|
939
|
+
opts.on('-F', '--from FORMAT',
|
940
|
+
'The format to convert from. Can be css, scss, sass.',
|
941
|
+
'By default, this is inferred from the input filename.',
|
942
|
+
'If there is none, defaults to css.') do |name|
|
943
|
+
@options[:from] = name.downcase.to_sym
|
944
|
+
raise "sass-convert no longer supports LessCSS." if @options[:from] == :less
|
945
|
+
unless [:css, :scss, :sass].include?(@options[:from])
|
946
|
+
raise "Unknown format for sass-convert --from: #{name}"
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
opts.on('-T', '--to FORMAT',
|
951
|
+
'The format to convert to. Can be scss or sass.',
|
952
|
+
'By default, this is inferred from the output filename.',
|
953
|
+
'If there is none, defaults to sass.') do |name|
|
954
|
+
@options[:to] = name.downcase.to_sym
|
955
|
+
unless [:scss, :sass].include?(@options[:to])
|
956
|
+
raise "Unknown format for sass-convert --to: #{name}"
|
957
|
+
end
|
958
|
+
end
|
959
|
+
|
960
|
+
opts.on('-i', '--in-place',
|
961
|
+
'Convert a file to its own syntax.',
|
962
|
+
'This can be used to update some deprecated syntax.') do
|
963
|
+
@options[:in_place] = true
|
964
|
+
end
|
965
|
+
|
966
|
+
opts.on('-R', '--recursive',
|
967
|
+
'Convert all the files in a directory. Requires --from and --to.') do
|
968
|
+
@options[:recursive] = true
|
969
|
+
end
|
970
|
+
|
971
|
+
opts.on("-?", "-h", "--help", "Show this help message.") do
|
972
|
+
puts opts
|
973
|
+
exit
|
974
|
+
end
|
975
|
+
|
976
|
+
opts.on("-v", "--version", "Print the Sass version.") do
|
977
|
+
puts("Sass #{Sass.version[:string]}")
|
978
|
+
exit
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
def style(opts)
|
983
|
+
opts.separator ''
|
984
|
+
opts.separator 'Style:'
|
985
|
+
|
986
|
+
opts.on('--dasherize', 'Convert underscores to dashes.') do
|
987
|
+
@options[:for_tree][:dasherize] = true
|
988
|
+
end
|
989
|
+
|
990
|
+
opts.on('--indent NUM',
|
991
|
+
'How many spaces to use for each level of indentation. Defaults to 2.',
|
992
|
+
'"t" means use hard tabs.') do |indent|
|
993
|
+
|
994
|
+
if indent == 't'
|
995
|
+
@options[:for_tree][:indent] = "\t"
|
996
|
+
else
|
997
|
+
@options[:for_tree][:indent] = " " * indent.to_i
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
|
1001
|
+
opts.on('--old', 'Output the old-style ":prop val" property syntax.',
|
1002
|
+
'Only meaningful when generating Sass.') do
|
1003
|
+
@options[:for_tree][:old] = true
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def input_and_output(opts)
|
1008
|
+
opts.separator ''
|
1009
|
+
opts.separator 'Input and Output:'
|
1010
|
+
|
1011
|
+
opts.on('-s', '--stdin', :NONE,
|
1012
|
+
'Read input from standard input instead of an input file.',
|
1013
|
+
'This is the default if no input file is specified. Requires --from.') do
|
1014
|
+
@options[:input] = $stdin
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
encoding_option(opts)
|
1018
|
+
|
1019
|
+
opts.on('--unix-newlines', 'Use Unix-style newlines in written files.',
|
1020
|
+
('Always true on Unix.' unless Sass::Util.windows?)) do
|
1021
|
+
@options[:unix_newlines] = true if Sass::Util.windows?
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
def miscellaneous(opts)
|
1026
|
+
opts.separator ''
|
1027
|
+
opts.separator 'Miscellaneous:'
|
1028
|
+
|
1029
|
+
opts.on('--cache-location PATH',
|
1030
|
+
'The path to save parsed Sass files. Defaults to .sass-cache.') do |loc|
|
1031
|
+
@options[:for_engine][:cache_location] = loc
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
|
1035
|
+
@options[:for_engine][:read_cache] = false
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error') do
|
1039
|
+
@options[:trace] = true
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def process_directory
|
1044
|
+
unless @options[:input] = @args.shift
|
1045
|
+
raise "Error: directory required when using --recursive."
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
output = @options[:output] = @args.shift
|
1049
|
+
raise "Error: --from required when using --recursive." unless @options[:from]
|
1050
|
+
raise "Error: --to required when using --recursive." unless @options[:to]
|
1051
|
+
unless File.directory?(@options[:input])
|
1052
|
+
raise "Error: '#{@options[:input]}' is not a directory"
|
1053
|
+
end
|
1054
|
+
if @options[:output] && File.exist?(@options[:output]) &&
|
1055
|
+
!File.directory?(@options[:output])
|
1056
|
+
raise "Error: '#{@options[:output]}' is not a directory"
|
1057
|
+
end
|
1058
|
+
@options[:output] ||= @options[:input]
|
1059
|
+
|
1060
|
+
if @options[:to] == @options[:from] && !@options[:in_place]
|
1061
|
+
fmt = @options[:from]
|
1062
|
+
raise "Error: converting from #{fmt} to #{fmt} without --in-place"
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
ext = @options[:from]
|
1066
|
+
Sass::Util.glob("#{@options[:input]}/**/*.#{ext}") do |f|
|
1067
|
+
output =
|
1068
|
+
if @options[:in_place]
|
1069
|
+
f
|
1070
|
+
elsif @options[:output]
|
1071
|
+
output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
|
1072
|
+
output_name[0...@options[:input].size] = @options[:output]
|
1073
|
+
output_name
|
1074
|
+
else
|
1075
|
+
f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
unless File.directory?(File.dirname(output))
|
1079
|
+
puts_action :directory, :green, File.dirname(output)
|
1080
|
+
FileUtils.mkdir_p(File.dirname(output))
|
1081
|
+
end
|
1082
|
+
puts_action :convert, :green, f
|
1083
|
+
if File.exist?(output)
|
1084
|
+
puts_action :overwrite, :yellow, output
|
1085
|
+
else
|
1086
|
+
puts_action :create, :green, output
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
process_file(f, output)
|
1090
|
+
end
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
def process_file(input, output)
|
1094
|
+
input_path, output_path = path_for(input), path_for(output)
|
1095
|
+
if input_path
|
1096
|
+
@options[:from] ||=
|
1097
|
+
case input_path
|
1098
|
+
when /\.scss$/; :scss
|
1099
|
+
when /\.sass$/; :sass
|
1100
|
+
when /\.less$/; raise "sass-convert no longer supports LessCSS."
|
1101
|
+
when /\.css$/; :css
|
1102
|
+
end
|
1103
|
+
elsif @options[:in_place]
|
1104
|
+
raise "Error: the --in-place option requires a filename."
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
if output_path
|
1108
|
+
@options[:to] ||=
|
1109
|
+
case output_path
|
1110
|
+
when /\.scss$/; :scss
|
1111
|
+
when /\.sass$/; :sass
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
@options[:from] ||= :css
|
1116
|
+
@options[:to] ||= :sass
|
1117
|
+
@options[:for_engine][:syntax] = @options[:from]
|
1118
|
+
|
1119
|
+
out =
|
1120
|
+
Sass::Util.silence_sass_warnings do
|
1121
|
+
if @options[:from] == :css
|
1122
|
+
require 'sass/css'
|
1123
|
+
Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
|
1124
|
+
else
|
1125
|
+
if input_path
|
1126
|
+
Sass::Engine.for_file(input_path, @options[:for_engine])
|
1127
|
+
else
|
1128
|
+
Sass::Engine.new(input.read, @options[:for_engine])
|
1129
|
+
end.to_tree.send("to_#{@options[:to]}", @options[:for_tree])
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
output = input_path if @options[:in_place]
|
1134
|
+
write_output(out, output)
|
1135
|
+
rescue Sass::SyntaxError => e
|
1136
|
+
raise e if @options[:trace]
|
1137
|
+
file = " of #{e.sass_filename}" if e.sass_filename
|
1138
|
+
raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
|
1139
|
+
rescue LoadError => err
|
1140
|
+
handle_load_error(err)
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
def path_for(file)
|
1144
|
+
return file.path if file.is_a?(File)
|
1145
|
+
return file if file.is_a?(String)
|
1146
|
+
end
|
1147
|
+
end
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
# 重写sass_scss.rb 去掉require
|
1151
|
+
module Sass::Exec
|
1152
|
+
# The `sass` and `scss` executables.
|
1153
|
+
class SassScss < Base
|
1154
|
+
attr_reader :default_syntax
|
1155
|
+
|
1156
|
+
# @param args [Array<String>] The command-line arguments
|
1157
|
+
def initialize(args, default_syntax)
|
1158
|
+
super(args)
|
1159
|
+
@options[:sourcemap] = :auto
|
1160
|
+
@options[:for_engine] = {
|
1161
|
+
:load_paths => default_sass_path
|
1162
|
+
}
|
1163
|
+
@default_syntax = default_syntax
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
protected
|
1167
|
+
|
1168
|
+
# Tells optparse how to parse the arguments.
|
1169
|
+
#
|
1170
|
+
# @param opts [OptionParser]
|
1171
|
+
def set_opts(opts)
|
1172
|
+
opts.banner = <<END
|
1173
|
+
Usage: #{default_syntax} [options] [INPUT] [OUTPUT]
|
1174
|
+
|
1175
|
+
Description:
|
1176
|
+
Converts SCSS or Sass files to CSS.
|
1177
|
+
END
|
1178
|
+
|
1179
|
+
common_options(opts)
|
1180
|
+
watching_and_updating(opts)
|
1181
|
+
input_and_output(opts)
|
1182
|
+
miscellaneous(opts)
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
# Processes the options set by the command-line arguments,
|
1186
|
+
# and runs the Sass compiler appropriately.
|
1187
|
+
def process_result
|
1188
|
+
# require 'sass'
|
1189
|
+
|
1190
|
+
if !@options[:update] && !@options[:watch] &&
|
1191
|
+
@args.first && colon_path?(@args.first)
|
1192
|
+
if @args.size == 1
|
1193
|
+
@args = split_colon_path(@args.first)
|
1194
|
+
else
|
1195
|
+
@options[:update] = true
|
1196
|
+
end
|
1197
|
+
end
|
1198
|
+
load_compass if @options[:compass]
|
1199
|
+
return interactive if @options[:interactive]
|
1200
|
+
return watch_or_update if @options[:watch] || @options[:update]
|
1201
|
+
super
|
1202
|
+
|
1203
|
+
if @options[:sourcemap] != :none && @options[:output_filename]
|
1204
|
+
@options[:sourcemap_filename] = Sass::Util.sourcemap_name(@options[:output_filename])
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
@options[:for_engine][:filename] = @options[:filename]
|
1208
|
+
@options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
|
1209
|
+
@options[:for_engine][:sourcemap_filename] = @options[:sourcemap_filename]
|
1210
|
+
@options[:for_engine][:sourcemap] = @options[:sourcemap]
|
1211
|
+
|
1212
|
+
run
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
private
|
1216
|
+
|
1217
|
+
def common_options(opts)
|
1218
|
+
opts.separator ''
|
1219
|
+
opts.separator 'Common Options:'
|
1220
|
+
|
1221
|
+
opts.on('-I', '--load-path PATH', 'Specify a Sass import path.') do |path|
|
1222
|
+
(@options[:for_engine][:load_paths] ||= []) << path
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
|
1226
|
+
require lib
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
opts.on('--compass', 'Make Compass imports available and load project configuration.') do
|
1230
|
+
@options[:compass] = true
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
opts.on('-t', '--style NAME', 'Output style. Can be nested (default), compact, ' \
|
1234
|
+
'compressed, or expanded.') do |name|
|
1235
|
+
@options[:for_engine][:style] = name.to_sym
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
opts.on("-?", "-h", "--help", "Show this help message.") do
|
1239
|
+
puts opts
|
1240
|
+
exit
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
opts.on("-v", "--version", "Print the Sass version.") do
|
1244
|
+
puts("Sass #{Sass.version[:string]}")
|
1245
|
+
exit
|
1246
|
+
end
|
1247
|
+
end
|
1248
|
+
|
1249
|
+
def watching_and_updating(opts)
|
1250
|
+
opts.separator ''
|
1251
|
+
opts.separator 'Watching and Updating:'
|
1252
|
+
|
1253
|
+
opts.on('--watch', 'Watch files or directories for changes.',
|
1254
|
+
'The location of the generated CSS can be set using a colon:',
|
1255
|
+
" #{@default_syntax} --watch input.#{@default_syntax}:output.css",
|
1256
|
+
" #{@default_syntax} --watch input-dir:output-dir") do
|
1257
|
+
@options[:watch] = true
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
# Polling is used by default on Windows.
|
1261
|
+
unless Sass::Util.windows?
|
1262
|
+
opts.on('--poll', 'Check for file changes manually, rather than relying on the OS.',
|
1263
|
+
'Only meaningful for --watch.') do
|
1264
|
+
@options[:poll] = true
|
1265
|
+
end
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
opts.on('--update', 'Compile files or directories to CSS.',
|
1269
|
+
'Locations are set like --watch.') do
|
1270
|
+
@options[:update] = true
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
opts.on('-f', '--force', 'Recompile every Sass file, even if the CSS file is newer.',
|
1274
|
+
'Only meaningful for --update.') do
|
1275
|
+
@options[:force] = true
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
|
1279
|
+
'Only meaningful for --watch and --update.') do
|
1280
|
+
@options[:stop_on_error] = true
|
1281
|
+
end
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
def input_and_output(opts)
|
1285
|
+
opts.separator ''
|
1286
|
+
opts.separator 'Input and Output:'
|
1287
|
+
|
1288
|
+
if @default_syntax == :sass
|
1289
|
+
opts.on('--scss',
|
1290
|
+
'Use the CSS-superset SCSS syntax.') do
|
1291
|
+
@options[:for_engine][:syntax] = :scss
|
1292
|
+
end
|
1293
|
+
else
|
1294
|
+
opts.on('--sass',
|
1295
|
+
'Use the indented Sass syntax.') do
|
1296
|
+
@options[:for_engine][:syntax] = :sass
|
1297
|
+
end
|
1298
|
+
end
|
1299
|
+
|
1300
|
+
# This is optional for backwards-compatibility with Sass 3.3, which didn't
|
1301
|
+
# enable sourcemaps by default and instead used "--sourcemap" to do so.
|
1302
|
+
opts.on(:OPTIONAL, '--sourcemap=TYPE',
|
1303
|
+
'How to link generated output to the source files.',
|
1304
|
+
' auto (default): relative paths where possible, file URIs elsewhere',
|
1305
|
+
' file: always absolute file URIs',
|
1306
|
+
' inline: include the source text in the sourcemap',
|
1307
|
+
' none: no sourcemaps') do |type|
|
1308
|
+
if type && !%w[auto file inline none].include?(type)
|
1309
|
+
$stderr.puts "Unknown sourcemap type #{type}.\n\n"
|
1310
|
+
$stderr.puts opts
|
1311
|
+
exit
|
1312
|
+
elsif type.nil?
|
1313
|
+
Sass::Util.sass_warn <<MESSAGE.rstrip
|
1314
|
+
DEPRECATION WARNING: Passing --sourcemap without a value is deprecated.
|
1315
|
+
Sourcemaps are now generated by default, so this flag has no effect.
|
1316
|
+
MESSAGE
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
@options[:sourcemap] = (type || :auto).to_sym
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
opts.on('-s', '--stdin', :NONE,
|
1323
|
+
'Read input from standard input instead of an input file.',
|
1324
|
+
'This is the default if no input file is specified.') do
|
1325
|
+
@options[:input] = $stdin
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
encoding_option(opts)
|
1329
|
+
|
1330
|
+
opts.on('--unix-newlines', 'Use Unix-style newlines in written files.',
|
1331
|
+
('Always true on Unix.' unless Sass::Util.windows?)) do
|
1332
|
+
@options[:unix_newlines] = true if Sass::Util.windows?
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
opts.on('-g', '--debug-info',
|
1336
|
+
'Emit output that can be used by the FireSass Firebug plugin.') do
|
1337
|
+
@options[:for_engine][:debug_info] = true
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
opts.on('-l', '--line-numbers', '--line-comments',
|
1341
|
+
'Emit comments in the generated CSS indicating the corresponding source line.') do
|
1342
|
+
@options[:for_engine][:line_numbers] = true
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
def miscellaneous(opts)
|
1347
|
+
opts.separator ''
|
1348
|
+
opts.separator 'Miscellaneous:'
|
1349
|
+
|
1350
|
+
opts.on('-i', '--interactive',
|
1351
|
+
'Run an interactive SassScript shell.') do
|
1352
|
+
@options[:interactive] = true
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
|
1356
|
+
require 'stringio'
|
1357
|
+
@options[:check_syntax] = true
|
1358
|
+
@options[:output] = StringIO.new
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
opts.on('--precision NUMBER_OF_DIGITS', Integer,
|
1362
|
+
"How many digits of precision to use when outputting decimal numbers.",
|
1363
|
+
"Defaults to #{Sass::Script::Value::Number.precision}.") do |precision|
|
1364
|
+
Sass::Script::Value::Number.precision = precision
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
opts.on('--cache-location PATH',
|
1368
|
+
'The path to save parsed Sass files. Defaults to .sass-cache.') do |loc|
|
1369
|
+
@options[:for_engine][:cache_location] = loc
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
opts.on('-C', '--no-cache', "Don't cache parsed Sass files.") do
|
1373
|
+
@options[:for_engine][:cache] = false
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error.') do
|
1377
|
+
@options[:trace] = true
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
|
1381
|
+
@options[:for_engine][:quiet] = true
|
1382
|
+
end
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
def load_compass
|
1386
|
+
begin
|
1387
|
+
require 'compass'
|
1388
|
+
rescue LoadError
|
1389
|
+
require 'rubygems'
|
1390
|
+
begin
|
1391
|
+
require 'compass'
|
1392
|
+
rescue LoadError
|
1393
|
+
puts "ERROR: Cannot load compass."
|
1394
|
+
exit 1
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
Compass.add_project_configuration
|
1398
|
+
Compass.configuration.project_path ||= Dir.pwd
|
1399
|
+
@options[:for_engine][:load_paths] ||= []
|
1400
|
+
@options[:for_engine][:load_paths] += Compass.configuration.sass_load_paths
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
def interactive
|
1404
|
+
require 'sass/repl'
|
1405
|
+
Sass::Repl.new(@options).run
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
# @comment
|
1409
|
+
# rubocop:disable MethodLength
|
1410
|
+
def watch_or_update
|
1411
|
+
require 'sass/plugin'
|
1412
|
+
Sass::Plugin.options.merge! @options[:for_engine]
|
1413
|
+
Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
|
1414
|
+
Sass::Plugin.options[:poll] = @options[:poll]
|
1415
|
+
Sass::Plugin.options[:sourcemap] = @options[:sourcemap]
|
1416
|
+
|
1417
|
+
if @options[:force]
|
1418
|
+
raise "The --force flag may only be used with --update." unless @options[:update]
|
1419
|
+
Sass::Plugin.options[:always_update] = true
|
1420
|
+
end
|
1421
|
+
|
1422
|
+
raise <<MSG if @args.empty?
|
1423
|
+
What files should I watch? Did you mean something like:
|
1424
|
+
#{@default_syntax} --watch input.#{@default_syntax}:output.css
|
1425
|
+
#{@default_syntax} --watch input-dir:output-dir
|
1426
|
+
MSG
|
1427
|
+
|
1428
|
+
if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
|
1429
|
+
flag = @options[:update] ? "--update" : "--watch"
|
1430
|
+
err =
|
1431
|
+
if !File.exist?(@args[1])
|
1432
|
+
"doesn't exist"
|
1433
|
+
elsif @args[1] =~ /\.css$/
|
1434
|
+
"is a CSS file"
|
1435
|
+
end
|
1436
|
+
raise <<MSG if err
|
1437
|
+
File #{@args[1]} #{err}.
|
1438
|
+
Did you mean: #{@default_syntax} #{flag} #{@args[0]}:#{@args[1]}
|
1439
|
+
MSG
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
# Watch the working directory for changes without adding it to the load
|
1443
|
+
# path. This preserves the pre-3.4 behavior when the working directory was
|
1444
|
+
# on the load path. We should remove this when we can look for directories
|
1445
|
+
# to watch by traversing the import graph.
|
1446
|
+
class << Sass::Plugin.compiler
|
1447
|
+
# We have to use a class var to make this visible to #watched_file? and
|
1448
|
+
# #watched_paths.
|
1449
|
+
# rubocop:disable ClassVars
|
1450
|
+
@@working_directory = Sass::Util.realpath('.').to_s
|
1451
|
+
# rubocop:ensable ClassVars
|
1452
|
+
|
1453
|
+
def watched_file?(file)
|
1454
|
+
super(file) ||
|
1455
|
+
(file =~ /\.s[ac]ss$/ && file.start_with?(@@working_directory + File::SEPARATOR))
|
1456
|
+
end
|
1457
|
+
|
1458
|
+
def watched_paths
|
1459
|
+
@watched_paths ||= super + [@@working_directory]
|
1460
|
+
end
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
dirs, files = @args.map {|name| split_colon_path(name)}.
|
1464
|
+
partition {|i, _| File.directory? i}
|
1465
|
+
files.map! do |from, to|
|
1466
|
+
to ||= from.gsub(/\.[^.]*?$/, '.css')
|
1467
|
+
sourcemap = Sass::Util.sourcemap_name(to) if @options[:sourcemap]
|
1468
|
+
[from, to, sourcemap]
|
1469
|
+
end
|
1470
|
+
dirs.map! {|from, to| [from, to || from]}
|
1471
|
+
Sass::Plugin.options[:template_location] = dirs
|
1472
|
+
|
1473
|
+
Sass::Plugin.on_updated_stylesheet do |_, css, sourcemap|
|
1474
|
+
[css, sourcemap].each do |file|
|
1475
|
+
next unless file
|
1476
|
+
puts_action :write, :green, file
|
1477
|
+
end
|
1478
|
+
end
|
1479
|
+
|
1480
|
+
had_error = false
|
1481
|
+
Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
|
1482
|
+
Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
|
1483
|
+
Sass::Plugin.on_deleting_sourcemap {|filename| puts_action :delete, :yellow, filename}
|
1484
|
+
Sass::Plugin.on_compilation_error do |error, _, _|
|
1485
|
+
if error.is_a?(SystemCallError) && !@options[:stop_on_error]
|
1486
|
+
had_error = true
|
1487
|
+
puts_action :error, :red, error.message
|
1488
|
+
STDOUT.flush
|
1489
|
+
next
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
raise error unless error.is_a?(Sass::SyntaxError) && !@options[:stop_on_error]
|
1493
|
+
had_error = true
|
1494
|
+
puts_action :error, :red,
|
1495
|
+
"#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
|
1496
|
+
STDOUT.flush
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
if @options[:update]
|
1500
|
+
Sass::Plugin.update_stylesheets(files)
|
1501
|
+
exit 1 if had_error
|
1502
|
+
return
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
|
1506
|
+
|
1507
|
+
Sass::Plugin.on_template_modified do |template|
|
1508
|
+
puts ">>> Change detected to: #{template}"
|
1509
|
+
STDOUT.flush
|
1510
|
+
end
|
1511
|
+
Sass::Plugin.on_template_created do |template|
|
1512
|
+
puts ">>> New template detected: #{template}"
|
1513
|
+
STDOUT.flush
|
1514
|
+
end
|
1515
|
+
Sass::Plugin.on_template_deleted do |template|
|
1516
|
+
puts ">>> Deleted template detected: #{template}"
|
1517
|
+
STDOUT.flush
|
1518
|
+
end
|
1519
|
+
|
1520
|
+
Sass::Plugin.watch(files)
|
1521
|
+
end
|
1522
|
+
# @comment
|
1523
|
+
# rubocop:enable MethodLength
|
1524
|
+
|
1525
|
+
def run
|
1526
|
+
input = @options[:input]
|
1527
|
+
output = @options[:output]
|
1528
|
+
|
1529
|
+
@options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
|
1530
|
+
@options[:for_engine][:syntax] ||= @default_syntax
|
1531
|
+
engine =
|
1532
|
+
if input.is_a?(File) && !@options[:check_syntax]
|
1533
|
+
Sass::Engine.for_file(input.path, @options[:for_engine])
|
1534
|
+
else
|
1535
|
+
# We don't need to do any special handling of @options[:check_syntax] here,
|
1536
|
+
# because the Sass syntax checking happens alongside evaluation
|
1537
|
+
# and evaluation doesn't actually evaluate any code anyway.
|
1538
|
+
Sass::Engine.new(input.read, @options[:for_engine])
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
input.close if input.is_a?(File)
|
1542
|
+
|
1543
|
+
if @options[:sourcemap] != :none && @options[:sourcemap_filename]
|
1544
|
+
relative_sourcemap_path = Sass::Util.relative_path_from(
|
1545
|
+
@options[:sourcemap_filename], Sass::Util.pathname(@options[:output_filename]).dirname)
|
1546
|
+
rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s)
|
1547
|
+
write_output(rendered, output)
|
1548
|
+
write_output(mapping.to_json(
|
1549
|
+
:type => @options[:sourcemap],
|
1550
|
+
:css_path => @options[:output_filename],
|
1551
|
+
:sourcemap_path => @options[:sourcemap_filename]) + "\n",
|
1552
|
+
@options[:sourcemap_filename])
|
1553
|
+
else
|
1554
|
+
write_output(engine.render, output)
|
1555
|
+
end
|
1556
|
+
rescue Sass::SyntaxError => e
|
1557
|
+
write_output(Sass::SyntaxError.exception_to_css(e), output) if output.is_a?(String)
|
1558
|
+
raise e
|
1559
|
+
ensure
|
1560
|
+
output.close if output.is_a? File
|
1561
|
+
end
|
1562
|
+
|
1563
|
+
def colon_path?(path)
|
1564
|
+
!split_colon_path(path)[1].nil?
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
def split_colon_path(path)
|
1568
|
+
one, two = path.split(':', 2)
|
1569
|
+
if one && two && Sass::Util.windows? &&
|
1570
|
+
one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
|
1571
|
+
# If we're on Windows and we were passed a drive letter path,
|
1572
|
+
# don't split on that colon.
|
1573
|
+
one2, two = two.split(':', 2)
|
1574
|
+
one = one + ':' + one2
|
1575
|
+
end
|
1576
|
+
return one, two
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
# Whether path is likely to be meant as the destination
|
1580
|
+
# in a source:dest pair.
|
1581
|
+
def probably_dest_dir?(path)
|
1582
|
+
return false unless path
|
1583
|
+
return false if colon_path?(path)
|
1584
|
+
Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
|
1585
|
+
end
|
1586
|
+
|
1587
|
+
def default_sass_path
|
1588
|
+
return unless ENV['SASS_PATH']
|
1589
|
+
# The select here prevents errors when the environment's
|
1590
|
+
# load paths specified do not exist.
|
1591
|
+
ENV['SASS_PATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)}
|
1592
|
+
end
|
1593
|
+
end
|
1594
|
+
end
|