bf4/metric_fu

View on GitHub
lib/metric_fu/metrics/flog/flog.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'pathname'
require 'optparse'
require 'rbconfig'
class RubyParser
  alias_method :original_process, :process
  def process(s,f,t)
    original_process(s,f,t)
  rescue => e
    if e.message =~ /wrong number of arguments/
      original_process(s,f)
    else
      raise
    end
  end
end
module MetricFu

  class Flog < Generator

    def emit
      files = []
      MetricFu.flog[:dirs_to_flog].each do |directory|
        directory = "." if directory=='./'
        dir_files = Dir.glob("#{directory}/**/*.rb")
        dir_files = remove_excluded_files(dir_files)
        files += dir_files
      end
      options = ::Flog.parse_options ["--all", "--details"]
      # TODO determine if flogging should continue despite errors
      # options = ::Flog.parse_options ["--all", "--details", "--continue"]

      @flogger = ::Flog.new options
      @flogger.flog files

    rescue LoadError => e
      message = "#{e.class}\t#{e.message}\n\t#{e.backtrace.join('\n\t')}"
      if MetricFu.configuration.mri?
        puts "Flog Error: #{message}"
      else
        puts "Flog tasks only available in MRI: #{message}"
      end
    end

    def analyze
      @method_containers = {}
      @flogger.calls.each do |full_method_name, operators|
        container_name = full_method_name.split('#').first
        path = @flogger.method_locations[full_method_name]
        if @method_containers[container_name]
          @method_containers[container_name].add_method(full_method_name, operators, @flogger.totals[full_method_name], path)
          @method_containers[container_name].add_path(path)
        else
          mc = MethodContainer.new(container_name, path)
          mc.add_method(full_method_name, operators, @flogger.totals[full_method_name], path)
          @method_containers[container_name] = mc
        end
      end
    end

    def to_h
      sorted_containers = @method_containers.values.sort_by {|c| c.highest_score}.reverse
      {:flog => { :total => @flogger.total,
                  :average => @flogger.average,
                  :method_containers => sorted_containers.map {|method_container| method_container.to_h}}}
    end

    def per_file_info(out)
      @method_containers.each_pair do |klass, container|
        container.methods.each_pair do |method_name, data|
          next if data[:path].nil?

          file, line = data[:path].split(':')

          out[file] ||= {}
          out[file][line] ||= []
          out[file][line] << {:type => :flog, :description => "Score of %.2f" % data[:score]}
        end
      end
    end
  end

  class MethodContainer
    attr_reader :methods

    def initialize(name, path)
      @name = name
      add_path path
      @methods = {}
    end

    def add_path path
      return unless path
      @path ||= path.split(':').first
    end

    def add_method(full_method_name, operators, score, path)
      @methods[full_method_name] = {:operators => operators, :score => score, :path => path}
    end

    def to_h
      { :name => @name,
        :path => @path || '',
        :total_score => total_score,
        :highest_score => highest_score,
        :average_score => average_score,
        :methods => @methods}
    end

    def highest_score
      method_scores.max
    end

    private

    def method_scores
      @method_scores ||= @methods.values.map {|v| v[:score] }
    end

    def total_score
      @total_score ||= method_scores.inject(0) {|sum, score| sum += score}
    end

    def average_score
      total_score / method_scores.size.to_f
    end
  end
end