zmoazeni/csscss

View on GitHub
lib/csscss/cli.rb

Summary

Maintainability
C
1 day
Test Coverage
module Csscss
  class CLI
    def initialize(argv)
      @argv               = argv
      @verbose            = false
      @color              = !windows_1_9
      @minimum            = 3
      @compass            = false
      @ignored_properties = []
      @ignored_selectors  = []
      @match_shorthand    = true
      @ignore_sass_mixins = false
    end

    def run
      parse(@argv)
      execute
    end

    private
    def execute
      deprecate("Use --show-parser-errors instead of CSSCSS_DEBUG") if ENV["CSSCSS_DEBUG"]

      all_contents= @argv.map do |filename|
        if filename =~ URI.regexp
          load_css_file(filename)
        else
          case File.extname(filename).downcase
          when ".scss", ".sass"
            load_sass_file(filename)
          when ".less"
            load_less_file(filename)
          else
            load_css_file(filename)
          end
        end
      end.join("\n")

      unless all_contents.strip.empty?
        redundancies = RedundancyAnalyzer.new(all_contents).redundancies(
          minimum:            @minimum,
          ignored_properties: @ignored_properties,
          ignored_selectors:  @ignored_selectors,
          match_shorthand:    @match_shorthand
        )

        if @json
          puts JSONReporter.new(redundancies).report
        else
          report = Reporter.new(redundancies).report(verbose:@verbose, color:@color)
          puts report unless report.empty?
        end
      end

    rescue Parslet::ParseFailed => e
      line, column = e.cause.source.line_and_column(e.cause.pos)
      puts "Had a problem parsing the css at line: #{line}, column: #{column}".red
      if @show_parser_errors || ENV['CSSCSS_DEBUG'] == 'true'
        puts e.cause.ascii_tree.red
      else
        puts "Run with --show-parser-errors for verbose parser errors".red
      end
      exit 1
    end

    def parse(argv)
      options = OptionParser.new do |opts|
        opts.banner  = "Usage: csscss [files..]"
        opts.version = Csscss::VERSION

        opts.on("-v", "--[no-]verbose", "Display each rule") do |v|
          @verbose = v
        end

        opts.on("--[no-]color", "Colorize output", "(default is #{@color})") do |c|
          @color = c
        end

        opts.on("-n", "--num N", Integer, "Print matches with at least this many rules.", "(default is 3)") do |n|
          @minimum = n
        end

        opts.on("--[no-]match-shorthand", "Expand shorthand rules and matches on explicit rules", "(default is true)") do |match_shorthand|
          @match_shorthand = match_shorthand
        end

        opts.on("-j", "--[no-]json", "Output results in JSON") do |j|
          @json = j
        end

        opts.on("--ignore-sass-mixins", "EXPERIMENTAL: Ignore matches that come from including sass/scss mixins",
                "This is an experimental feature and may not be included in future releases",
                "(default is false)") do |ignore|
          @ignore_sass_mixins = ignore
        end

        opts.on("--[no-]compass", "Enable compass extensions when parsing sass/scss (default is false)") do |compass|
          enable_compass if @compass = compass
        end

        opts.on("--compass-with-config config", "Enable compass extensions when parsing sass/scss and pass config file") do |config|
          @compass = true
          enable_compass(config)
        end

        opts.on("--require file.rb", "Load ruby file before running csscss.", "Great for bootstrapping requires/configurations") do |file|
          load file
        end

        opts.on("--ignore-properties property1,property2,...", Array, "Ignore these properties when finding matches") do |ignored_properties|
          @ignored_properties = ignored_properties
        end

        opts.on('--ignore-selectors "selector1","selector2",...', Array, "Ignore these selectors when finding matches") do |ignored_selectors|
          @ignored_selectors = ignored_selectors
        end

        opts.on("--show-parser-errors", "Print verbose parser errors") do |show_parser_errors|
          @show_parser_errors = show_parser_errors
        end

        opts.on("-V", "--version", "Show version") do |v|
          puts opts.ver
          exit
        end

        opts.on_tail("-h", "--help", "Show this message") do
          print_help(opts)
        end
      end
      options.parse!(argv)

      print_help(options) if argv.empty?
    rescue OptionParser::ParseError
      print_help(options)
    end

    def print_help(opts)
      puts opts
      exit
    end

    def deprecate(message)
      $stderr.puts("DEPRECATED: #{message}".yellow)
    end

    def enable_compass(config = nil)
      abort 'Must install the "compass" gem before enabling its extensions' unless gem_installed?("compass")

      if config
        Compass.add_configuration(config)
      else
        Compass.add_configuration("config.rb") if File.exist?("config.rb")
      end
    end

    def windows_1_9
      RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ && RUBY_VERSION =~ /^1\.9/
    end

    def gem_installed?(gem_name)
      begin
        require gem_name
        true
      rescue LoadError
        false
      end
    end

    def load_sass_file(filename)
      abort 'Must install the "sass" gem before parsing sass/scss files' unless gem_installed?("sass")
      require "csscss/sass_include_extensions" if @ignore_sass_mixins

      sass_options = {cache:false}
      sass_options[:load_paths] = Compass.configuration.sass_load_paths if @compass
      begin
        Sass::Engine.for_file(filename, sass_options).render
      rescue Sass::SyntaxError => e
        if e.message =~ /compass/ && !@compass
          puts "Enable --compass option to use compass's extensions"
          exit 1
        else
          raise e
        end
      end
    end

    def load_less_file(filename)
      abort 'Must install the "less" gem before parsing less files' unless gem_installed?("less")
      contents = load_css_file(filename)
      Less::Parser.new.parse(contents).to_css
    end

    def load_css_file(filename)
      open(filename) {|f| f.read }
    end

    class << self
      def run(argv)
        new(argv).run
      end
    end
  end
end