Skip to content

Commit 527c276

Browse files
committedNov 4, 2014
Merge pull request #1 from bleonard/introspect
More detailed introspection of app
2 parents 2e0dde7 + 4104e16 commit 527c276

14 files changed

+1083
-54
lines changed
 

‎Gemfile

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
22

33
# Specify your gem's dependencies in rails_stats.gemspec
44
gemspec
5+
6+
gem 'debugger'

‎README.md

+20-9
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,34 @@ See stuff about a Rails app.
55
```bash
66
$ bundle exec thor stats:calculate ../../path/to/app/
77

8-
Directory: ../../path/to/app/
8+
Directory: ~/path/to/app/
99

1010
+----------------------+-------+-------+---------+---------+-----+-------+
1111
| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |
1212
+----------------------+-------+-------+---------+---------+-----+-------+
1313
| Controllers | 1848 | 1483 | 32 | 174 | 5 | 6 |
1414
| Helpers | 2257 | 1892 | 45 | 245 | 5 | 5 |
15+
| Jobs | 399 | 295 | 11 | 33 | 3 | 6 |
1516
| Models | 4584 | 3509 | 61 | 526 | 8 | 4 |
1617
| Observers | 42 | 22 | 2 | 5 | 2 | 2 |
1718
| Libraries | 2987 | 2272 | 30 | 287 | 9 | 5 |
18-
| Helper tests | 124 | 107 | 3 | 16 | 5 | 4 |
19-
| Model tests | 3397 | 2522 | 0 | 18 | 0 | 138 |
20-
| Integration tests | 91 | 73 | 0 | 1 | 0 | 71 |
21-
| Library tests | 856 | 721 | 0 | 2 | 0 | 358 |
22-
| Cucumber tests | 3450 | 3008 | 29 | 146 | 5 | 18 |
19+
| Configuration | 1233 | 669 | 4 | 17 | 4 | 37 |
20+
| Spec Support | 1416 | 1152 | 4 | 30 | 7 | 36 |
21+
| Integration Tests | 91 | 73 | 0 | 1 | 0 | 71 |
22+
| Lib Tests | 101 | 83 | 0 | 1 | 0 | 81 |
23+
| Model Tests | 3397 | 2522 | 0 | 18 | 0 | 138 |
24+
| Cucumber Support | 739 | 521 | 0 | 1 | 0 | 519 |
25+
| Cucumber Features | 2711 | 2487 | 29 | 145 | 5 | 15 |
2326
+----------------------+-------+-------+---------+---------+-----+-------+
24-
| Total | 19636 | 15609 | 202 | 1420 | 7 | 8 |
27+
| Total | 21805 | 16980 | 218 | 1483 | 6 | 9 |
2528
+----------------------+-------+-------+---------+---------+-----+-------+
26-
Code LOC: 9156 Test LOC: 3944 Code to Test Ratio: 1:0.4
27-
```
29+
Code LOC: 10142 Test LOC: 6838 Code to Test Ratio: 1:0.7
30+
31+
```
32+
33+
### TODO
34+
35+
* option to print out by app directory (stats per engine)
36+
* Add views (jbuilder, erb, haml) but don't count towards ratios
37+
* Support JS for projects that have it in public
38+
* Add css but don't count towards ratios

‎lib/rails_stats.rb

+12
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,17 @@ module RailsStats
44

55
end
66

7+
require 'debugger'
8+
9+
require 'rails_stats/inflector'
10+
require 'rails_stats/code_statistics_calculator'
11+
require 'rails_stats/util'
12+
require 'rails_stats/app_statistics'
13+
require 'rails_stats/spec_statistics'
14+
require 'rails_stats/cucumber_statistics'
15+
require 'rails_stats/root_statistics'
16+
require 'rails_stats/gem_statistics'
17+
require 'rails_stats/code_statistics'
18+
719
require "rails_stats/rake"
820
RailsStats.extend RailsStats::Rake

‎lib/rails_stats/app_statistics.rb

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
module RailsStats
2+
class AppStatistics
3+
attr_reader :statistics, :total, :test
4+
5+
def initialize(directory)
6+
@test = false
7+
@directory = directory
8+
@statistics = calculate_statistics
9+
@total = calculate_total
10+
end
11+
12+
def key_concepts
13+
directories.collect{ |path| File.basename(path) }
14+
end
15+
16+
private
17+
18+
def calculate_total
19+
out = CodeStatisticsCalculator.new
20+
@statistics.each do |key, stats|
21+
out.add(stats)
22+
end
23+
out
24+
end
25+
26+
def calculate_statistics
27+
Util.calculate_statistics(directories)
28+
end
29+
30+
def directories
31+
return @directories if @directories
32+
out = []
33+
Dir.foreach(@directory) do |file_name|
34+
path = File.join(@directory, file_name)
35+
next unless File.directory?(path)
36+
next if (/^\./ =~ file_name)
37+
next if file_name == "assets" # doing separately
38+
next if file_name == "views" # TODO
39+
out << path
40+
end
41+
42+
assets = File.join(@directory, "assets")
43+
if File.directory?(assets)
44+
Dir.foreach(assets) do |file_name|
45+
path = File.join(assets, file_name)
46+
next unless File.directory?(path)
47+
next if (/^\./ =~ file_name)
48+
49+
case file_name
50+
when "javascripts"
51+
out << path
52+
# TODO when "css"
53+
end
54+
end
55+
end
56+
57+
out
58+
end
59+
end
60+
61+
end

‎lib/rails_stats/code_statistics.rb

+84-34
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,26 @@
11
# railties/lib/rails/code_statistics.rb
22

3-
require 'rails_stats/code_statistics_calculator'
4-
53
module RailsStats
6-
class CodeStatistics #:nodoc:
7-
8-
TEST_TYPES = ['Controller tests',
9-
'Helper tests',
10-
'Model tests',
11-
'Mailer tests',
12-
'Integration tests',
13-
'Request tests',
14-
'Library tests',
15-
'Cucumber tests',
16-
'Functional tests (old)',
17-
'Unit tests (old)']
18-
19-
def initialize(*pairs)
20-
@pairs = pairs
21-
@statistics = calculate_statistics
22-
@total = calculate_total if pairs.length > 1
4+
class CodeStatistics
5+
6+
RAILS_APP_FOLDERS = ['models',
7+
'controllers',
8+
'helpers',
9+
'mailers',
10+
'views',
11+
'assets']
12+
13+
def initialize(root_directory)
14+
@root_directory = root_directory
15+
@key_concepts = calculate_key_concepts
16+
@projects = calculate_projects
17+
@statistics = calculate_statistics
18+
@total = calculate_total
2319
end
2420

2521
def to_s
2622
print_header
27-
@pairs.each { |pair| print_line(pair.first, @statistics[pair.first]) }
23+
@statistics.each { |key, stats| print_line(key, stats) }
2824
print_splitter
2925

3026
if @total
@@ -36,26 +32,80 @@ def to_s
3632
end
3733

3834
private
39-
def calculate_statistics
40-
Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}]
35+
def calculate_key_concepts
36+
# returns names of main things like models, controllers, services, etc
37+
concepts = {}
38+
app_projects.each do |project|
39+
project.key_concepts.each do |key|
40+
concepts[key] = true
41+
end
42+
end
43+
44+
# TODO: maybe gem names?
45+
46+
concepts.keys
4147
end
4248

43-
def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|feature)$/)
44-
stats = CodeStatisticsCalculator.new
49+
def calculate_projects
50+
out = []
51+
out += app_projects
52+
out += calculate_root_projects
53+
out += calculate_gem_projects
54+
out += calculate_spec_projects
55+
out += calculate_test_projects
56+
out += calculate_cucumber_projects
57+
out
58+
end
4559

46-
Dir.foreach(directory) do |file_name|
47-
path = "#{directory}/#{file_name}"
60+
def app_projects
61+
@app_projects ||= calculate_app_projects
62+
end
4863

49-
if File.directory?(path) && (/^\./ !~ file_name)
50-
stats.add(calculate_directory_statistics(path, pattern))
51-
end
64+
def calculate_app_projects
65+
apps = Util.calculate_projects(@root_directory, "**", "app", RAILS_APP_FOLDERS)
66+
apps.collect do |root_path|
67+
AppStatistics.new(root_path)
68+
end
69+
end
70+
71+
def calculate_gem_projects
72+
gems = Util.calculate_projects(@root_directory, "**", "*.gemspec")
73+
gems.collect do |root_path|
74+
GemStatistics.new(root_path)
75+
end
76+
end
77+
78+
def calculate_spec_projects
79+
specs = Util.calculate_projects(@root_directory, "**", "spec", "spec_helper.rb")
80+
specs.collect do |root_path|
81+
SpecStatistics.new(root_path, @key_concepts)
82+
end
83+
end
84+
85+
def calculate_test_projects
86+
[] # TODO: test unit
87+
end
5288

53-
next unless file_name =~ pattern
89+
def calculate_root_projects
90+
[RootStatistics.new(@root_directory)]
91+
end
5492

55-
stats.add_by_file_path(path)
93+
def calculate_cucumber_projects
94+
cukes = Util.calculate_projects(@root_directory, "**", "*.feature")
95+
cukes.collect do |root_path|
96+
CucumberStatistics.new(root_path)
5697
end
98+
end
5799

58-
stats
100+
def calculate_statistics
101+
out = {}
102+
@projects.each do |project|
103+
project.statistics.each do |key, stats|
104+
out[key] ||= CodeStatisticsCalculator.new(project.test)
105+
out[key].add(stats)
106+
end
107+
end
108+
out
59109
end
60110

61111
def calculate_total
@@ -66,13 +116,13 @@ def calculate_total
66116

67117
def calculate_code
68118
code_loc = 0
69-
@statistics.each { |k, v| code_loc += v.code_lines unless TEST_TYPES.include? k }
119+
@statistics.each { |k, v| code_loc += v.code_lines unless v.test }
70120
code_loc
71121
end
72122

73123
def calculate_tests
74124
test_loc = 0
75-
@statistics.each { |k, v| test_loc += v.code_lines if TEST_TYPES.include? k }
125+
@statistics.each { |k, v| test_loc += v.code_lines if v.test }
76126
test_loc
77127
end
78128

‎lib/rails_stats/code_statistics_calculator.rb

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module RailsStats
44
class CodeStatisticsCalculator #:nodoc:
5-
attr_reader :lines, :code_lines, :classes, :methods
5+
attr_reader :lines, :code_lines, :classes, :methods, :test
66

77
PATTERNS = {
88
rb: {
@@ -31,11 +31,12 @@ class CodeStatisticsCalculator #:nodoc:
3131
}
3232
}
3333

34-
def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0)
35-
@lines = lines
36-
@code_lines = code_lines
37-
@classes = classes
38-
@methods = methods
34+
def initialize(test=false)
35+
@test = test
36+
@lines = 0
37+
@code_lines = 0
38+
@classes = 0
39+
@methods = 0
3940
end
4041

4142
def add(code_statistics_calculator)
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module RailsStats
2+
class CucumberStatistics
3+
attr_reader :statistics, :total, :test
4+
5+
def initialize(directory)
6+
@test = true
7+
@directory = directory
8+
@statistics = calculate_statistics
9+
@total = calculate_total
10+
end
11+
12+
private
13+
14+
def calculate_total
15+
out = CodeStatisticsCalculator.new(true)
16+
@statistics.each do |key, stats|
17+
out.add(stats)
18+
end
19+
out
20+
end
21+
22+
def calculate_statistics
23+
out = {}
24+
categorize_files.each do |key, list|
25+
out[key] = Util.calculate_file_statistics(list)
26+
end
27+
out
28+
end
29+
30+
def categorize_files
31+
out = {}
32+
Dir[File.join(@directory, "**", "*.rb")].each do |file_path|
33+
out["Cucumber Support"] ||= []
34+
out["Cucumber Support"] << file_path
35+
end
36+
37+
Dir[File.join(@directory, "**", "*.feature")].each do |file_path|
38+
out["Cucumber Features"] ||= []
39+
out["Cucumber Features"] << file_path
40+
end
41+
42+
out
43+
end
44+
end
45+
46+
end

‎lib/rails_stats/gem_statistics.rb

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module RailsStats
2+
class GemStatistics
3+
attr_reader :statistics, :total, :test
4+
5+
def initialize(directory)
6+
@test = false
7+
@directory = directory
8+
@statistics = calculate_statistics
9+
@total = calculate_total
10+
end
11+
12+
private
13+
14+
def calculate_total
15+
out = CodeStatisticsCalculator.new
16+
@statistics.each do |key, stats|
17+
out.add(stats)
18+
end
19+
out
20+
end
21+
22+
def calculate_statistics
23+
# ignore gem/app so as to not double-count engines
24+
lib = File.join(@directory, "lib")
25+
Util.calculate_statistics([lib]) do |path|
26+
"Gems"
27+
end
28+
end
29+
end
30+
31+
end

‎lib/rails_stats/inflector.rb

+605
Large diffs are not rendered by default.

‎lib/rails_stats/rake.rb

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# railties/lib/rails/tasks/statistics.rake
22

3-
require 'rails_stats/code_statistics'
4-
53
module RailsStats
64
module Rake
75
STATS_DIRECTORIES = [
@@ -33,8 +31,7 @@ module Rake
3331

3432
def calculate(root_directory)
3533
puts "\nDirectory: #{root_directory}\n\n"
36-
stats_dirs = STATS_DIRECTORIES.collect { |name, dir| [ name, "#{root_directory}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
37-
CodeStatistics.new(*stats_dirs).to_s
34+
CodeStatistics.new(root_directory).to_s
3835
end
3936
end
4037
end

‎lib/rails_stats/root_statistics.rb

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module RailsStats
2+
class RootStatistics
3+
attr_reader :statistics, :total, :test
4+
5+
ROOT_FOLDERS = {
6+
"lib" => "Libraries",
7+
"config" => "Configuration"
8+
}
9+
10+
def initialize(directory)
11+
@test = false
12+
@directory = directory
13+
@statistics = calculate_statistics
14+
@total = calculate_total
15+
end
16+
17+
private
18+
19+
def calculate_total
20+
out = CodeStatisticsCalculator.new
21+
@statistics.each do |key, stats|
22+
out.add(stats)
23+
end
24+
out
25+
end
26+
27+
def calculate_statistics
28+
Util.calculate_statistics(directories) do |folder|
29+
ROOT_FOLDERS[File.basename(folder)]
30+
end
31+
end
32+
33+
def directories
34+
out = []
35+
ROOT_FOLDERS.each do |folder, name|
36+
out << File.join(@directory, folder)
37+
end
38+
out
39+
end
40+
end
41+
42+
end

‎lib/rails_stats/spec_statistics.rb

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
module RailsStats
2+
class SpecStatistics
3+
attr_reader :statistics, :total, :test
4+
5+
SPEC_FOLDERS = ['controllers',
6+
'features',
7+
'helpers',
8+
'models',
9+
'requests',
10+
'routing',
11+
'integrations',
12+
'integration',
13+
'mailers',
14+
'lib']
15+
16+
def initialize(directory, key_concepts)
17+
@test = true
18+
@directory = directory
19+
@key_concepts = key_concepts
20+
@statistics = calculate_statistics
21+
@total = calculate_total
22+
end
23+
24+
private
25+
26+
def calculate_total
27+
out = CodeStatisticsCalculator.new(true)
28+
@statistics.each do |key, stats|
29+
out.add(stats)
30+
end
31+
out
32+
end
33+
34+
def calculate_statistics
35+
out = {}
36+
categorize_files.each do |key, list|
37+
out[key] = Util.calculate_file_statistics(list)
38+
end
39+
out
40+
end
41+
42+
def categorize_files
43+
out = {}
44+
Dir[File.join(@directory, "**", "*.rb")].each do |file_path|
45+
if file_path =~ /.*_spec.rb$/
46+
key = categorize_file(file_path)
47+
else
48+
key = "Spec Support"
49+
end
50+
51+
out[key] ||= []
52+
out[key] << file_path
53+
end
54+
55+
out
56+
end
57+
58+
def categorize_file(file_path)
59+
types = (@key_concepts + SPEC_FOLDERS).uniq
60+
types.each do |folder|
61+
if file_path =~ /\/#{folder}\//
62+
folder = Inflector.humanize(folder)
63+
folder = Inflector.titleize(folder)
64+
folder = Inflector.singularize(folder)
65+
return "#{folder} Tests"
66+
end
67+
end
68+
69+
# something else
70+
return "Other Tests"
71+
end
72+
end
73+
74+
end

‎lib/rails_stats/util.rb

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
module RailsStats
2+
module Util
3+
extend self
4+
5+
def calculate_projects(*args)
6+
projects = {}
7+
8+
children = [args.pop].flatten
9+
parent = args.flatten
10+
children.each do |dirname|
11+
child_folder_pattern = File.join(*(parent.dup + [dirname]))
12+
Dir[child_folder_pattern].each do |marker_path|
13+
next if marker_path =~ /\/vendor\//
14+
15+
projects[File.absolute_path(File.dirname(marker_path))] = true
16+
end
17+
end
18+
19+
out = projects.keys
20+
21+
# TODO: make sure none are children of other ones in there
22+
out
23+
end
24+
25+
def calculate_file_statistics(file, type = :code, &block)
26+
stats = CodeStatisticsCalculator.new
27+
28+
files = [file].flatten
29+
30+
files.each do |path|
31+
stats.add_by_file_path(path)
32+
end
33+
34+
stats
35+
end
36+
37+
def calculate_statistics(directories, type = :code, &block)
38+
out = {}
39+
40+
directories = [directories].flatten
41+
is_test = (type.to_s == "test")
42+
43+
directories.each do |dir_path|
44+
next unless File.directory?(dir_path)
45+
46+
key = nil
47+
if block_given?
48+
key = yield(dir_path)
49+
end
50+
51+
key = path_to_name(dir_path, is_test) if !key || key.length == 0
52+
53+
out[key] ||= CodeStatisticsCalculator.new(is_test)
54+
out[key].add(calculate_directory_statistics(dir_path))
55+
end
56+
57+
out.keys.each do |key|
58+
out.delete(key) if out[key].lines == 0
59+
end
60+
61+
out
62+
end
63+
64+
def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|feature)$/)
65+
stats = CodeStatisticsCalculator.new
66+
67+
Dir.foreach(directory) do |file_name|
68+
path = "#{directory}/#{file_name}"
69+
70+
if File.directory?(path) && (/^\./ !~ file_name)
71+
stats.add(calculate_directory_statistics(path, pattern))
72+
end
73+
74+
next unless file_name =~ pattern
75+
76+
stats.add_by_file_path(path)
77+
end
78+
79+
stats
80+
end
81+
82+
def path_to_name(path, is_test)
83+
folder = File.basename(path)
84+
folder = Inflector.humanize(folder)
85+
folder = Inflector.titleize(folder)
86+
87+
if is_test
88+
folder = Inflector.singularize(folder)
89+
folder = "#{folder} Tests"
90+
else
91+
folder = Inflector.pluralize(folder)
92+
end
93+
94+
folder
95+
end
96+
end
97+
end

‎lib/rails_stats/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module RailsStats
2-
VERSION = "0.0.3"
2+
VERSION = "0.0.4"
33
end

0 commit comments

Comments
 (0)
Please sign in to comment.