sassmagic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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