Skip to content

Instantly share code, notes, and snippets.

@thegedge
Created April 11, 2020 19:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thegedge/17b1888262a52170c26af026d43703d0 to your computer and use it in GitHub Desktop.
Save thegedge/17b1888262a52170c26af026d43703d0 to your computer and use it in GitHub Desktop.
A Ruby script to compute complexity/churn stats on a codebase
#!/usr/bin/env ruby
require "flog"
require "open3"
def capture_output(cmd, *args)
stdout, stderr, status = Open3.capture3(cmd, *args)
return stdout if status.success?
STDERR.puts("Failed to run git #{args.join(" ")}")
STDERR.puts(stderr)
''
end
def flog(file)
flog = Flog.new
flog.flog(file)
flog.total_score
end
def normalize(hash)
return hash if hash.empty?
min = hash.each_value.min
max = hash.each_value.max
range = [1, max - min].max
hash.transform_values { |v| (v - min) / range }
end
def complexity(files)
files.each_with_object({}) do |file, h|
h[file] = flog(file)
end
end
def churn
capture_output("git", "log", "--name-only", '--since="6 months ago"', '--format=')
.each_line(chomp: true)
.select { |name| name.end_with?('.rb') }
.reject { |name| name.end_with?('_test.rb') }
.select { |name| File.readable?(name) }
.tally
end
def filter_out_subdir(hash, subdir)
return hash unless subdir
hash.select { |file, _| file.start_with?("#{subdir}/") }
end
def complexity_and_churn(subdir)
churns = normalize(filter_out_subdir(churn, subdir))
complexities = normalize(complexity(churns.keys))
churns.map do |file, churn|
[0.5 * churn + 0.5 * complexities[file], file]
end
end
complexity_and_churn(ARGV.first).sort.reverse.take(10).each do |score, file|
puts "%.3f #{file}" % score
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment