In Files

Parent

Class/Module Index [+]

Quicksearch

Flog

Constants

BRANCHING
OTHER_SCORES

Various non-call constructs

SCORES
THRESHOLD
VERSION

Attributes

calls[R]
class_stack[R]
mass[R]
method_locations[R]
method_stack[R]
multiplier[RW]
option[R]

Public Class Methods

expand_dirs_to_files(*dirs) click to toggle source

REFACTOR: from flay

# File lib/flog.rb, line 76
def self.expand_dirs_to_files *dirs
  extensions = ['rb']

  dirs.flatten.map { |p|
    if File.directory? p then
      Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
    else
      p
    end
  }.flatten.sort
end
new(option = {}) click to toggle source
# File lib/flog.rb, line 213
def initialize option = {}
  super()
  @option              = option
  @class_stack         = []
  @method_stack        = []
  @method_locations    = {}
  @mass                = {}
  @parser              = RubyParser.new
  self.auto_shift_type = true
  self.reset
end
parse_options(args = ARGV) click to toggle source
# File lib/flog.rb, line 88
def self.parse_options args = ARGV
  option = {
    :quiet    => true,
    :continue => false,
  }

  OptionParser.new do |opts|
    opts.on("-a", "--all", "Display all flog results, not top 60%.") do
      option[:all] = true
    end

    opts.on("-b", "--blame", "Include blame information for methods.") do
      option[:blame] = true
    end

    opts.on("-c", "--continue", "Continue despite syntax errors.") do
      option[:continue] = true
    end

    opts.on("-d", "--details", "Show method details.") do
      option[:details] = true
    end

    opts.on("-g", "--group", "Group and sort by class.") do
      option[:group] = true
    end

    opts.on("-h", "--help", "Show this message.") do
      puts opts
      exit
    end

    opts.on("-I dir1,dir2,dir3", Array, "Add to LOAD_PATH.") do |dirs|
      dirs.each do |dir|
        $: << dir
      end
    end

    opts.on("-m", "--methods-only", "Skip code outside of methods.") do
      option[:methods] = true
    end

    opts.on("-q", "--quiet", "Don't show method details. [default]") do
      option[:quiet] = true
    end

    opts.on("-s", "--score", "Display total score only.") do
      option[:score] = true
    end

    opts.on("-v", "--verbose", "Display progress during processing.") do
      option[:verbose] = true
    end
  end.parse! Array(args)

  option
end

Public Instance Methods

add_to_score(name, score = OTHER_SCORES[name]) click to toggle source

Add a score to the tally. Score can be predetermined or looked up automatically. Uses multiplier for additional spankings. Spankings!

# File lib/flog.rb, line 151
def add_to_score name, score = OTHER_SCORES[name]
  @calls[signature][name] += score * @multiplier
end
average() click to toggle source

really?

# File lib/flog.rb, line 158
def average
  return 0 if calls.size == 0
  total / calls.size
end
flog(*files_or_dirs) click to toggle source

Flog the given files or directories. Smart. Deals with "-", syntax errors, and traversing subdirectories intelligently.

# File lib/flog.rb, line 167
def flog(*files_or_dirs)
  files = Flog.expand_dirs_to_files(*files_or_dirs)

  files.each do |file|
    begin
      # TODO: replace File.open to deal with "-"
      ruby = file == '-' ? $stdin.read : File.read(file)
      warn "** flogging #{file}" if option[:verbose]

      ast = @parser.process(ruby, file)
      next unless ast
      mass[file] = ast.mass
      process ast
    rescue SyntaxError, Racc::ParseError => e
      if e.inspect =~ /<%|%>/ or ruby =~ /<%|%>/ then
        warn "#{e.inspect} at #{e.backtrace.first(5).join(', ')}"
        warn "\n...stupid lemmings and their bad erb templates... skipping"
      else
        raise e unless option[:continue]
        warn file
        warn "#{e.inspect} at #{e.backtrace.first(5).join(', ')}"
      end
    end
  end
end
in_klass(name) click to toggle source

Adds name to the class stack, for the duration of the block

# File lib/flog.rb, line 196
def in_klass name
  @class_stack.unshift name
  yield
  @class_stack.shift
end
in_method(name, file, line) click to toggle source

Adds name to the method stack, for the duration of the block

# File lib/flog.rb, line 205
def in_method(name, file, line)
  method_name = Regexp === name ? name.inspect : name.to_s
  @method_stack.unshift method_name
  @method_locations[signature] = "#{file}:#{line}"
  yield
  @method_stack.shift
end
klass_name() click to toggle source

Returns the first class in the list, or @@no_class if there are none.

# File lib/flog.rb, line 229
def klass_name
  name = @class_stack.first || @@no_class
  if Sexp === name then
    name = case name.first
           when :colon2 then
             name = name.flatten
             name.delete :const
             name.delete :colon2
             name.join("::")
           when :colon3 then
             name.last.to_s
           else
             name
           end
  end
  name
end
method_name() click to toggle source

Returns the first method in the list, or “none” if there are none.

