Skip to content

Instantly share code, notes, and snippets.

@michaelfeathers
Created April 9, 2011 13:28
Show Gist options
  • Save michaelfeathers/911400 to your computer and use it in GitHub Desktop.
Save michaelfeathers/911400 to your computer and use it in GitHub Desktop.
Print the complexities of all methods over time for a class in a git repo.
#!/usr/bin/env ruby
# Print the complexities of all methods over time
# for a class in a git repo.
#
# Requires: bash
# >= git 1.7.1
# >= ruby 1.9.2
# >= flog 2.5.0
require 'stringio'
require 'flog'
require 'time'
class CodeEvent
def initialize(hash = {})
hash.each_pair do |attr,value|
define_attribute(attr)
self.send(writer_for(attr), value)
end
end
def file_name
return nil unless instance_variable_get(:@location)
location.split(/\//).select {|seg| seg.end_with?('.rb') }.first
end
def class_name
return nil unless instance_variable_get(:@location)
segs = location.split(/\//)
class_index = segs.find_index {|seg| seg.end_with?('.rb')}
return nil unless class_index
segs[class_index + 1]
end
def method_name
return nil unless instance_variable_get(:@location)
location.split(/\//).last
end
private
def define_attribute(attr)
singleton_class.send(:public)
singleton_class.send(:attr_accessor, attr)
end
def singleton_class
class << self; self; end
end
def reader_for(sym)
sym.to_s.end_with?('=') ? sym.to_s.chop.to_sym : sym
end
def writer_for(sym)
(sym.to_s + "=").to_sym
end
def method_missing(sym, *args, &block)
if sym.to_s.end_with?('=')
define_attribute(reader_for(sym))
self.send(sym,*args)
else
super
end
end
end
TEMP_FILE = "/tmp/class_trend_junk.rb"
class ComplexityGrabber < StringIO
def initialize(class_name)
super()
@class_name = class_name
end
def class_complexities
complexities = []
string.each_line do |line|
match_data = line.match /(\d+\.\d+):\s#{@class_name}*#(\w+)/
next unless match_data
complexity, method_name = match_data.captures
complexities << CodeEvent.new(:method_name => method_name, :complexity => complexity.to_f)
end
complexities
end
end
class ClassTrend
def self.trails_for(filename, classname)
new(filename, classname)
.generate_datestamped_complexities.sort { |left,right| left.date <=> right.date }
.select { |o| o.method_name != "none" }
end
def initialize(filename, classname)
@filename = filename
@classname = classname
end
def generate_datestamped_complexities
complexities = []
sha1s_of_commits.each do |sha1|
method_events = complexity_events_of(sha1)
method_events.each do |event|
event.date = commit_date_of(sha1)
complexities << event
end
end
complexities
end
def commit_date_of(sha1)
Time.parse(`git show -s --format=%cd #{sha1}`)
end
def complexity_events_of(sha1)
`git show #{sha1}:#{@filename} > #{TEMP_FILE}`
flogger = Flog.new({ :all => true })
grabber = ComplexityGrabber.new(@classname)
flogger.flog(TEMP_FILE)
flogger.report(grabber)
$stderr.print '.'
grabber.class_complexities
end
def sha1s_of_commits
@sha1s ||= `git log #{@filename}| grep ^commit`.split(/\n/).map(&:split).map {|fields| fields[sha1_column = 1] }
end
end
if ARGV.size != 2
puts "help: classtrend <rubyfilename> <classname>"
puts ""
puts " Run in a git repository folder"
puts ""
puts " Ex. classtrend app/models/flubber.rb Flubber"
puts ""
exit
end
class TrailRenderer
def initialize(trails)
@method_groups = trails.group_by(&:method_name)
@date_groups = trails.group_by(&:date)
@method_names = @method_groups.map {|x| x[0] }
end
def as_csv
header_line + body
end
private
def header_line
"," + (@method_names.map {|name| name + "," }).inject(:+) + "\n"
end
def body
@date_groups.map { |date,array| "#{date.strftime('%D')},#{method_complexities(array)}\n" }.inject(:+)
end
def method_complexities(array)
@method_names.map {|name| complexity_for_method(name,array) + ","}.inject(:+)
end
def complexity_for_method(method_name, array)
matches = array.select { |e| e.method_name == method_name }
matches.length > 0 ? matches.first.complexity.to_s: "0.0"
end
end
trails = ClassTrend.trails_for(ARGV[0], ARGV[1])
print TrailRenderer.new(trails).as_csv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment