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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +23 -15
  3. data/CHANGELOG.md +18 -3
  4. data/Gemfile +2 -2
  5. data/README.md +224 -232
  6. data/bin/rails-stats +27 -0
  7. data/lib/rails_stats/all.rb +8 -0
  8. data/lib/rails_stats/app_statistics.rb +1 -1
  9. data/lib/rails_stats/code_statistics_calculator.rb +18 -0
  10. data/lib/rails_stats/console_formatter.rb +60 -12
  11. data/lib/rails_stats/cucumber_statistics.rb +1 -1
  12. data/lib/rails_stats/gem_statistics.rb +1 -1
  13. data/lib/rails_stats/json_formatter.rb +59 -6
  14. data/lib/rails_stats/pack_finder.rb +67 -0
  15. data/lib/rails_stats/root_statistics.rb +1 -1
  16. data/lib/rails_stats/spec_statistics.rb +1 -1
  17. data/lib/rails_stats/stats_calculator.rb +135 -5
  18. data/lib/rails_stats/stats_formatter.rb +1 -0
  19. data/lib/rails_stats/tasks.rb +4 -1
  20. data/lib/rails_stats/test_statistics.rb +1 -1
  21. data/lib/rails_stats/util.rb +10 -2
  22. data/lib/rails_stats/version.rb +1 -1
  23. data/rails_stats.gemspec +4 -3
  24. data/test/dummy/app/javascript/dummy.jsx +1 -0
  25. data/test/dummy/app/javascript/dummy.tsx +0 -0
  26. data/test/dummy/app/javascript/index.ts +0 -0
  27. data/test/dummy/app/models/comment.rb +3 -0
  28. data/test/dummy/app/models/pet.rb +2 -0
  29. data/test/dummy/app/models/user.rb +2 -0
  30. data/test/dummy/db/schema.rb +10 -0
  31. data/test/fixtures/console-output.txt +8 -24
  32. data/test/fixtures/packwerk_app/app/controllers/core_controller.rb +4 -0
  33. data/test/fixtures/packwerk_app/app/models/core_model.rb +5 -0
  34. data/test/fixtures/packwerk_app/components/billing/app/models/billing_model.rb +4 -0
  35. data/test/fixtures/packwerk_app/components/billing/lib/billing.rb +2 -0
  36. data/test/fixtures/packwerk_app/lib/core_lib.rb +3 -0
  37. data/test/fixtures/packwerk_app/package.yml +1 -0
  38. data/test/fixtures/packwerk_app/packs/pack1/app/controllers/pack1_controller.rb +4 -0
  39. data/test/fixtures/packwerk_app/packs/pack1/app/models/pack1_model.rb +6 -0
  40. data/test/fixtures/packwerk_app/packs/pack1/lib/pack1_lib.rb +2 -0
  41. data/test/fixtures/packwerk_app/packs/pack1/package.yml +3 -0
  42. data/test/fixtures/packwerk_app/packs/pack1/spec/models/pack1_model_spec.rb +4 -0
  43. data/test/fixtures/packwerk_app/packs/pack2/app/models/pack2_model.rb +4 -0
  44. data/test/fixtures/packwerk_app/packs/pack2/test/models/pack2_model_test.rb +4 -0
  45. data/test/lib/rails_stats/code_statistics_test.rb +5 -5
  46. data/test/lib/rails_stats/console_formatter_test.rb +48 -0
  47. data/test/lib/rails_stats/json_formatter_test.rb +70 -127
  48. data/test/lib/rails_stats/pack_finder_test.rb +29 -0
  49. data/test/lib/rails_stats/stats_calculator_test.rb +64 -0
  50. data/test/lib/rails_stats/util_test.rb +49 -0
  51. metadata +56 -3
@@ -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'
@@ -1,6 +1,6 @@
1
1
  module RailsStats
2
2
  class AppStatistics
3
- attr_reader :statistics, :total, :test
3
+ attr_reader :statistics, :total, :test, :directory
4
4
 
5
5
  def initialize(directory)
6
6
  @directories = []
@@ -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
- print_header
9
- sorted_keys = @statistics.keys.sort
10
- sorted_keys.each { |key| print_line(key, @statistics[key]) }
11
- print_splitter
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
@@ -1,6 +1,6 @@
1
1
  module RailsStats
2
2
  class CucumberStatistics
3
- attr_reader :statistics, :total, :test
3
+ attr_reader :statistics, :total, :test, :directory
4
4
 
5
5
  def initialize(directory)
6
6
  @test = true
@@ -1,6 +1,6 @@
1
1
  module RailsStats
2
2
  class GemStatistics
3
- attr_reader :statistics, :total, :test
3
+ attr_reader :statistics, :total, :test, :directory
4
4
 
5
5
  def initialize(directory)
6
6
  @test = false
@@ -3,21 +3,50 @@ require "json"
3
3
  module RailsStats
4
4
  class JSONFormatter < StatsFormatter
5
5
  def result
6
- @result = @statistics.map { |key, stats| stat_hash(key, stats) }
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
- if @grand_total
9
- @result << stat_hash("Total", @grand_total).merge(code_test_hash)
10
- end
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.to_json
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
@@ -1,6 +1,6 @@
1
1
  module RailsStats
2
2
  class RootStatistics
3
- attr_reader :statistics, :total, :test
3
+ attr_reader :statistics, :total, :test, :directory
4
4
 
5
5
  ROOT_FOLDERS = {
6
6
  "lib" => "Libraries",
@@ -1,6 +1,6 @@
1
1
  module RailsStats
2
2
  class SpecStatistics
3
- attr_reader :statistics, :total, :test
3
+ attr_reader :statistics, :total, :test, :directory
4
4
 
5
5
  SPEC_FOLDERS = ['controllers',
6
6
  'features',
@@ -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
@@ -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 = Rails.root.to_s if defined?(Rails)
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
 
@@ -1,6 +1,6 @@
1
1
  module RailsStats
2
2
  class TestStatistics
3
- attr_reader :statistics, :total, :test
3
+ attr_reader :statistics, :total, :test, :directory
4
4
 
5
5
  SPEC_FOLDERS = ['controllers',
6
6
  'functional',
@@ -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
- if File.directory?(path) && (/^\./ !~ file_name)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsStats
4
- VERSION = "2.0.1"
4
+ VERSION = "2.2.0"
5
5
  end
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.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
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
@@ -0,0 +1,3 @@
1
+ class Comments < ApplicationRecord
2
+ belongs_to :commentable, polymorphic: true
3
+ end
@@ -0,0 +1,2 @@
1
+ class Pets < User
2
+ end
@@ -0,0 +1,2 @@
1
+ class Users < ApplicationRecord
2
+ end
@@ -0,0 +1,10 @@
1
+
2
+ ActiveRecord::Schema[7.0].define(version: 2023_04_25_154701) do
3
+ create_table "users", force: :cascade do |t|
4
+ t.string "email"
5
+ end
6
+ create_table "comments", force: :cascade do |t|
7
+ t.bigint :commentable_id
8
+ t.string :commentable_type
9
+ end
10
+ end