attractor 2.0.5 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32f34f95771347469f06cb38654a9cbdfd90addb8cbf70e718689352256cc15c
4
- data.tar.gz: f8024a093f262346b4705dfcdcfd8cc96b44fab40562c9d81eb46b44c5a663f1
3
+ metadata.gz: c1b66250e293edf3d4298ee78c92a928b92128d0f8525a012ba4c99b8e16a77e
4
+ data.tar.gz: 354068cdc6c91978e80e3860ac65255811daf364d64c15dcd7221db2bac79f22
5
5
  SHA512:
6
- metadata.gz: 952196da679b26b588c622d4204a148cf32105975dc7918bca71102e9abb0f4753206ac6cc68509b93b492aeb690b7b6d0d258ecf3dc42de5d11a6490e9bd6bd
7
- data.tar.gz: a6bf90fef7df1ea26f3e45cec87360d3befed11107faa2b6554f3ac5df8bdf5985342346845e44e8ef2085a3e8678d7f5976c35fc4fd82fbf04fc48cb98d12b1
6
+ metadata.gz: 456416f564b3e7e5efa7874f901fea6a0c26c23e59bf157dd5c78cb58014f4bcee58f0172cebdc83061d94195d94631e1818eba905835799e1664527fc26caa9
7
+ data.tar.gz: 472072d37700e794de8ee98d48f2252186598f709094c9046acc97deb91ef34dfbc089a4c7b70f0965ce8fc4621877c63d41a167f4d2968f2b734b9f5e4e18de
data/README.md CHANGED
@@ -47,7 +47,7 @@
47
47
  [![MIT License][license-shield]][license-url]
48
48
  [![Twitter follow][twitter-shield]][twitter-url]
