attractor 2.0.5 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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