module Sass::Exec # The `sass` and `scss` executables. class SassScss < Base attr_reader :default_syntax # @param args [Array] The command-line arguments def initialize(args, default_syntax) super(args) @options[:sourcemap] = :auto @options[:for_engine] = { :load_paths => default_sass_path } @default_syntax = default_syntax end protected # Tells optparse how to parse the arguments. # # @param opts [OptionParser] def set_opts(opts) opts.banner = <>> Sass is watching for changes. Press Ctrl-C to stop." Sass::Plugin.on_template_modified do |template| puts ">>> Change detected to: #{template}" STDOUT.flush end Sass::Plugin.on_template_created do |template| puts ">>> New template detected: #{template}" STDOUT.flush end Sass::Plugin.on_template_deleted do |template| puts ">>> Deleted template detected: #{template}" STDOUT.flush end Sass::Plugin.watch(files) end # @comment # rubocop:enable MethodLength def run input = @options[:input] output = @options[:output] if input == $stdin # See issue 1745 (@options[:for_engine][:load_paths] ||= []) << ::Sass::Importers::DeprecatedPath.new(".") end @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/ @options[:for_engine][:syntax] ||= @default_syntax engine = if input.is_a?(File) && !@options[:check_syntax] Sass::Engine.for_file(input.path, @options[:for_engine]) else # We don't need to do any special handling of @options[:check_syntax] here, # because the Sass syntax checking happens alongside evaluation # and evaluation doesn't actually evaluate any code anyway. Sass::Engine.new(input.read, @options[:for_engine]) end input.close if input.is_a?(File) if @options[:sourcemap] != :none && @options[:sourcemap_filename] relative_sourcemap_path = Sass::Util.relative_path_from( @options[:sourcemap_filename], Sass::Util.pathname(@options[:output_filename]).dirname) rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s) write_output(rendered, output) write_output( mapping.to_json( :type => @options[:sourcemap], :css_path => @options[:output_filename], :sourcemap_path => @options[:sourcemap_filename]) + "\n", @options[:sourcemap_filename]) else write_output(engine.render, output) end rescue Sass::SyntaxError => e write_output(Sass::SyntaxError.exception_to_css(e), output) if output.is_a?(String) raise e ensure output.close if output.is_a? File end def colon_path?(path) !split_colon_path(path)[1].nil? end def split_colon_path(path) one, two = path.split(':', 2) if one && two && Sass::Util.windows? && one =~ /\A[A-Za-z]\Z/ && two =~ %r{\A[/\\]} # If we're on Windows and we were passed a drive letter path, # don't split on that colon. one2, two = two.split(':', 2) one = one + ':' + one2 end return one, two end # Whether path is likely to be meant as the destination # in a source:dest pair. def probably_dest_dir?(path) return false unless path return false if colon_path?(path) Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty? end def default_sass_path return unless ENV['SASS_PATH'] # The select here prevents errors when the environment's # load paths specified do not exist. ENV['SASS_PATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)} end end end