49
49
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
50
- [![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-)
50
+ [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
51
51
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
52
52
 
53
53
  <a href="https://www.patreon.com/user?u=24747270"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
@@ -186,6 +186,17 @@ attractor:
186
186
 
187
187
  ## CLI Commands and Options
188
188
 
189
+ Initialize the local cache:
190
+
191
+ ```sh
192
+ attractor init
193
+ --file_prefix|-p app/models
194
+ --type|-t rb|js
195
+ --start_ago|-s (e.g. 5y, 3m, 7w)
196
+ --minimum_churn|-c (minimum times a file must have changed to be processed)
197
+ --ignore|-i 'spec/*_spec.rb,db/schema.rb,tmp'
198
+ ```
199
+
189
200
  Print a simple output to console:
190
201
 
191
202
  ```sh
@@ -195,6 +206,7 @@ attractor calc
195
206
  --watch|-w
196
207
  --start_ago|-s (e.g. 5y, 3m, 7w)
197
208
  --minimum_churn|-c (minimum times a file must have changed to be processed)
209
+ --ignore|-i 'spec/*_spec.rb,db/schema.rb,tmp'
198
210
  ```
199
211
 
200
212
  Generate a full report
@@ -207,6 +219,7 @@ attractor report
207
219
  --no-open-browser|--ci
208
220
  --start_ago|-s (e.g. 5y, 3m, 7w)
209
221
  --minimum_churn|-c (minimum times a file must have changed to be processed)
222
+ --ignore|-i 'spec/*_spec.rb,db/schema.rb,tmp'
210
223
  ```
211
224
 
212
225
  Serve the output on `http://localhost:7890`
@@ -218,8 +231,16 @@ attractor serve
218
231
  --no-open-browser|--ci
219
232
  --start_ago|-s (e.g. 5y, 3m, 7w)
220
233
  --minimum_churn|-c (minimum times a file must have changed to be processed)
234
+ --ignore|-i 'spec/*_spec.rb,db/schema.rb,tmp'
221
235
  ```
222
236
 
237
+ Clear the local cache:
238
+
239
+ ```sh
240
+ attractor clean
241
+ ```
242
+
243
+
223
244
  ## Development
224
245
 
225
246
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -245,15 +266,17 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
245
266
  <!-- markdownlint-disable -->
246
267
  <table>
247
268
  <tr>
248
- <td align="center"><a href="http://www.julianrubisch.at"><img src="https://avatars0.githubusercontent.com/u/4352208?v=4" width="100px;" alt=""/><br /><sub><b>Julian Rubisch</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=julianrubisch" title="Code">💻</a> <a href="https://github.com/julianrubisch/attractor/commits?author=julianrubisch" title="Documentation">📖</a></td>
249
- <td align="center"><a href="https://github.com/olimart"><img src="https://avatars3.githubusercontent.com/u/547754?v=4" width="100px;" alt=""/><br /><sub><b>Olivier</b></sub></a><br /><a href="#maintenance-olimart" title="Maintenance">🚧</a></td>
250
- <td align="center"><a href="https://www.andrewmason.me/"><img src="https://avatars1.githubusercontent.com/u/18423853?v=4" width="100px;" alt=""/><br /><sub><b>Andrew Mason</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=andrewmcodes" title="Code">💻</a> <a href="https://github.com/julianrubisch/attractor/pulls?q=is%3Apr+reviewed-by%3Aandrewmcodes" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/julianrubisch/attractor/commits?author=andrewmcodes" title="Documentation">📖</a></td>
251
- <td align="center"><a href="https://www.ombulabs.com"><img src="https://avatars2.githubusercontent.com/u/17584?v=4" width="100px;" alt=""/><br /><sub><b>Ernesto Tagwerker</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=etagwerker" title="Code">💻</a></td>
269
+ <td align="center"><a href="http://www.julianrubisch.at"><img src="https://avatars0.githubusercontent.com/u/4352208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Julian Rubisch</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=julianrubisch" title="Code">💻</a> <a href="https://github.com/julianrubisch/attractor/commits?author=julianrubisch" title="Documentation">📖</a></td>
270
+ <td align="center"><a href="https://github.com/olimart"><img src="https://avatars3.githubusercontent.com/u/547754?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Olivier</b></sub></a><br /><a href="#maintenance-olimart" title="Maintenance">🚧</a></td>
271
+ <td align="center"><a href="https://www.andrewmason.me/"><img src="https://avatars1.githubusercontent.com/u/18423853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Mason</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=andrewmcodes" title="Code">💻</a> <a href="https://github.com/julianrubisch/attractor/pulls?q=is%3Apr+reviewed-by%3Aandrewmcodes" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/julianrubisch/attractor/commits?author=andrewmcodes" title="Documentation">📖</a></td>
272
+ <td align="center"><a href="https://www.ombulabs.com"><img src="https://avatars2.githubusercontent.com/u/17584?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ernesto Tagwerker</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=etagwerker" title="Code">💻</a></td>
273
+ <td align="center"><a href="https://experimentslabs.com"><img src="https://avatars.githubusercontent.com/u/1732268?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Manuel Tancoigne</b></sub></a><br /><a href="https://github.com/julianrubisch/attractor/commits?author=mtancoigne" title="Code">💻</a></td>
252
274
  </tr>
253
275
  </table>
254
276
 
255
- <!-- markdownlint-enable -->
277
+ <!-- markdownlint-restore -->
256
278
  <!-- prettier-ignore-end -->
279
+
257
280
  <!-- ALL-CONTRIBUTORS-LIST:END -->
258
281
 
259
282
  This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
data/Rakefile CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'autoprefixer-rails'
4
- require 'bootstrap'
5
- require 'bundler/gem_tasks'
6
- require 'fileutils'
7
- require 'rspec/core/rake_task'
8
- require 'sassc'
9
- require 'structured_changelog/tasks'
3
+ require "autoprefixer-rails"
4
+ require "bootstrap"
5
+ require "bundler/gem_tasks"
6
+ require "fileutils"
7
+ require "rspec/core/rake_task"
8
+ require "sassc"
9
+ require "structured_changelog/tasks"
10
10
 
11
11
  RSpec::Core::RakeTask.new(:spec)
12
12
 
@@ -14,18 +14,18 @@ task default: :spec
14
14
 
15
15
  task build: :assets
16
16
 
17
- desc 'Preprocess assets'
17
+ desc "Preprocess assets"
18
18
  task :assets do
19
- puts 'Preprocessing SCSS and JS files'
19
+ puts "Preprocessing SCSS and JS files"
20
20
 
21
- puts 'Copying over bootstrap'
21
+ puts "Copying over bootstrap"
22
22
 
23
- FileUtils.cp_r Gem::Specification.find_by_name('bootstrap').gem_dir, 'tmp'
23
+ FileUtils.cp_r Gem::Specification.find_by_name("bootstrap").gem_dir, "tmp"
24
24
 
25
- sass = File.read(File.expand_path('./src/stylesheets/main.scss'))
25
+ sass = File.read(File.expand_path("./src/stylesheets/main.scss"))
26
26
  css = SassC::Engine.new(sass, style: :compressed).render
27
27
  prefixed = AutoprefixerRails.process(css)
28
- File.open(File.expand_path('./app/assets/stylesheets/main.css'), 'w') { |file| file.write(prefixed) }
28
+ File.open(File.expand_path("./app/assets/stylesheets/main.css"), "w") { |file| file.write(prefixed) }
29
29
 
30
30
  npm_output = `npm run build`
31
31
  puts npm_output
data/exe/attractor CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'attractor/cli'
2
+ require "attractor/cli"
3
3
  Attractor::CLI.start
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "psych"
5
+
6
+ module Attractor
7
+ class Cache
8
+ class << self
9
+ def read(file_path:)
10
+ adapter.read(file_path: file_path)
11
+ end
12
+
13
+ def write(file_path:, value:)
14
+ adapter.write(file_path: file_path, value: value)
15
+ end
16
+
17
+ def persist!
18
+ adapter.persist!
19
+ end
20
+
21
+ def clear
22
+ adapter.clear
23
+ end
24
+
25
+ private
26
+
27
+ def adapter
28
+ @@adapter ||= CacheAdapter::JSON.instance
29
+ end
30
+ end
31
+ end
32
+
33
+ module CacheAdapter
34
+ class Base
35
+ include Singleton
36
+ end
37
+
38
+ class JSON < Base
39
+ def initialize
40
+ super
41
+
42
+ @data_directory = "tmp"
43
+ FileUtils.mkdir_p @data_directory
44
+ FileUtils.touch filename
45
+
46
+ begin
47
+ @store = ::JSON.parse(File.read(filename))
48
+ rescue ::JSON::ParserError
49
+ @store = {}
50
+ end
51
+ end
52
+
53
+ def read(file_path:)
54
+ value_hash = @store[file_path]
55
+
56
+ Value.new(**value_hash.values.first.transform_keys(&:to_sym)) unless value_hash.nil?
57
+ rescue ArgumentError => e
58
+ puts "Couldn't rehydrate value from cache: #{e.message}"
59
+ nil
60
+ end
61
+
62
+ def write(file_path:, value:)
63
+ mappings = {x: :churn, y: :complexity}
64
+
65
+ transformed_value = value.to_h.transform_keys { |k| mappings[k] || k }
66
+ @store[file_path] = {value.current_commit => transformed_value}
67
+ end
68
+
69
+ def persist!
70
+ File.write(filename, ::JSON.dump(@store))
71
+ end
72
+
73
+ def clear
74
+ FileUtils.rm filename
75
+ end
76
+
77
+ def filename
78
+ "#{@data_directory}/attractor-cache.json"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'churn/calculator'
3
+ require "churn/calculator"
4
4
 
5
- require 'attractor/value'
5
+ require "attractor/value"
6
6
 
7
7
  module Attractor
8
8
  # calculates churn and complexity
9
9
  class BaseCalculator
10
10
  attr_reader :type
11
11
 
12
- def initialize(file_prefix: '', file_extension: 'rb', minimum_churn_count: 3, start_ago: '5y')
12
+ def initialize(file_prefix: "", ignores: "", file_extension: "rb", minimum_churn_count: 3, start_ago: "5y")
13
13
  @file_prefix = file_prefix
14
14
  @file_extension = file_extension
15
15
  @minimum_churn_count = minimum_churn_count
16
16
  @start_date = Date.today - Attractor::DurationParser.new(start_ago).duration
17
+ @ignores = ignores
17
18
  end
18
19
 
19
20
  def calculate
@@ -21,17 +22,40 @@ module Attractor
21
22
  file_extension: @file_extension,
22
23
  file_prefix: @file_prefix,
23
24
  minimum_churn_count: @minimum_churn_count,
24
- start_date: @start_date
25
+ start_date: @start_date,
26
+ ignores: @ignores
25
27
  ).report(false)
26
28
 
27
- churn[:churn][:changes].map do |change|
28
- complexity, details = yield(change)
29
- Value.new(file_path: change[:file_path],
30
- churn: change[:times_changed],
31
- complexity: complexity,
32
- details: details,
33
- history: git_history_for_file(file_path: change[:file_path]))
29
+ puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files"
30
+
31
+ values = churn[:churn][:changes].map do |change|
32
+ history = git_history_for_file(file_path: change[:file_path])
33
+ commit = history&.first&.first
34
+
35
+ cached_value = Cache.read(file_path: change[:file_path])
36
+
37
+ if !cached_value.nil? && !cached_value.current_commit.nil? && cached_value.current_commit == commit
38
+ value = cached_value
39
+ else
40
+ complexity, details = yield(change)
41
+
42
+ value = Value.new(file_path: change[:file_path],
43
+ churn: change[:times_changed],
44
+ complexity: complexity,
45
+ details: details,
46
+ history: history)
47
+ Cache.write(file_path: change[:file_path], value: value)
48
+ end
49
+
50
+ print "."
51
+ value
34
52
  end
53
+
54
+ Cache.persist!
55
+
56
+ print "\n\n"
57
+
58
+ values
35
59
  end
36
60
 
37
61
  private
@@ -39,10 +63,10 @@ module Attractor
39
63
  def git_history_for_file(file_path:, limit: 10)
40
64
  history = `git log --oneline -n #{limit} -- #{file_path}`
41
65
  history.split("\n")
42
- .map do |log_entry|
66
+ .map do |log_entry|
43
67
  log_entry.partition(/\A(\S+)\s/)
44
- .map(&:strip)
45
- .reject(&:empty?)
68
+ .map(&:strip)
69
+ .reject(&:empty?)
46
70
  end
47
71
  end
48
72
  end
data/lib/attractor/cli.rb CHANGED
@@ -1,95 +1,101 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
3
+ require "thor"
4
4
 
5
- require 'attractor'
5
+ require "attractor"
6
6
 
7
7
  module Attractor
8
8
  # contains methods implementing the CLI
9
9
  class CLI < Thor
10
10
  shared_options = [[:file_prefix, aliases: :p],
11
- [:watch, aliases: :w, type: :boolean],
12
- [:minimum_churn, aliases: :c, type: :numeric, default: 3],
13
- [:start_ago, aliases: :s, type: :string, default: '5y'],
14
- [:type, aliases: :t]]
11
+ [:ignore, aliases: :i, default: ""],
12
+ [:watch, aliases: :w, type: :boolean],
13
+ [:minimum_churn, aliases: :c, type: :numeric, default: 3],
14
+ [:start_ago, aliases: :s, type: :string, default: "5y"],
15
+ [:type, aliases: :t]]
15
16
 
16
- advanced_options = [[:format, aliases: :f, default: 'html'],
17
- [:no_open_browser, type: :boolean],
18
- [:ci, type: :boolean]]
17
+ advanced_options = [[:format, aliases: :f, default: "html"],
18
+ [:no_open_browser, type: :boolean],
19
+ [:ci, type: :boolean]]
19
20
 
20
21
  desc "version", "Prints Attractor's version information"
21
- map %w(-v --version) => :version
22
+ map %w[-v --version] => :version
22
23
  def version
23
24
  puts "Attractor version #{Attractor::VERSION}"
24
25
  rescue RuntimeError => e
25
26
  puts "Runtime error: #{e.message}"
26
27
  end
27
28
 
28
- desc 'calc', 'Calculates churn and complexity for all ruby files in current directory'
29
+ desc "clean", "Clears attractor's cache"
30
+ def clean
31
+ puts "Clearing attractor cache"
32
+ Attractor.clear
33
+ end
34
+
35
+ desc "init", "Initializes attractor's cache"
36
+ shared_options.each do |shared_option|
37
+ option(*shared_option)
38
+ end
39
+ def init
40
+ puts "Warming attractor cache"
41
+ Attractor.init(calculators(options))
42
+ end
43
+
44
+ desc "calc", "Calculates churn and complexity for all ruby files in current directory"
29
45
  shared_options.each do |shared_option|
30
46
  option(*shared_option)
31
47
  end
48
+ option(:format, aliases: :f, default: :table)
32
49
  def calc
33
50
  file_prefix = options[:file_prefix]
34
- if options[:watch]
35
- puts 'Listening for file changes...'
36
- Attractor::ConsoleReporter.new(file_prefix: file_prefix, calculators: calculators(options)).watch
37
- else
38
- Attractor::ConsoleReporter.new(file_prefix: file_prefix, calculators: calculators(options)).report
39
- end
51
+ output_format = options[:format]
52
+
53
+ report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), format: output_format)
40
54
  rescue RuntimeError => e
