rails_stats 2.0.1 → 2.2.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +23 -15
- data/CHANGELOG.md +18 -3
- data/Gemfile +2 -2
- data/README.md +224 -232
- data/bin/rails-stats +27 -0
- data/lib/rails_stats/all.rb +8 -0
- data/lib/rails_stats/app_statistics.rb +1 -1
- data/lib/rails_stats/code_statistics_calculator.rb +18 -0
- data/lib/rails_stats/console_formatter.rb +60 -12
- data/lib/rails_stats/cucumber_statistics.rb +1 -1
- data/lib/rails_stats/gem_statistics.rb +1 -1
- data/lib/rails_stats/json_formatter.rb +59 -6
- data/lib/rails_stats/pack_finder.rb +67 -0
- data/lib/rails_stats/root_statistics.rb +1 -1
- data/lib/rails_stats/spec_statistics.rb +1 -1
- data/lib/rails_stats/stats_calculator.rb +135 -5
- data/lib/rails_stats/stats_formatter.rb +1 -0
- data/lib/rails_stats/tasks.rb +4 -1
- data/lib/rails_stats/test_statistics.rb +1 -1
- data/lib/rails_stats/util.rb +10 -2
- data/lib/rails_stats/version.rb +1 -1
- data/rails_stats.gemspec +4 -3
- data/test/dummy/app/javascript/dummy.jsx +1 -0
- data/test/dummy/app/javascript/dummy.tsx +0 -0
- data/test/dummy/app/javascript/index.ts +0 -0
- data/test/dummy/app/models/comment.rb +3 -0
- data/test/dummy/app/models/pet.rb +2 -0
- data/test/dummy/app/models/user.rb +2 -0
- data/test/dummy/db/schema.rb +10 -0
- data/test/fixtures/console-output.txt +8 -24
- data/test/fixtures/packwerk_app/app/controllers/core_controller.rb +4 -0
- data/test/fixtures/packwerk_app/app/models/core_model.rb +5 -0
- data/test/fixtures/packwerk_app/components/billing/app/models/billing_model.rb +4 -0
- data/test/fixtures/packwerk_app/components/billing/lib/billing.rb +2 -0
- data/test/fixtures/packwerk_app/lib/core_lib.rb +3 -0
- data/test/fixtures/packwerk_app/package.yml +1 -0
- data/test/fixtures/packwerk_app/packs/pack1/app/controllers/pack1_controller.rb +4 -0
- data/test/fixtures/packwerk_app/packs/pack1/app/models/pack1_model.rb +6 -0
- data/test/fixtures/packwerk_app/packs/pack1/lib/pack1_lib.rb +2 -0
- data/test/fixtures/packwerk_app/packs/pack1/package.yml +3 -0
- data/test/fixtures/packwerk_app/packs/pack1/spec/models/pack1_model_spec.rb +4 -0
- data/test/fixtures/packwerk_app/packs/pack2/app/models/pack2_model.rb +4 -0
- data/test/fixtures/packwerk_app/packs/pack2/test/models/pack2_model_test.rb +4 -0
- data/test/lib/rails_stats/code_statistics_test.rb +5 -5
- data/test/lib/rails_stats/console_formatter_test.rb +48 -0
- data/test/lib/rails_stats/json_formatter_test.rb +70 -127
- data/test/lib/rails_stats/pack_finder_test.rb +29 -0
- data/test/lib/rails_stats/stats_calculator_test.rb +64 -0
- data/test/lib/rails_stats/util_test.rb +49 -0
- metadata +56 -3
data/lib/rails_stats/all.rb
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
module RailsStats
|
|
2
|
+
# Vendored / generated directory names we never descend into or treat as a
|
|
3
|
+
# pack. Shared by PackFinder (which builds IGNORED_PATH from it) and Util
|
|
4
|
+
# (which matches them by basename while walking the tree).
|
|
5
|
+
IGNORED_DIRS = %w[node_modules vendor tmp log .git].freeze
|
|
6
|
+
end
|
|
7
|
+
|
|
1
8
|
require 'rails_stats/stats_calculator'
|
|
2
9
|
require 'rails_stats/stats_formatter'
|
|
3
10
|
require 'rails_stats/json_formatter'
|
|
4
11
|
require 'rails_stats/console_formatter'
|
|
5
12
|
require 'rails_stats/inflector'
|
|
6
13
|
require 'rails_stats/code_statistics_calculator'
|
|
14
|
+
require 'rails_stats/pack_finder'
|
|
7
15
|
require 'rails_stats/util'
|
|
8
16
|
require 'rails_stats/app_statistics'
|
|
9
17
|
require 'rails_stats/test_statistics'
|
|
@@ -18,6 +18,24 @@ module RailsStats
|
|
|
18
18
|
end_block_comment: %r{\*/},
|
|
19
19
|
method: /function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/,
|
|
20
20
|
},
|
|
21
|
+
jsx: {
|
|
22
|
+
line_comment: %r{^\s*//},
|
|
23
|
+
begin_block_comment: %r{^\s*/\*},
|
|
24
|
+
end_block_comment: %r{\*/},
|
|
25
|
+
method: /^\s*function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/,
|
|
26
|
+
},
|
|
27
|
+
ts: {
|
|
28
|
+
line_comment: %r{^\s*//},
|
|
29
|
+
begin_block_comment: %r{^\s*/\*},
|
|
30
|
+
end_block_comment: %r{\*/},
|
|
31
|
+
method: /^\s*function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/,
|
|
32
|
+
},
|
|
33
|
+
tsx: {
|
|
34
|
+
line_comment: %r{^\s*//},
|
|
35
|
+
begin_block_comment: %r{^\s*/\*},
|
|
36
|
+
end_block_comment: %r{\*/},
|
|
37
|
+
method: /^\s*function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/,
|
|
38
|
+
},
|
|
21
39
|
coffee: {
|
|
22
40
|
line_comment: /^\s*#/,
|
|
23
41
|
begin_block_comment: /^\s*###/,
|
|
@@ -3,25 +3,59 @@ require "bundler/stats/cli"
|
|
|
3
3
|
module RailsStats
|
|
4
4
|
class ConsoleFormatter < StatsFormatter
|
|
5
5
|
def to_s
|
|
6
|
-
Bundler::Stats::CLI.start
|
|
6
|
+
Bundler::Stats::CLI.start(['--format', 'text'])
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if @grand_total
|
|
14
|
-
print_line("Code", @code_total)
|
|
15
|
-
print_line("Tests", @tests_total)
|
|
16
|
-
print_line("Total", @grand_total)
|
|
17
|
-
print_splitter
|
|
8
|
+
if calculator.packs?
|
|
9
|
+
print_grouped
|
|
10
|
+
else
|
|
11
|
+
print_flat
|
|
18
12
|
end
|
|
19
13
|
|
|
20
14
|
print_code_test_stats
|
|
15
|
+
print_polymorphic_stats
|
|
16
|
+
print_schema_stats
|
|
21
17
|
end
|
|
22
18
|
|
|
23
19
|
private
|
|
24
20
|
|
|
21
|
+
def print_flat
|
|
22
|
+
print_table(@statistics)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def print_grouped
|
|
26
|
+
@statistics_by_group.each do |group, group_statistics|
|
|
27
|
+
puts "== #{group_label(group)} =="
|
|
28
|
+
print_table(group_statistics)
|
|
29
|
+
puts ""
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "== #{grand_total_label} =="
|
|
33
|
+
print_table(@statistics)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def group_label(group)
|
|
37
|
+
group == RailsStats::StatsCalculator::CORE_GROUP ? "Core Application" : "Pack: #{group}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def grand_total_label
|
|
41
|
+
"Total (all packs)"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Prints a full table (header, one line per concept, and Code/Tests/Total
|
|
45
|
+
# totals) for the given {concept => CodeStatisticsCalculator} hash.
|
|
46
|
+
def print_table(statistics)
|
|
47
|
+
code, tests, grand = calculator.totals_for(statistics)
|
|
48
|
+
|
|
49
|
+
print_header
|
|
50
|
+
statistics.keys.sort.each { |key| print_line(key, statistics[key]) }
|
|
51
|
+
print_splitter
|
|
52
|
+
|
|
53
|
+
print_line("Code", code)
|
|
54
|
+
print_line("Tests", tests)
|
|
55
|
+
print_line("Total", grand)
|
|
56
|
+
print_splitter
|
|
57
|
+
end
|
|
58
|
+
|
|
25
59
|
def print_header
|
|
26
60
|
print_splitter
|
|
27
61
|
puts "| Name | Files | Lines | LOC | Classes | Methods | M/C | LOC/M |"
|
|
@@ -53,5 +87,19 @@ module RailsStats
|
|
|
53
87
|
puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)} Files: #{calculator.files_total}"
|
|
54
88
|
puts ""
|
|
55
89
|
end
|
|
90
|
+
|
|
91
|
+
def print_polymorphic_stats
|
|
92
|
+
puts " Polymorphic models count: #{calculator.polymorphic} polymorphic associations"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def print_schema_stats
|
|
96
|
+
if File.exist?(calculator.schema_path)
|
|
97
|
+
puts " Schema Stats: #{calculator.schema} `create_table` calls in schema.rb"
|
|
98
|
+
elsif File.exist?(calculator.structure_path)
|
|
99
|
+
puts " Schema Stats: #{calculator.schema} `CREATE TABLE` calls in structure.sql"
|
|
100
|
+
else
|
|
101
|
+
puts " Schema Stats: No schema.rb or structure.sql file found"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
56
104
|
end
|
|
57
|
-
end
|
|
105
|
+
end
|
|
@@ -3,21 +3,50 @@ require "json"
|
|
|
3
3
|
module RailsStats
|
|
4
4
|
class JSONFormatter < StatsFormatter
|
|
5
5
|
def result
|
|
6
|
-
@result =
|
|
6
|
+
@result = []
|
|
7
|
+
old_stdout = $stdout
|
|
8
|
+
$stdout = StringIO.new
|
|
9
|
+
Bundler::Stats::CLI.start(["-f", "json"])
|
|
10
|
+
bundler_stats_cli_json_result = $stdout.string
|
|
11
|
+
$stdout = old_stdout
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
@result << JSON.parse(bundler_stats_cli_json_result) unless bundler_stats_cli_json_result.strip.empty?
|
|
14
|
+
|
|
15
|
+
@result += @statistics.map { |key, stats| stat_hash(key, stats) }
|
|
16
|
+
@result << stat_hash("Code", @code_total).merge(code_test_hash) if @code_total
|
|
17
|
+
@result << stat_hash("Tests", @tests_total).merge(code_test_hash) if @tests_total
|
|
18
|
+
@result << stat_hash("Total", @grand_total).merge(code_test_hash) if @grand_total
|
|
19
|
+
|
|
20
|
+
@result << { "packs" => packs_breakdown } if calculator.packs?
|
|
21
|
+
|
|
22
|
+
@result << { "schema_stats" => schema_info }
|
|
23
|
+
@result << { "polymorphic_stats" => print_polymorphic_stats }
|
|
11
24
|
|
|
12
25
|
@result
|
|
13
26
|
end
|
|
14
27
|
|
|
15
28
|
def to_s
|
|
16
|
-
puts result
|
|
29
|
+
puts JSON.generate(result, ascii_only: false)
|
|
17
30
|
end
|
|
18
31
|
|
|
19
32
|
private
|
|
20
33
|
|
|
34
|
+
# Per-pack / per-component breakdown, keyed by group name ("Core" plus
|
|
35
|
+
# each pack's relative path). Each group lists its concept stats followed
|
|
36
|
+
# by Code/Tests/Total rows for that group.
|
|
37
|
+
def packs_breakdown
|
|
38
|
+
@statistics_by_group.each_with_object({}) do |(group, group_statistics), out|
|
|
39
|
+
code, tests, grand = calculator.totals_for(group_statistics)
|
|
40
|
+
|
|
41
|
+
rows = group_statistics.map { |key, stats| stat_hash(key, stats) }
|
|
42
|
+
rows << stat_hash("Code", code).merge("total" => true)
|
|
43
|
+
rows << stat_hash("Tests", tests).merge("total" => true)
|
|
44
|
+
rows << stat_hash("Total", grand).merge("total" => true)
|
|
45
|
+
|
|
46
|
+
out[group] = rows
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
21
50
|
def code_test_hash
|
|
22
51
|
code = calculator.code_loc
|
|
23
52
|
tests = calculator.test_loc
|
|
@@ -42,5 +71,29 @@ module RailsStats
|
|
|
42
71
|
"loc_over_m" => loc_over_m.to_s
|
|
43
72
|
}
|
|
44
73
|
end
|
|
74
|
+
|
|
75
|
+
def print_polymorphic_stats
|
|
76
|
+
if calculator.polymorphic
|
|
77
|
+
{
|
|
78
|
+
"polymorphic_models_count" => calculator.polymorphic,
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def schema_info
|
|
84
|
+
if File.exist?(calculator.schema_path)
|
|
85
|
+
{
|
|
86
|
+
"schema_path" => calculator.schema_path,
|
|
87
|
+
"create_table calls count" => calculator.schema,
|
|
88
|
+
}
|
|
89
|
+
elsif File.exist?(calculator.structure_path)
|
|
90
|
+
{
|
|
91
|
+
"structure_path" => calculator.structure_path,
|
|
92
|
+
"create_table calls count" => calculator.schema
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
{ "schema_stats" => "No schema.rb or structure.sql file found" }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
45
98
|
end
|
|
46
|
-
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module RailsStats
|
|
2
|
+
# Detects "packs" (packwerk / packs-rails) and "components" (component-based
|
|
3
|
+
# Rails, à la Stephan Hagemann) inside a Rails application so that stats can
|
|
4
|
+
# be reported per pack/component plus a "Core" group for everything else.
|
|
5
|
+
#
|
|
6
|
+
# A directory is considered a pack when either:
|
|
7
|
+
#
|
|
8
|
+
# * it contains a `package.yml` file (the packwerk marker), or
|
|
9
|
+
# * it is a direct child of a `packs/` or `components/` directory and holds
|
|
10
|
+
# any of the usual code folders (`app`, `lib`, `spec`, `test`).
|
|
11
|
+
#
|
|
12
|
+
# Nested packs (e.g. `packs/a/packs/b`) are supported. The root directory
|
|
13
|
+
# itself is never reported as a pack.
|
|
14
|
+
module PackFinder
|
|
15
|
+
extend self
|
|
16
|
+
|
|
17
|
+
CODE_FOLDERS = %w[app lib spec test].freeze
|
|
18
|
+
PACK_CONTAINERS = %w[packs components].freeze
|
|
19
|
+
|
|
20
|
+
# Path regex used to filter glob results to skip vendored/generated
|
|
21
|
+
# directories. Built from the shared RailsStats::IGNORED_DIRS list.
|
|
22
|
+
IGNORED_PATH = %r{/(#{IGNORED_DIRS.map { |dir| Regexp.escape(dir) }.join("|")})/}.freeze
|
|
23
|
+
|
|
24
|
+
# Returns the absolute paths of every detected pack/component, sorted.
|
|
25
|
+
def find(root_directory)
|
|
26
|
+
root = File.absolute_path(root_directory)
|
|
27
|
+
packs = {}
|
|
28
|
+
|
|
29
|
+
collect_packwerk_packs(root, packs)
|
|
30
|
+
collect_convention_packs(root, packs)
|
|
31
|
+
|
|
32
|
+
packs.keys.sort
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def collect_packwerk_packs(root, packs)
|
|
38
|
+
Dir.glob(File.join(root, "**", "package.yml")).each do |marker_path|
|
|
39
|
+
next if marker_path =~ IGNORED_PATH
|
|
40
|
+
|
|
41
|
+
dir = File.absolute_path(File.dirname(marker_path))
|
|
42
|
+
next if dir == root
|
|
43
|
+
|
|
44
|
+
packs[dir] = true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def collect_convention_packs(root, packs)
|
|
49
|
+
PACK_CONTAINERS.each do |container|
|
|
50
|
+
Dir.glob(File.join(root, "**", container, "*")).each do |path|
|
|
51
|
+
next if path =~ IGNORED_PATH
|
|
52
|
+
next unless File.directory?(path)
|
|
53
|
+
|
|
54
|
+
dir = File.absolute_path(path)
|
|
55
|
+
next if dir == root
|
|
56
|
+
next unless code_folder?(dir)
|
|
57
|
+
|
|
58
|
+
packs[dir] = true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def code_folder?(dir)
|
|
64
|
+
CODE_FOLDERS.any? { |folder| File.directory?(File.join(dir, folder)) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -8,16 +8,46 @@ module RailsStats
|
|
|
8
8
|
'assets']
|
|
9
9
|
|
|
10
10
|
def initialize(root_directory)
|
|
11
|
-
@root_directory = root_directory
|
|
11
|
+
@root_directory = File.absolute_path(root_directory)
|
|
12
|
+
@schema_path = File.join(@root_directory, "db", "schema.rb")
|
|
13
|
+
@structure_path = File.join(@root_directory, "db", "structure.sql")
|
|
14
|
+
@pack_roots = PackFinder.find(@root_directory)
|
|
12
15
|
@key_concepts = calculate_key_concepts
|
|
13
16
|
@projects = calculate_projects
|
|
14
17
|
@statistics = calculate_statistics
|
|
18
|
+
@statistics_by_group = calculate_statistics_by_group
|
|
15
19
|
@code_loc = calculate_code
|
|
16
20
|
@test_loc = calculate_tests
|
|
21
|
+
@schema = calculate_create_table_calls
|
|
22
|
+
@polymorphic = calculate_polymorphic
|
|
17
23
|
@files_total, @code_total, @tests_total, @grand_total = calculate_totals
|
|
18
24
|
end
|
|
19
25
|
|
|
20
|
-
attr_reader :code_loc, :code_total, :files_total, :grand_total, :statistics, :test_loc, :tests_total
|
|
26
|
+
attr_reader :code_loc, :code_total, :files_total, :grand_total, :statistics, :test_loc, :tests_total, :schema, :schema_path, :structure_path, :polymorphic, :pack_roots, :statistics_by_group
|
|
27
|
+
|
|
28
|
+
CORE_GROUP = "Core".freeze
|
|
29
|
+
|
|
30
|
+
# Returns true when the application is split into packs/components, i.e.
|
|
31
|
+
# there is at least one group beyond the core application.
|
|
32
|
+
def packs?
|
|
33
|
+
@pack_roots.any?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Given a {concept => CodeStatisticsCalculator} hash (such as one of the
|
|
37
|
+
# values in +statistics_by_group+ or the flat +statistics+), returns the
|
|
38
|
+
# [code, tests, grand] totals for that group.
|
|
39
|
+
def totals_for(group_statistics)
|
|
40
|
+
code = CodeStatisticsCalculator.new
|
|
41
|
+
tests = CodeStatisticsCalculator.new
|
|
42
|
+
grand = CodeStatisticsCalculator.new
|
|
43
|
+
|
|
44
|
+
group_statistics.each_value do |stats|
|
|
45
|
+
grand.add(stats)
|
|
46
|
+
stats.test ? tests.add(stats) : code.add(stats)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
[code, tests, grand]
|
|
50
|
+
end
|
|
21
51
|
|
|
22
52
|
private
|
|
23
53
|
|
|
@@ -46,7 +76,6 @@ module RailsStats
|
|
|
46
76
|
out
|
|
47
77
|
end
|
|
48
78
|
|
|
49
|
-
|
|
50
79
|
def app_projects
|
|
51
80
|
@app_projects ||= calculate_app_projects
|
|
52
81
|
end
|
|
@@ -79,9 +108,24 @@ module RailsStats
|
|
|
79
108
|
end
|
|
80
109
|
end
|
|
81
110
|
|
|
82
|
-
|
|
83
111
|
def calculate_root_projects
|
|
84
|
-
[RootStatistics.new(@root_directory)]
|
|
112
|
+
projects = [RootStatistics.new(@root_directory)]
|
|
113
|
+
projects + calculate_pack_root_projects
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Packs that are not self-contained gems/engines (no gemspec) still have
|
|
117
|
+
# a `lib` (and sometimes `config`) directory that would otherwise be
|
|
118
|
+
# missed, since only the root RootStatistics scans the top-level lib.
|
|
119
|
+
# Gems are already covered by GemStatistics, so skip them here to avoid
|
|
120
|
+
# double-counting.
|
|
121
|
+
def calculate_pack_root_projects
|
|
122
|
+
@pack_roots.reject { |pack_root| gem_pack?(pack_root) }.collect do |pack_root|
|
|
123
|
+
RootStatistics.new(pack_root)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def gem_pack?(pack_root)
|
|
128
|
+
Dir[File.join(pack_root, "*.gemspec")].any?
|
|
85
129
|
end
|
|
86
130
|
|
|
87
131
|
def calculate_cucumber_projects
|
|
@@ -102,6 +146,62 @@ module RailsStats
|
|
|
102
146
|
out
|
|
103
147
|
end
|
|
104
148
|
|
|
149
|
+
# Same aggregation as +calculate_statistics+, but keeps each project's
|
|
150
|
+
# numbers grouped by the pack/component it belongs to (or "Core"). The
|
|
151
|
+
# returned hash is ordered with "Core" first followed by packs sorted by
|
|
152
|
+
# their relative path.
|
|
153
|
+
def calculate_statistics_by_group
|
|
154
|
+
grouped = {}
|
|
155
|
+
@projects.each do |project|
|
|
156
|
+
group = group_key_for(project.directory)
|
|
157
|
+
grouped[group] ||= {}
|
|
158
|
+
project.statistics.each do |key, stats|
|
|
159
|
+
grouped[group][key] ||= CodeStatisticsCalculator.new(project.test)
|
|
160
|
+
grouped[group][key].add(stats)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Drop empty concepts so a pack with, say, only models does not list a
|
|
165
|
+
# bunch of zero-line rows.
|
|
166
|
+
grouped.each_value do |group_statistics|
|
|
167
|
+
group_statistics.delete_if { |_key, stats| stats.lines == 0 }
|
|
168
|
+
end
|
|
169
|
+
grouped.delete_if { |_group, group_statistics| group_statistics.empty? }
|
|
170
|
+
|
|
171
|
+
sort_groups(grouped)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def sort_groups(grouped)
|
|
175
|
+
ordered = {}
|
|
176
|
+
ordered[CORE_GROUP] = grouped[CORE_GROUP] if grouped.key?(CORE_GROUP)
|
|
177
|
+
grouped.keys.reject { |key| key == CORE_GROUP }.sort.each do |key|
|
|
178
|
+
ordered[key] = grouped[key]
|
|
179
|
+
end
|
|
180
|
+
ordered
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Maps a project directory to its group: the relative path of the pack it
|
|
184
|
+
# lives in, or "Core" when it belongs to the main application.
|
|
185
|
+
def group_key_for(directory)
|
|
186
|
+
pack_root = pack_root_for(directory)
|
|
187
|
+
return CORE_GROUP unless pack_root
|
|
188
|
+
|
|
189
|
+
relative = pack_root.sub(/\A#{Regexp.escape(@root_directory)}\/?/, "")
|
|
190
|
+
relative.empty? ? CORE_GROUP : relative
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Returns the most specific (longest matching) pack root that contains the
|
|
194
|
+
# given directory, or nil when it is not part of any pack.
|
|
195
|
+
def pack_root_for(directory)
|
|
196
|
+
directory = File.absolute_path(directory.to_s)
|
|
197
|
+
best = nil
|
|
198
|
+
@pack_roots.each do |pack_root|
|
|
199
|
+
next unless directory == pack_root || directory.start_with?("#{pack_root}/")
|
|
200
|
+
best = pack_root if best.nil? || pack_root.length > best.length
|
|
201
|
+
end
|
|
202
|
+
best
|
|
203
|
+
end
|
|
204
|
+
|
|
105
205
|
def calculate_totals
|
|
106
206
|
files_total = @statistics.sum do |k,value|
|
|
107
207
|
value.files_total
|
|
@@ -133,5 +233,35 @@ module RailsStats
|
|
|
133
233
|
@statistics.each { |k, v| @test_loc += v.code_lines if v.test }
|
|
134
234
|
@test_loc
|
|
135
235
|
end
|
|
236
|
+
|
|
237
|
+
def calculate_create_table_calls
|
|
238
|
+
if @schema_path && File.exist?(@schema_path)
|
|
239
|
+
count_create_table_calls(schema_path, "create_table")
|
|
240
|
+
elsif @structure_path && File.exist?(@structure_path)
|
|
241
|
+
count_create_table_calls(structure_path, "CREATE TABLE")
|
|
242
|
+
else
|
|
243
|
+
0
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def count_create_table_calls(file_path, keyword)
|
|
248
|
+
create_table_count = 0
|
|
249
|
+
File.foreach(file_path) do |line|
|
|
250
|
+
create_table_count += 1 if line.strip.start_with?(keyword)
|
|
251
|
+
end
|
|
252
|
+
create_table_count
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def calculate_polymorphic
|
|
256
|
+
polymorphic_count = 0
|
|
257
|
+
Dir.glob(File.join(@root_directory, "app", "models", "*.rb")).each do |file|
|
|
258
|
+
File.foreach(file) do |line|
|
|
259
|
+
if line =~ /belongs_to\s+:\w+,\s+polymorphic:\s+true/
|
|
260
|
+
polymorphic_count += 1
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
polymorphic_count
|
|
265
|
+
end
|
|
136
266
|
end
|
|
137
267
|
end
|
|
@@ -3,6 +3,7 @@ module RailsStats
|
|
|
3
3
|
def initialize(calculator, opts = {})
|
|
4
4
|
@calculator = calculator
|
|
5
5
|
@statistics = calculator.statistics
|
|
6
|
+
@statistics_by_group = calculator.statistics_by_group
|
|
6
7
|
@code_total = calculator.code_total
|
|
7
8
|
@tests_total = calculator.tests_total
|
|
8
9
|
@grand_total = calculator.grand_total
|
data/lib/rails_stats/tasks.rb
CHANGED
|
@@ -3,8 +3,11 @@ task :stats, [:path, :format] do |t, args|
|
|
|
3
3
|
Rake::Task["stats"].clear # clear out normal one if there
|
|
4
4
|
require 'rails_stats/all'
|
|
5
5
|
|
|
6
|
+
# Respect an explicitly given path even when running inside a booted Rails
|
|
7
|
+
# app. Only fall back to Rails.root when no path was provided. This is what
|
|
8
|
+
# makes `rake stats[packs/pack1]` work from inside the app (see issue #23).
|
|
6
9
|
path = args[:path]
|
|
7
|
-
path
|
|
10
|
+
path ||= Rails.root.to_s if defined?(Rails)
|
|
8
11
|
fmt = args[:format] || ""
|
|
9
12
|
raise "no path given for stats" unless path
|
|
10
13
|
|
data/lib/rails_stats/util.rb
CHANGED
|
@@ -75,14 +75,22 @@ module RailsStats
|
|
|
75
75
|
out
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|feature)$/)
|
|
78
|
+
def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|jsx|ts|tsx|coffee|feature)$/)
|
|
79
79
|
stats = CodeStatisticsCalculator.new
|
|
80
80
|
|
|
81
81
|
Dir.foreach(directory) do |file_name|
|
|
82
82
|
path = "#{directory}/#{file_name}"
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
# Skip vendored / generated directories (e.g. a pack's
|
|
85
|
+
# app/webpack/node_modules). Pruning here avoids both the wasted walk
|
|
86
|
+
# and opening a directory that merely looks like a source file, such as
|
|
87
|
+
# node_modules/popper.js (which would raise Errno::EISDIR).
|
|
88
|
+
if File.directory?(path)
|
|
89
|
+
next if /^\./ =~ file_name
|
|
90
|
+
next if IGNORED_DIRS.include?(file_name)
|
|
91
|
+
|
|
85
92
|
stats.add(calculate_directory_statistics(path, pattern))
|
|
93
|
+
next
|
|
86
94
|
end
|
|
87
95
|
|
|
88
96
|
next unless file_name =~ pattern
|
data/lib/rails_stats/version.rb
CHANGED
data/rails_stats.gemspec
CHANGED
|
@@ -6,15 +6,16 @@ require 'rails_stats/version'
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "rails_stats"
|
|
8
8
|
spec.version = RailsStats::VERSION
|
|
9
|
-
spec.authors = ["Brian Leonard"]
|
|
10
|
-
spec.email = ["brian@bleonard.com"]
|
|
9
|
+
spec.authors = ["Brian Leonard", "Ernesto Tagwerker"]
|
|
10
|
+
spec.email = ["brian@bleonard.com", "ernesto@ombulabs.com"]
|
|
11
11
|
spec.summary = %q{Analyze a Rails project}
|
|
12
12
|
spec.description = %q{Point it to a directory and see stuff about the app}
|
|
13
13
|
spec.homepage = "https://github.com/fastruby/rails_stats"
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
|
17
|
-
spec.
|
|
17
|
+
spec.bindir = "bin"
|
|
18
|
+
spec.executables = ["rails-stats"]
|
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
20
|
spec.require_paths = ["lib"]
|
|
20
21
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
File without changes
|