rails_code_stats 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/MIT_LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Nathan Humbert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,6 @@
1
+ Rails Code Stats
2
+
3
+ Improved rails code statistics.
4
+
5
+
6
+ Copyright (c) 2009-2010 Nathan Humbert, released under the MIT license
@@ -0,0 +1,107 @@
1
+ class CodeStatistics #:nodoc:
2
+
3
+ TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests)
4
+
5
+ def initialize(*pairs)
6
+ @pairs = pairs
7
+ @statistics = calculate_statistics
8
+ @total = calculate_total if pairs.length > 1
9
+ end
10
+
11
+ def to_s
12
+ print_header
13
+ @pairs.each { |pair| print_line(pair.first, @statistics[pair.first]) }
14
+ print_splitter
15
+
16
+ if @total
17
+ print_line("Total", @total)
18
+ print_splitter
19
+ end
20
+
21
+ print_code_test_stats
22
+ end
23
+
24
+ private
25
+ def calculate_statistics
26
+ @pairs.inject({}) { |stats, pair| stats[pair.first] = calculate_directory_statistics(pair.last); stats }
27
+ end
28
+
29
+ def calculate_directory_statistics(directory, pattern = /.*\.rb$/)
30
+ stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
31
+
32
+ Dir.foreach(directory) do |file_name|
33
+ if File.stat(directory + "/" + file_name).directory? and (/^\./ !~ file_name)
34
+ newstats = calculate_directory_statistics(directory + "/" + file_name, pattern)
35
+ stats.each { |k, v| stats[k] += newstats[k] }
36
+ end
37
+
38
+ next unless file_name =~ pattern
39
+
40
+ f = File.open(directory + "/" + file_name)
41
+
42
+ while line = f.gets
43
+ stats["lines"] += 1
44
+ stats["classes"] += 1 if line =~ /class [A-Z]/
45
+ stats["methods"] += 1 if line =~ /def [a-z]/
46
+ stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
47
+ end
48
+ end
49
+
50
+ stats
51
+ end
52
+
53
+ def calculate_total
54
+ total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
55
+ @statistics.each_value { |pair| pair.each { |k, v| total[k] += v } }
56
+ total
57
+ end
58
+
59
+ def calculate_code
60
+ code_loc = 0
61
+ @statistics.each { |k, v| code_loc += v['codelines'] unless TEST_TYPES.include? k }
62
+ code_loc
63
+ end
64
+
65
+ def calculate_tests
66
+ test_loc = 0
67
+ @statistics.each { |k, v| test_loc += v['codelines'] if TEST_TYPES.include? k }
68
+ test_loc
69
+ end
70
+
71
+ def print_header
72
+ print_splitter
73
+ puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |"
74
+ print_splitter
75
+ end
76
+
77
+ def print_splitter
78
+ puts "+----------------------+-------+-------+---------+---------+-----+-------+"
79
+ end
80
+
81
+ def print_line(name, statistics)
82
+ m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0
83
+ loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0
84
+
85
+ start = if TEST_TYPES.include? name
86
+ "| #{name.ljust(20)} "
87
+ else
88
+ "| #{name.ljust(20)} "
89
+ end
90
+
91
+ puts start +
92
+ "| #{statistics["lines"].to_s.rjust(5)} " +
93
+ "| #{statistics["codelines"].to_s.rjust(5)} " +
94
+ "| #{statistics["classes"].to_s.rjust(7)} " +
95
+ "| #{statistics["methods"].to_s.rjust(7)} " +
96
+ "| #{m_over_c.to_s.rjust(3)} " +
97
+ "| #{loc_over_m.to_s.rjust(5)} |"
98
+ end
99
+
100
+ def print_code_test_stats
101
+ code = calculate_code
102
+ tests = calculate_tests
103
+
104
+ puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)}"
105
+ puts ""
106
+ end
107
+ end
@@ -0,0 +1,43 @@
1
+ class CommandLineMetricOutput
2
+ require 'rails_app_source_metrics'
3
+
4
+ def initialize
5
+ metrics = RailsAppSourceMetrics.new()
6
+ print_header
7
+ metrics.code_sections.each { |section| print_section(section) }
8
+ print_splitter
9
+ print_totals(metrics)
10
+ end
11
+
12
+
13
+ def print_splitter
14
+ puts "+----------------------+-------+-------+---------+---------+-----+-------+"
15
+ end
16
+
17
+ def print_header
18
+ print_splitter
19
+ puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |"
20
+ print_splitter
21
+ end
22
+
23
+ def print_section(section)
24
+ puts "| #{section[:name].ljust(20)} "+ print_stats(section[:stats])
25
+ end
26
+
27
+ def print_totals(metrics)
28
+ puts "| #{'Totals'.ljust(20)} "+ print_stats(metrics.totals)
29
+ print_splitter
30
+ puts " Code LOC: #{metrics.lines_of_code} Test LOC: #{metrics.lines_of_test_code} Code to Test Ratio: 1:#{sprintf("%.1f", metrics.code_to_test_ratio)}"
31
+ puts ""
32
+ end
33
+
34
+ def print_stats(stats)
35
+ "| #{stats[:lines].to_s.rjust(5)} " +
36
+ "| #{stats[:lines_of_code].to_s.rjust(5)} " +
37
+ "| #{stats[:classes].to_s.rjust(7)} " +
38
+ "| #{stats[:methods].to_s.rjust(7)} " +
39
+ "| #{stats[:methods_per_class].to_s.rjust(3)} " +
40
+ "| #{stats[:lines_of_code_per_method].to_s.rjust(5)} |"
41
+ end
42
+
43
+ end
@@ -0,0 +1,72 @@
1
+ class DirectorySourceStats
2
+ def initialize(directory)
3
+ @stats = { :lines => 0, :lines_of_code => 0, :classes => 0, :methods => 0 }
4
+ if File.exists?(directory)
5
+ calculate_directory_statistics(directory)
6
+ end
7
+ end
8
+
9
+ def get_stats
10
+ return @stats
11
+ end
12
+
13
+ private
14
+
15
+ def calculate_directory_statistics(directory)
16
+ Dir.foreach(directory) do |file_name|
17
+ path = directory + "/" + file_name
18
+ if subdirectory?(path) and !hidden?(file_name)
19
+ calculate_directory_statistics(path)
20
+ elsif source_file?(file_name)
21
+ process_source_file(path)
22
+ end
23
+ end
24
+ end
25
+
26
+ def source_file?(file_name)
27
+ file_name =~ /.*\.rb$/
28
+ end
29
+
30
+ def hidden?( file_name )
31
+ file_name =~ /^\./
32
+ end
33
+
34
+ def subdirectory?(path)
35
+ File.stat(path).directory?
36
+ end
37
+
38
+ def process_source_file(path)
39
+ file_handle = File.open(path)
40
+ parse_source_file(file_handle)
41
+ file_handle.close
42
+ end
43
+
44
+ def parse_source_file(file_handle)
45
+ while line = file_handle.gets
46
+ @stats[:lines] += 1
47
+ @stats[:classes] += 1 if class_definition?(line)
48
+ @stats[:methods] += 1 if method_call?(line)
49
+ @stats[:lines_of_code] += 1 unless whitespace_or_comment?(line)
50
+ end
51
+ end
52
+
53
+ def class_definition?(line)
54
+ return line =~ /^\s*class [A-Z]/
55
+ end
56
+
57
+ def method_call?(line)
58
+ return ( line =~ /def [a-z]/ or shoulda_method_call?(line) or test_unit_method_call?(line) )
59
+ end
60
+
61
+ def shoulda_method_call?(line)
62
+ return line =~ /^\s*should\s+(".*"|'.*')\s+do\s*$/
63
+ end
64
+
65
+ def test_unit_method_call?(line)
66
+ return line =~ /^\s*test\s+(".*"|'.*')\s+do\s*$/
67
+ end
68
+
69
+ def whitespace_or_comment?(line)
70
+ return line =~ /^\s*$/ || line =~ /^\s*#/
71
+ end
72
+ end
@@ -0,0 +1,74 @@
1
+ class RailsAppSourceMetrics
2
+ require 'directory_source_stats'
3
+
4
+ CODE_SECTIONS = [
5
+ { :name => "Controllers", :directory => "app/controllers/" },
6
+ { :name => "Helpers", :directory => "app/helpers/" },
7
+ { :name => "Models", :directory => "app/models/" },
8
+ { :name => "Libraries", :directory => "lib/" },
9
+ { :name => "Integration Tests", :directory => "test/integration/" },
10
+ { :name => "Functional Tests", :directory => "test/functional/" },
11
+ { :name => "Unit Tests", :directory => "test/unit/" }
12
+ ]
13
+
14
+ attr_reader :code_sections, :totals, :lines_of_code, :lines_of_test_code, :code_to_test_ratio
15
+
16
+ def initialize
17
+ @code_sections = CODE_SECTIONS
18
+ @code_sections.each do |section|
19
+ source_stats = DirectorySourceStats.new(File.join(Rails.root, section[:directory]))
20
+ section[:stats] = add_metrics_to_stats(source_stats.get_stats)
21
+ end
22
+ calculate_totals
23
+ end
24
+
25
+
26
+ private
27
+ def add_metrics_to_stats(stats)
28
+ stats = methods_per_class(stats)
29
+ stats = lines_of_code_per_method(stats)
30
+ return stats
31
+ end
32
+
33
+ def methods_per_class(stats)
34
+ begin
35
+ stats[:methods_per_class] = (stats[:methods] / stats[:classes])
36
+ rescue
37
+ stats[:methods_per_class] = 0
38
+ end
39
+ return stats
40
+ end
41
+
42
+ def lines_of_code_per_method(stats)
43
+ begin
44
+ stats[:lines_of_code_per_method] = (stats[:lines_of_code] / stats[:methods])
45
+ rescue
46
+ stats[:lines_of_code_per_method] = 0
47
+ end
48
+ return stats
49
+ end
50
+
51
+ def calculate_totals
52
+ @lines_of_code = code_totals[:lines_of_code]
53
+ @lines_of_test_code = test_totals[:lines_of_code]
54
+ @code_to_test_ratio = @lines_of_test_code / @lines_of_code.to_f
55
+ @totals = base_totals(@code_sections)
56
+ end
57
+
58
+ def test_totals
59
+ test_code_sections = @code_sections.select { |section| section[:directory] =~ /^test/ }
60
+ return base_totals(test_code_sections)
61
+ end
62
+
63
+ def code_totals
64
+ test_code_sections = @code_sections.select { |section| section[:directory] !~ /^test/ }
65
+ return base_totals(test_code_sections)
66
+ end
67
+
68
+ def base_totals(code_sections)
69
+ total = { :lines => 0, :lines_of_code => 0, :classes => 0, :methods => 0 }
70
+ code_sections.each { |section| section[:stats].each { |k, v| total[k] += v if total.has_key?(k) } }
71
+ return add_metrics_to_stats(total)
72
+ end
73
+
74
+ end
@@ -0,0 +1,12 @@
1
+ require 'rails_code_stats'
2
+ require 'rails'
3
+ module RailsCodeStats
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :rails_code_stats
6
+
7
+ rake_tasks do
8
+ load "tasks/rails_code_stats.rake"
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module RailsCodeStats
2
+
3
+ require 'rails_code_stats/railtie' if defined?(Rails)
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ desc "Rails application code statistics"
2
+ task(:rails_code_stats) do
3
+ require 'command_line_metric_output'
4
+ CommandLineMetricOutput.new()
5
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_code_stats
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Nathan Humbert
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-17 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: This gem calculates basic code statistics on a rails app
22
+ email:
23
+ - nathan.humbert+rcs@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - README
32
+ - MIT_LICENSE
33
+ - lib/tasks/rails_code_stats.rake
34
+ - lib/rails_code_stats/railtie.rb
35
+ - lib/rails_code_stats.rb
36
+ - lib/code_statistics.rb
37
+ - lib/command_line_metric_output.rb
38
+ - lib/directory_source_stats.rb
39
+ - lib/rails_app_source_metrics.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/nathanhumbert/rails_code_stats
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.6
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Rails Code Stats
70
+ test_files: []
71
+