41
55
  puts "Runtime error: #{e.message}"
42
56
  end
43
57
 
44
- desc 'report', 'Generates an HTML report'
58
+ desc "report", "Generates an HTML report"
45
59
  (shared_options + advanced_options).each do |option|
46
60
  option(*option)
47
61
  end
48
62
  def report
49
63
  file_prefix = options[:file_prefix]
50
64
  open_browser = !(options[:no_open_browser] || options[:ci])
51
- if options[:watch]
52
- puts 'Listening for file changes...'
53
- Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).watch
54
- else
55
- case options[:format]
56
- when 'html'
57
- Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
58
- else
59
- Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
60
- end
61
- end
65
+
66
+ report! Attractor::HtmlReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
62
67
  rescue RuntimeError => e
63
68
  puts "Runtime error: #{e.message}"
64
69
  end
65
70
 
66
- desc 'serve', 'Serves the report on localhost'
71
+ desc "serve", "Serves the report on localhost"
67
72
  (shared_options + advanced_options).each do |option|
68
73
  option(*option)
69
74
  end
70
75
  def serve
71
76
  file_prefix = options[:file_prefix]
72
77
  open_browser = !(options[:no_open_browser] || options[:ci])
73
- if options[:watch]
74
- puts 'Listening for file changes...'
75
- Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).watch
76
- else
77
- case options[:format]
78
- when 'html'
79
- Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
80
- else
81
- Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
82
- end
83
- end
78
+
79
+ report! Attractor::SinatraReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
84
80
  end
