Class: Flog

### Home Classes Methods

### In Files

- lib/flog.rb

### Parent

SexpProcessor

### Methods

- ::expand_dirs_to_files
- ::new
- ::parse_options
- #add_to_score
- #average
- #flog
- #in_klass
- #in_method
- #klass_name
- #method_name
- #output_details
- #output_method_details
- #penalize_by
- #process_alias
- #process_and
- #process_attrasgn
- #process_block
- #process_block_pass
- #process_call
- #process_case
- #process_class
- #process_dasgn_curr
- #process_defn
- #process_defs
- #process_else
- #process_iasgn
- #process_if
- #process_iter
- #process_lasgn
- #process_lit
- #process_masgn
- #process_module
- #process_or
- #process_rescue
- #process_sclass
- #process_super
- #process_until
- #process_until_empty
- #process_when
- #process_while
- #process_yield
- #report
- #reset
- #score_method
- #signature
- #total
- #totals

### Files

- History.txt
- Manifest.txt
- README.txt

### Class/Module Index

Quicksearch

- Flog
- FlogGauntlet
- FlogTask

No matching classes.

# 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.