# File lib/flog.rb, line 251
def method_name
  m = @method_stack.first || @@no_method
  m = "##{m}" unless m =~ /::/
  m
end
output_details(io, max = nil) click to toggle source

Output the report up to a given max or report everything, if nil.

# File lib/flog.rb, line 260
def output_details(io, max = nil)
  my_totals = totals
  current = 0

  if option[:group] then
    scores = Hash.new 0
    methods = Hash.new { |h,k| h[k] = [] }

    calls.sort_by { |k,v| -my_totals[k] }.each do |class_method, call_list|
      klass = class_method.split(/#/).first
      score = totals[class_method]
      methods[klass] << [class_method, score]
      scores[klass] += score
      current += score
      break if max and current >= max
    end

    scores.sort_by { |_, n| -n }.each do |klass, total|
      io.puts
      io.puts "%8.1f: %s" % [total, "#{klass} total"]
      methods[klass].each do |name, score|
        location = @method_locations[name]
        if location then
          io.puts "%8.1f: %-32s %s" % [score, name, location]
        else
          io.puts "%8.1f: %s" % [score, name]
        end
      end
    end
  else
    io.puts
    calls.sort_by { |k,v| -my_totals[k] }.each do |class_method, call_list|
      current += output_method_details(io, class_method, call_list)
      break if max and current >= max
    end
  end
end
output_method_details(io, class_method, call_list) click to toggle source

Output the details for a method

# File lib/flog.rb, line 301
def output_method_details(io, class_method, call_list)
  return 0 if option[:methods] and class_method =~ /##{@@no_method}/

  total = totals[class_method]

  location = @method_locations[class_method]
  if location then # REFACTOR
    io.puts "%8.1f: %-32s %s" % [total, class_method, location]
  else
    io.puts "%8.1f: %s" % [total, class_method]
  end

  if option[:details] then
    call_list.sort_by { |k,v| -v }.each do |call, count|
      io.puts "  %6.1f:   %s" % [count, call]
    end
    io.puts
  end

  total
end
penalize_by(bonus) click to toggle source

For the duration of the block the complexity factor is increased by bonus This allows the complexity of sub-expressions to be influenced by the expressions in which they are found. Yields 42 to the supplied block.

# File lib/flog.rb, line 329
def penalize_by bonus
  @multiplier += bonus
  yield
  @multiplier -= bonus
end
process_alias(exp) click to toggle source

Process Methods:

# File lib/flog.rb, line 417
def process_alias(exp)
  process exp.shift
  process exp.shift
  add_to_score :alias
  s()
end
process_and(exp) click to toggle source
# File lib/flog.rb, line 424
def process_and(exp)
  add_to_score :branch
  penalize_by 0.1 do
    process exp.shift # lhs
    process exp.shift # rhs
  end
  s()
end
Also aliased as: process_or
process_attrasgn(exp) click to toggle source
# File lib/flog.rb, line 434
def process_attrasgn(exp)
  add_to_score :assignment
  process exp.shift # lhs
  exp.shift # name
  process exp.shift # rhs
  s()
end
process_block(exp) click to toggle source
# File lib/flog.rb, line 442
def process_block(exp)
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end
process_block_pass(exp) click to toggle source
# File lib/flog.rb, line 449
def process_block_pass(exp)
  arg = exp.shift

  add_to_score :block_pass

  case arg.first
  when :lvar, :dvar, :ivar, :cvar, :self, :const, :nil then
    # do nothing
  when :lit, :call then
    add_to_score :to_proc_normal
  when :iter, :dsym, :dstr, *BRANCHING then
    add_to_score :to_proc_icky!
  else
    raise({:block_pass_even_ickier! => [arg, call]}.inspect)
  end

  process arg

  s()
end
process_call(exp) click to toggle source
# File lib/flog.rb, line 470
def process_call(exp)
  penalize_by 0.2 do
    recv = process exp.shift
  end
  name = exp.shift
  penalize_by 0.2 do
    args = process exp.shift
  end

  add_to_score name, SCORES[name]

  s()
end
process_case(exp) click to toggle source
# File lib/flog.rb, line 484
def process_case(exp)
  add_to_score :branch
  process exp.shift # recv
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end
process_class(exp) click to toggle source
# File lib/flog.rb, line 493
def process_class(exp)
  in_klass exp.shift do
    penalize_by 1.0 do
      process exp.shift # superclass expression
    end
    process_until_empty exp
  end
  s()
end
process_dasgn_curr(exp) click to toggle source
# File lib/flog.rb, line 503
def process_dasgn_curr(exp) # FIX: remove
  add_to_score :assignment
  exp.shift # name
  process exp.shift # assigment, if any
  s()
end
Also aliased as: process_iasgn, process_lasgn
process_defn(exp) click to toggle source
# File lib/flog.rb, line 512
def process_defn(exp)
  in_method exp.shift, exp.file, exp.line do
    process_until_empty exp
  end
  s()
end
process_defs(exp) click to toggle source
# File lib/flog.rb, line 519
def process_defs(exp)
  recv = process exp.shift
  in_method "::#{exp.shift}", exp.file, exp.line do
    process_until_empty exp
  end
  s()
end
process_else(exp) click to toggle source

TODO: it’s not clear to me whether this can be generated at all.

# File lib/flog.rb, line 528
def process_else(exp)
  add_to_score :branch
  penalize_by 0.1 do
    process_until_empty exp
  end
  s()
end
Also aliased as: process_rescue, process_when
process_iasgn(exp) click to toggle source
Alias for: process_dasgn_curr
process_if(exp) click to toggle source
# File lib/flog.rb, line 538
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
process_iter(exp) click to toggle source
# File lib/flog.rb, line 548
def process_iter(exp)
  context = (self.context - [:class, :module, :scope])
  context = context.uniq.sort_by { |s| s.to_s }

  if context == [:block, :iter] or context == [:iter] then
    recv = exp.first

    # DSL w/ names. eg task :name do ... end
    if (recv[0] == :call and recv[1] == nil and recv.arglist[1] and
        [:lit, :str].include? recv.arglist[1][0]) then
      msg = recv[2]
      submsg = recv.arglist[1][1]
      in_klass msg do                           # :task
        in_method submsg, exp.file, exp.line do # :name
          process_until_empty exp
        end
      end
      return s()
    end
  end

  add_to_score :branch

  exp.delete 0 # TODO: what is this?

  process exp.shift # no penalty for LHS

  penalize_by 0.1 do
    process_until_empty exp
  end

  s()
end
process_lasgn(exp) click to toggle source
Alias for: process_dasgn_curr
process_lit(exp) click to toggle source
# File lib/flog.rb, line 582
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 then
    add_to_score :lit_fixnum
  when Float, Symbol, Regexp, Range then
    # do nothing
  else
    raise value.inspect
  end
  s()
end
process_masgn(exp) click to toggle source
# File lib/flog.rb, line 597
def process_masgn(exp)
  add_to_score :assignment
  process_until_empty exp
  s()
end
process_module(exp) click to toggle source
# File lib/flog.rb, line 603
def process_module(exp)
  in_klass exp.shift do
    process_until_empty exp
  end
  s()
end
process_or(exp) click to toggle source
Alias for: process_and
process_rescue(exp) click to toggle source
Alias for: process_else
process_sclass(exp) click to toggle source
# File lib/flog.rb, line 610
def process_sclass(exp)
  penalize_by 0.5 do
    recv = process exp.shift
    process_until_empty exp
  end

  add_to_score :sclass
  s()
end
process_super(exp) click to toggle source
# File lib/flog.rb, line 620
def process_super(exp)
  add_to_score :super
  process_until_empty exp
  s()
end
process_until(exp) click to toggle source
Alias for: process_while
process_until_empty(exp) click to toggle source

Process each element of exp in turn.

# File lib/flog.rb, line 338
def process_until_empty exp
  process exp.shift until exp.empty?
end
process_when(exp) click to toggle source
Alias for: process_else
process_while(exp) click to toggle source
# File lib/flog.rb, line 626
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
Also aliased as: process_until
process_yield(exp) click to toggle source
# File lib/flog.rb, line 637
def process_yield(exp)
  add_to_score :yield
  process_until_empty exp
  s()
end
report(io = $stdout) click to toggle source

Report results to io, STDOUT by default.

# File lib/flog.rb, line 345
def report(io = $stdout)
  io.puts "%8.1f: %s" % [total, "flog total"]
  io.puts "%8.1f: %s" % [average, "flog/method average"]

  return if option[:score]

  if option[:all] then
    output_details(io)
  else
    output_details(io, total * THRESHOLD)
  end
ensure
  self.reset
end
reset() click to toggle source

Reset score data

# File lib/flog.rb, line 363
def reset
  @totals     = @total_score = nil
  @multiplier = 1.0
  @calls      = Hash.new { |h,k| h[k] = Hash.new 0 }
end
score_method(tally) click to toggle source

Compute the distance formula for a given tally

# File lib/flog.rb, line 372
def score_method(tally)
  a, b, c = 0, 0, 0
  tally.each do |cat, score|
    case cat
    when :assignment then a += score
    when :branch     then b += score
    else                  c += score
    end
  end
  Math.sqrt(a*a + b*b + c*c)
end
signature() click to toggle source
# File lib/flog.rb, line 384
def signature
  "#{klass_name}#{method_name}"
end
total() click to toggle source
# File lib/flog.rb, line 388
def total # FIX: I hate this indirectness
  totals unless @total_score # calculates total_score as well

  @total_score
end
totals() click to toggle source

Return the total score and populates @totals.

# File lib/flog.rb, line 397
def totals
  unless @totals then
    @total_score = 0
    @totals = Hash.new(0)

    calls.each do |meth, tally|
      next if option[:methods] and meth =~ /##{@@no_method}$/
      score = score_method(tally)

      @totals[meth] = score
      @total_score += score
    end
  end

  @totals
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.