85
81
 
86
82
  private
87
83
 
88
84
  def calculators(options)
89
85
  Attractor.calculators_for_type(options[:type],
90
- file_prefix: options[:file_prefix],
91
- minimum_churn_count: options[:minimum_churn],
92
- start_ago: options[:start_ago])
86
+ file_prefix: options[:file_prefix],
87
+ minimum_churn_count: options[:minimum_churn],
88
+ ignores: options[:ignore],
89
+ start_ago: options[:start_ago])
90
+ end
91
+
92
+ def report!(reporter)
93
+ if options[:watch]
94
+ puts "Listening for file changes..."
95
+ reporter.watch
96
+ else
97
+ reporter.report
98
+ end
93
99
  end
94
100
  end
95
101
  end
@@ -4,17 +4,19 @@ module Attractor
4
4
  # converts a duration string into an amount of days
5
5
  class DurationParser
6
6
  TOKENS = {
7
- 'd' => 1,
8
- 'w' => 7,
9
- 'm' => 30,
10
- 'y' => 365
7
+ "d" => 1,
8
+ "w" => 7,
9
+ "m" => 30,
10
+ "y" => 365
11
11
  }.freeze
12
12
 
13
13
  attr_reader :duration
14
14
 
15
15
  def initialize(input)
16
16
  @input = input
