class Flog
def add_to_score name, score = OTHER_SCORES[name]
def add_to_score name, score = OTHER_SCORES[name] return if option[:methods] and method_stack.empty? @calls[signature][name] += score * @multiplier end
def average
def average return 0 if calls.size == 0 total_score / calls.size end
def calculate
def calculate each_by_score threshold do |class_method, score, call_list| klass = class_method.scan(/.+(?=#|::)/).first method_scores[klass] << [class_method, score] scores[klass] += score end end
def calculate_total_scores
def calculate_total_scores return if @totals @total_score = 0 @totals = Hash.new(0) calls.each do |meth, tally| score = score_method(tally) @totals[meth] = score @total_score += score end end
def dsl_name? args
def dsl_name? args return false unless args and not args.empty? first_arg, = args first_arg = first_arg[1] if first_arg.sexp_type == :hash type, value, * = first_arg value if [:lit, :str].include? type end
def each_by_score max = nil
def each_by_score max = nil current = 0 calls.sort_by { |k,v| -totals[k] }.each do |class_method, call_list| score = totals[class_method] yield class_method, score, call_list current += score break if max and current >= max end end
def flog(*files)
def flog(*files) files.each do |file| next unless file == "-" or File.readable? file ruby = file == "-" ? $stdin.read : File.binread(file) flog_ruby ruby, file end calculate_total_scores end
def flog_ruby ruby, file="-", timeout = 10
def flog_ruby ruby, file="-", timeout = 10 flog_ruby! ruby, file, timeout rescue Timeout::Error warn "TIMEOUT parsing #{file}. Skipping." rescue RubyParser::SyntaxError, Racc::ParseError => e q = option[:quiet] if e.inspect =~ /<\%|%\>/ or ruby =~ /<\%|%\>/ then return if q warn "#{e.inspect} at #{e.backtrace.first(5).join(", ")}" warn "\n...stupid lemmings and their bad erb templates... skipping" else warn "ERROR: parsing ruby file #{file}" unless q unless option[:continue] then warn "ERROR! Aborting. You may want to run with --continue." raise e end return if q warn "%s: %s at:\n %s" % [e.class, e.message.strip, e.backtrace.first(5).join("\n ")] end end
def flog_ruby! ruby, file="-", timeout = 10
def flog_ruby! ruby, file="-", timeout = 10 @parser = (option[:parser] || RubyParser).new warn "** flogging #{file}" if option[:verbose] ast = @parser.process ruby, file, timeout return unless ast mass[file] = ast.mass process ast end
def initialize option = {}
def initialize option = {} super() @option = option @mass = {} @parser = nil @threshold = option[:threshold] || DEFAULT_THRESHOLD self.auto_shift_type = true self.reset end
def max_method
def max_method totals.max_by { |_, score| score } end
def max_score
def max_score max_method.last end
def no_method # :nodoc:
def no_method # :nodoc: @@no_method end
def penalize_by bonus
def penalize_by bonus @multiplier += bonus yield @multiplier -= bonus end
def process_alias(exp)
def process_alias(exp) process exp.shift process exp.shift add_to_score :alias s() end
def process_and(exp)
def process_and(exp) add_to_score :branch penalize_by 0.1 do process exp.shift # lhs process exp.shift # rhs end s() end
def process_attrasgn(exp)
def process_attrasgn(exp) add_to_score :assignment process exp.shift # lhs exp.shift # name process_until_empty exp # rhs s() end
def process_block(exp)
def process_block(exp) penalize_by 0.1 do process_until_empty exp end s() end
def process_block_pass(exp)
def process_block_pass(exp) arg = exp.shift add_to_score :block_pass return s() unless arg case arg.sexp_type when :lvar, :dvar, :ivar, :cvar, :self, :const, :colon2, :nil then # f(&b) # do nothing when :lit then # f(&:b) # do nothing -- this now costs about the same as a block when :call then # f(&x.b) # do nothing -- I don't like the indirection, but that's already scored when :lasgn then # f(&l=b) add_to_score :to_proc_lasgn when :iter, :dsym, :dstr, :hash, *BRANCHING then # below # f(&proc { ... }) # f(&"#{...}") # f(&:"#{...}") # f(&if ... then ... end") and all other branching forms # f(&{ a: 42 }) WHY?!? add_to_score :to_proc_icky! else raise({:block_pass_even_ickier! => arg}.inspect) end process arg s() end
def process_call(exp)
def process_call(exp) penalize_by 0.2 do process exp.shift # recv end name = exp.shift penalize_by 0.2 do process_until_empty exp end add_to_score name, SCORES[name] s() end
def process_case(exp)
def process_case(exp) add_to_score :branch process exp.shift # recv penalize_by 0.1 do process_until_empty exp end s() end
def process_class(exp)
def process_class(exp) super do penalize_by 1.0 do process exp.shift # superclass expression end process_until_empty exp end end
def process_dasgn_curr(exp) # FIX: remove
def process_dasgn_curr(exp) # FIX: remove add_to_score :assignment exp.shift # name process exp.shift # assigment, if any s() end
def process_else(exp)
def process_else(exp) add_to_score :branch penalize_by 0.1 do process_until_empty exp end s() end
def process_if(exp)
def process_if(exp) add_to_score :branch process exp.shift # cond penalize_by 0.1 do process exp.shift # true process exp.shift # false end s() end
def process_iter(exp)
def process_iter(exp) context = (self.context - [:class, :module, :scope]) context = context.uniq.sort_by { |s| s.to_s } exp.delete 0 # { || ... } has 0 in arg slot if context == [:block, :iter] or context == [:iter] then recv, = exp # DSL w/ names. eg task :name do ... end # looks like s(:call, nil, :task, s(:lit, :name)) # or s(:call, nil, :task, s(:str, "name")) # or s(:call, nil, :task, s(:hash, s(:lit, :name) ...)) t, r, m, *a = recv if t == :call and r == nil and submsg = dsl_name?(a) then m = "#{m}(#{submsg})" if m and [String, Symbol].include?(submsg.class) in_klass m do # :task/namespace in_method submsg, exp.file, exp.line, exp.line_max do # :name process_until_empty exp end end return s() end end add_to_score :block_call process exp.shift # no penalty for LHS penalize_by 0.1 do process_until_empty exp end s() end
def process_lit(exp)
def process_lit(exp) value = exp.shift case value when 0, -1 then # ignore those because they're used as array indicies instead of # first/last when Integer, Float, Rational, Complex then add_to_score :magic_number unless context[1] == :cdecl when Symbol, Regexp, Range then # do nothing else raise "Unhandled lit: #{value.inspect}:#{value.class}" end s() end
def process_masgn(exp)
def process_masgn(exp) add_to_score :assignment exp.map! { |s| Sexp === s ? s : s(:lasgn, s) } process_until_empty exp s() end
def process_safe_call(exp)
def process_safe_call(exp) penalize_by 0.3 do process exp.shift # recv end name = exp.shift penalize_by 0.2 do process_until_empty exp end add_to_score name, SCORES[name] s() end
def process_sclass(exp)
def process_sclass(exp) super do penalize_by 0.5 do process exp.shift # recv process_until_empty exp end end add_to_score :sclass s() end
def process_super(exp)
def process_super(exp) add_to_score :super process_until_empty exp s() end
def process_while(exp)
def process_while(exp) add_to_score :branch penalize_by 0.1 do process exp.shift # cond process exp.shift # body end exp.shift # pre/post s() end
def process_yield(exp)
def process_yield(exp) add_to_score :yield process_until_empty exp s() end
def reset
def reset @totals = @total_score = nil @multiplier = 1.0 @calls = Hash.new { |h,k| h[k] = Hash.new 0 } @method_scores = Hash.new { |h,k| h[k] = [] } @scores = Hash.new 0 method_locations.clear end
def score_method(tally)
def score_method(tally) a, b, c = 0, 0, 0 tally.each do |cat, score| case cat when :assignment then a += score when :branch, :block_call then b += score else c += score end end Math.sqrt(a*a + b*b + c*c) end
def threshold
def threshold option[:all] ? nil : total_score * @threshold end