Friday, October 23, 2009

Recipe 19.5. Gathering Statistics About Your Code










Recipe 19.5. Gathering Statistics About Your Code




Credit: Stefan Lang



Problem


You want to gather statistics about your Ruby project, like the total number of lines of code.




Solution


Here's a class that parses Ruby source files and gathers statistics. Put this in scriptlines.rb in your project's top-level directory.



# scriptlines.rb
# A ScriptLines instance analyses a Ruby script and maintains
# counters for the total number of lines, lines of
code, etc.
class ScriptLines

attr_reader :name
attr_accessor :bytes, :lines, :lines_of_code, :comment_lines

LINE_FORMAT = '%8s %8s %8s %8s %s'

def self.headline
sprintf LINE_FORMAT, "BYTES", "LINES", "LOC", "COMMENT", "FILE"
end

# The 'name' argument is usually a filename
def initialize(name)
@name = name
@bytes = 0
@lines = 0 # total number of lines
@lines_of_code = 0
@comment_lines = 0
end

# Iterates over all the lines in io (io might be a file or a
# string), analyses them and appropriately increases the counter
# attributes.
def read(io)
in_multiline_comment = false
io.each { |line|
@lines += 1
@bytes += line.size
case line
when /^=begin(\s|$)/
in_multiline_comment = true
@comment_lines += 1
when /^=end(\s|$)/:
@comment_lines += 1
in_multiline_comment = false
when /^\s*#/
@comment_lines += 1
when /^\s*$/
# empty/whitespace only line
else
if in_multiline_comment
@comment_lines += 1
else
@lines_of_code += 1
end
end
}
end

# Get a new ScriptLines instance whose counters hold the
# sum of self and other.
def +(other)
sum = self.dup
sum.bytes += other.bytes
sum.lines += other.lines
sum.lines_of_
code += other.lines_of_code
sum.comment_lines += other.comment_lines
sum
end

# Get a formatted string containing all counter numbers and the
# name of this instance.
def to_s
sprintf LINE_FORMAT,
@bytes, @lines, @lines_of_code, @comment_lines, @name
end
end



To tie the class into your build system, give your Rakefile a stats task like the following. This task assumes that the Rakefile and scriptlines.rb are in the same directory:



task 'stats' do
require 'scriptlines'

files = FileList['lib/**/*.rb']

puts ScriptLines.headline
sum = ScriptLines.new("TOTAL (#{files.size} file(s))")

# Print stats for each file.
files.each do |fn|
File.open(fn) do |file|
script_lines = ScriptLines.new(fn)
script_lines.read(file)
sum += script_lines
puts script_lines
end
end

# Print total stats.
puts sum
end





Discussion


ScriptLines performs a very basic parsing of Ruby code: it divides a source file into blank lines, comment lines, and lines containing Ruby code. If you want more detailed information, you can include each file and get more information about the defined classes and methods with reflection or an extension like Parse Tree.


Invoke the stats task to run all the Ruby scripts beneath your lib/ directory through ScriptLines. The following example output is for the highline library:



$ rake stats
(in /usr/local/lib/ruby/gems/1.8/gems/highline-1.0.1)
BYTES LINES LOC COMMENT FILE
18626 617 360 196 lib/highline.rb
12745 375 168 181 lib/highline/menu.rb
15760 430 181 227 lib/highline/question.rb
801 25 7 14 lib/highline/import.rb
47932 1447 716 618 TOTAL (4 scripts)



BYTES is the file size in bytes, LINES the number of total lines in each file, LOC stands for "Lines Of
Code," and COMMENT is the number of comment-only lines.


These simple metrics are good for gauging the complexity of a project, but don't use them as a measure of day-to-day progress. Complexity is not the same as progress, and a good day's work might consist of replacing a hundred lines of code with ten.




See Also


  • ri Kernel#sprintf

  • The RDoc documentation for Rake's FileList class (http://rake.rubyforge.org/classes/Rake/FileList.html)

  • The ParseTree extension (http://rubyforge.org/projects/parsetree/)













No comments:

Post a Comment