17
- @duration = 0
17
+ @duration = @input.is_a?(Numeric) ? @input : 0
18
+ return if @duration > 0
19
+
18
20
  parse
19
21
  end
20
22
 
@@ -2,7 +2,7 @@ module Attractor
2
2
  # from https://github.com/prontolabs/pronto/blob/master/lib/pronto/gem_names.rb
3
3
  class GemNames
4
4
  def to_a
5
- gems.map { |gem| gem.name.sub(/^attractor-/, '') }.uniq.sort
5
+ gems.map { |gem| gem.name.sub(/^attractor-/, "") }.uniq.sort
6
6
  end
7
7
 
8
8
  private
@@ -9,4 +9,3 @@ module Attractor
9
9
  end
10
10
  end
11
11
  end
12
-
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'descriptive_statistics/safe'
4
- require 'fileutils'
5
- require 'forwardable'
6
- require 'launchy'
7
- require 'tilt'
3
+ require "descriptive_statistics/safe"
4
+ require "fileutils"
5
+ require "forwardable"
6
+ require "launchy"
7
+ require "tilt"
8
8
 
9
9
  module Attractor
10
10
  # base reporter
@@ -15,21 +15,20 @@ module Attractor
15
15
  attr_writer :values
16
16
  def_delegator :@watcher, :watch
17
17
 
18
- def initialize(file_prefix:, calculators:, open_browser: true)
18
+ def initialize(calculators:, file_prefix: "", ignores: "", open_browser: true)
19
19
  @file_prefix = file_prefix || ""
20
20
  @calculators = calculators
21
21
  @open_browser = open_browser
22
- @values = @calculators.first.last.calculate
23
22
  @suggester = Suggester.new(values)
24
23
 
25
- @watcher = Watcher.new(@file_prefix, lambda do
24
+ @watcher = Watcher.new(@file_prefix, ignores, lambda do
26
25
  report
27
26
  end)
28
- rescue NoMethodError => e
29
- raise 'There was a problem gathering churn changes'
27
+ rescue NoMethodError => _e
28
+ raise "There was a problem gathering churn changes"
30
29
  end
31
30
 
32
- def suggestions(quantile:, type: 'rb')
31
+ def suggestions(quantile:, type: "rb")
33
32
  @suggester.values = values(type: type)
34
33
  @suggestions = @suggester.suggest(quantile)
35
34
  @suggestions
@@ -37,17 +36,17 @@ module Attractor
37
36
 
38
37
  def report
39
38
  @suggestions = @suggester.suggest
40
- @types = Hash[@calculators.map { |calc| [calc.first, calc.last.type] }]
39
+ @types = @calculators.map { |calc| [calc.first, calc.last.type] }.to_h
41
40
  end
42
41
 
43
42
  def render
44
- 'Attractor'
43
+ "Attractor"
45
44
  end
46
45
 
47
- def values(type: 'rb')
46
+ def values(type: "rb")
48
47
  @values = @calculators[type].calculate
49
48
  @values
50
- rescue NoMethodError => e
49
+ rescue NoMethodError => _e
51
50
  puts "No calculator for type #{type}"
52
51
  end
53
52
  end
@@ -3,26 +3,65 @@
3
3
  module Attractor
4
4
  # console reporter
5
5
  class ConsoleReporter < BaseReporter
6
- def report
7
- super
8
- puts 'Calculated churn and complexity'
9
- puts
10
- puts "file_path#{' ' * 53}complexity churn"
11
- puts '-' * 80
6
+ class TableFormatter
7
+ def call(calculators)
8
+ puts "Calculated churn and complexity"
9
+ puts
10
+ puts "file_path#{" " * 53}complexity churn"
11
+ puts "-" * 80
12
12
 
13
- @calculators.each do |calc|
14
- # e.g. ['js', JsCalculator']
15
- puts calc.last.type
13
+ calculators.each do |calc|
14
+ # e.g. ['js', JsCalculator']
15
+ puts calc.last.type
16
16
 
17
- values = calc.last.calculate
18
- suggester = Suggester.new(values)
17
+ values = calc.last.calculate
18
+ suggester = Suggester.new(values)
19
19
 
20
- puts values&.map(&:to_s)
21
- puts
22
- puts 'Suggestions for refactorings:'
23
- suggester.suggest&.each { |sug| puts sug.file_path }
24
- puts
20
+ puts values&.map(&:to_s)
21
+ puts
22
+ puts "Suggestions for refactorings:"
23
+ suggester.suggest&.each { |sug| puts sug.file_path }
24
+ puts
25
+ end
25
26
  end
26
27
  end
28
+
29
+ class CSVFormatter
30
+ def call(calculators)
31
+ require "csv"
32
+
33
+ result = CSV.generate do |csv|
34
+ csv << %w[file_path score complexity churn type refactor]
35
+
36
+ calculators.each do |calc|
37
+ type = calc.last.type
38
+ values = calc.last.calculate
39
+ suggester = Suggester.new(values)
40
+ to_be_refactored = suggester.suggest.map(&:file_path)
41
+
42
+ values.each do |value|
43
+ csv << [value.file_path, value.score, value.complexity, value.churn, type, to_be_refactored.include?(value.file_path)]
44
+ end
45
+ end
46
+ end
47
+
48
+ puts result
49
+ end
50
+ end
51
+
52
+ def initialize(format:, **other)
53
+ super(**other)
54
+ @formatter = case format.to_sym
55
+ when :csv
56
+ CSVFormatter.new
57
+ else
58
+ TableFormatter.new
59
+ end
60
+ end
61
+
62
+ def report
63
+ super
64
+ @formatter.call(@calculators)
65
+ end
27
66
  end
28
67
  end