attractor 2.0.3 → 2.3.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -6
  3. data/Rakefile +13 -13
  4. data/app/assets/javascripts/index.pack.js +1 -1
  5. data/app/assets/stylesheets/main.css +1 -1
  6. data/exe/attractor +1 -1
  7. data/lib/attractor.rb +29 -13
  8. data/lib/attractor.rb~ +44 -0
  9. data/lib/attractor/cache.rb +82 -0
  10. data/lib/attractor/cache.rb~ +67 -0
  11. data/lib/attractor/calculators/base_calculator.rb +38 -14
  12. data/lib/attractor/calculators/base_calculator.rb~ +64 -0
  13. data/lib/attractor/cli.rb +48 -44
  14. data/lib/attractor/cli.rb~ +84 -0
  15. data/lib/attractor/detectors/base_detector.rb~ +4 -0
  16. data/lib/attractor/duration_parser.rb +7 -5
  17. data/lib/attractor/duration_parser.rb~ +27 -0
  18. data/lib/attractor/gem_names.rb +3 -1
  19. data/lib/attractor/gem_names.rb~ +23 -0
  20. data/lib/attractor/registry_entry.rb +0 -1
  21. data/lib/attractor/registry_entry.rb~ +2 -0
  22. data/lib/attractor/reporters/base_reporter.rb +22 -14
  23. data/lib/attractor/reporters/base_reporter.rb~ +54 -0
  24. data/lib/attractor/reporters/console_reporter.rb +4 -4
  25. data/lib/attractor/reporters/console_reporter.rb~ +21 -0
  26. data/lib/attractor/reporters/html_reporter.rb +23 -25
  27. data/lib/attractor/reporters/html_reporter.rb~ +48 -0
  28. data/lib/attractor/reporters/sinatra_reporter.rb +18 -24
  29. data/lib/attractor/suggester.rb +3 -1
  30. data/lib/attractor/tmp/churn/9f86bc9b7ec6bf59cbf7a111ce8b1159ae128347.json +1 -0
  31. data/lib/attractor/value.rb +8 -4
  32. data/lib/attractor/value.rb~ +30 -0
  33. data/lib/attractor/version.rb +1 -1
  34. data/lib/attractor/watcher.rb +7 -3
  35. data/lib/attractor/watcher.rb~ +24 -0
  36. metadata +29 -31
  37. data/.babelrc +0 -4
  38. data/.eslintrc.json +0 -25
  39. data/.gitignore +0 -20
  40. data/.rubocop.yml +0 -2
  41. data/CHANGELOG.md +0 -99
  42. data/Gemfile +0 -4
  43. data/Guardfile +0 -27
  44. data/attractor.gemspec +0 -65
  45. data/bin/console +0 -14
  46. data/bin/setup +0 -8
  47. data/bin/standardize +0 -4
  48. data/bin/test +0 -6
  49. data/dist/calculator.bundle.js +0 -1
  50. data/lib/attractor/reporters/.keep +0 -0
  51. data/package-lock.json +0 -6906
  52. data/package.json +0 -53
  53. data/webpack.config.js +0 -23
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "descriptive_statistics/safe"
4
+ require "fileutils"
5
+ require "forwardable"
6
+ require "launchy"
7
+ require "tilt"
8
+
9
+ module Attractor
10
+ # base reporter
11
+ class BaseReporter
12
+ extend Forwardable
13
+ attr_accessor :file_prefix
14
+ attr_reader :types
15
+ attr_writer :values
16
+ def_delegator :@watcher, :watch
17
+
18
+ def initialize(calculators:, file_prefix: "", ignores: "", open_browser: true)
19
+ @file_prefix = file_prefix || ""
20
+ @calculators = calculators
21
+ @open_browser = open_browser
22
+ @values = @calculators.first.last.calculate
23
+ @suggester = Suggester.new(values)
24
+
25
+ @watcher = Watcher.new(@file_prefix, ignores, lambda do
26
+ report
27
+ end)
28
+ rescue NoMethodError => _e
29
+ raise "There was a problem gathering churn changes"
30
+ end
31
+
32
+ def suggestions(quantile:, type: "rb")
33
+ @suggester.values = values(type: type)
34
+ @suggestions = @suggester.suggest(quantile)
35
+ @suggestions
36
+ end
37
+
38
+ def report
39
+ @suggestions = @suggester.suggest
40
+ @types = Hash[@calculators.map { |calc| [calc.first, calc.last.type] }]
41
+ end
42
+
43
+ def render
44
+ "Attractor"
45
+ end
46
+
47
+ def values(type: "rb")
48
+ @values = @calculators[type].calculate
49
+ @values
50
+ rescue NoMethodError => _e
51
+ puts "No calculator for type #{type}"
52
+ end
53
+ end
54
+ end
@@ -5,10 +5,10 @@ module Attractor
5
5
  class ConsoleReporter < BaseReporter
6
6
  def report
7
7
  super
8
- puts 'Calculated churn and complexity'
8
+ puts "Calculated churn and complexity"
9
9
  puts
10
- puts "file_path#{' ' * 53}complexity churn"
11
- puts '-' * 80
10
+ puts "file_path#{" " * 53}complexity churn"
11
+ puts "-" * 80
12
12
 
13
13
  @calculators.each do |calc|
14
14
  # e.g. ['js', JsCalculator']
@@ -19,7 +19,7 @@ module Attractor
19
19
 
20
20
  puts values&.map(&:to_s)
21
21
  puts
22
- puts 'Suggestions for refactorings:'
22
+ puts "Suggestions for refactorings:"
23
23
  suggester.suggest&.each { |sug| puts sug.file_path }
24
24
  puts
25
25
  end
@@ -0,0 +1,21 @@
1
+ require 'base_reporter'
2
+
3
+ module Attractor
4
+ # console reporter
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
12
+
13
+ puts @values.map(&:to_s)
14
+
15
+ puts
16
+ puts 'Suggestions for refactorings:'
17
+ @suggestions.each { |sug| puts sug.file_path }
18
+ puts
19
+ end
20
+ end
21
+ end
@@ -6,29 +6,28 @@ module Attractor
6
6
  def report
7
7
  super
8
8
 
9
- puts 'Generating an HTML report'
9
+ puts "Generating an HTML report"
10
10
  @serve_static = true
11
11
 
12
- FileUtils.mkdir_p './attractor_output'
13
- FileUtils.mkdir_p './attractor_output/stylesheets'
14
- FileUtils.mkdir_p './attractor_output/images'
15
- FileUtils.mkdir_p './attractor_output/javascripts'
12
+ FileUtils.mkdir_p "./attractor_output"
13
+ FileUtils.mkdir_p "./attractor_output/stylesheets"
14
+ FileUtils.mkdir_p "./attractor_output/images"
15
+ FileUtils.mkdir_p "./attractor_output/javascripts"
16
16
 
17
- File.open('./attractor_output/images/attractor_logo.svg', 'w') { |file| file.write(logo) }
18
- File.open('./attractor_output/images/attractor_favicon.png', 'w') { |file| file.write(favicon) }
19
- File.open('./attractor_output/stylesheets/main.css', 'w') { |file| file.write(css) }
20
- File.open('./attractor_output/javascripts/index.pack.js', 'w') { |file| file.write(javascript_pack) }
17
+ File.open("./attractor_output/images/attractor_logo.svg", "w") { |file| file.write(logo) }
18
+ File.open("./attractor_output/images/attractor_favicon.png", "w") { |file| file.write(favicon) }
19
+ File.open("./attractor_output/stylesheets/main.css", "w") { |file| file.write(css) }
20
+ File.open("./attractor_output/javascripts/index.pack.js", "w") { |file| file.write(javascript_pack) }
21
21
 
22
22
  if @calculators.size > 1
23
23
  @calculators.each do |calc|
24
24
  @short_type = calc.first
25
- @values = calc.last.calculate
26
- suggester = Suggester.new(values)
25
+ suggester = Suggester.new(values(type: @short_type))
27
26
  @suggestions = suggester.suggest
28
27
 
29
- File.open("./attractor_output/javascripts/index.#{@short_type}.js", 'w') { |file| file.write(javascript) }
30
- File.open("./attractor_output/index.#{@short_type}.html", 'w') { |file| file.write(render) }
31
- puts "Generated HTML report at #{File.expand_path './attractor_output/'}/index.#{@short_type}.html"
28
+ File.open("./attractor_output/javascripts/index.#{@short_type}.js", "w") { |file| file.write(javascript) }
29
+ File.open("./attractor_output/index.#{@short_type}.html", "w") { |file| file.write(render) }
30
+ puts "Generated HTML report at #{File.expand_path "./attractor_output/"}/index.#{@short_type}.html"
32
31
  end
33
32
 
34
33
  if @open_browser
@@ -36,41 +35,40 @@ module Attractor
36
35
  puts "Opening browser window..."
37
36
  end
38
37
  else
39
- File.open('./attractor_output/javascripts/index.js', 'w') { |file| file.write(javascript) }
40
- File.open('./attractor_output/index.html', 'w') { |file| file.write(render) }
41
- puts "Generated HTML report at #{File.expand_path './attractor_output/index.html'}"
38
+ File.open("./attractor_output/javascripts/index.js", "w") { |file| file.write(javascript) }
39
+ File.open("./attractor_output/index.html", "w") { |file| file.write(render) }
40
+ puts "Generated HTML report at #{File.expand_path "./attractor_output/index.html"}"
42
41
 
43
42
  if @open_browser
44
- Launchy.open(File.expand_path('./attractor_output/index.html'))
43
+ Launchy.open(File.expand_path("./attractor_output/index.html"))
45
44
  puts "Opening browser window..."
46
45
  end
47
46
  end
48
-
49
47
  end
50
48
 
51
49
  def logo
52
- File.read(File.expand_path('../../../app/assets/images/attractor_logo.svg', __dir__))
50
+ File.read(File.expand_path("../../../app/assets/images/attractor_logo.svg", __dir__))
53
51
  end
54
52
 
55
53
  def favicon
56
- File.read(File.expand_path('../../../app/assets/images/attractor_favicon.png', __dir__))
54
+ File.read(File.expand_path("../../../app/assets/images/attractor_favicon.png", __dir__))
57
55
  end
58
56
 
59
57
  def css
60
- File.read(File.expand_path('../../../app/assets/stylesheets/main.css', __dir__))
58
+ File.read(File.expand_path("../../../app/assets/stylesheets/main.css", __dir__))
61
59
  end
62
60
 
63
61
  def javascript_pack
64
- File.read(File.expand_path('../../../app/assets/javascripts/index.pack.js', __dir__))
62
+ File.read(File.expand_path("../../../app/assets/javascripts/index.pack.js", __dir__))
65
63
  end
66
64
 
67
65
  def javascript
68
- template = Tilt.new(File.expand_path('../../../app/assets/javascripts/index.js.erb', __dir__))
66
+ template = Tilt.new(File.expand_path("../../../app/assets/javascripts/index.js.erb", __dir__))
69
67
  template.render self
70
68
  end
71
69
 
72
70
  def render
73
- template = Tilt.new(File.expand_path('../../../app/views/index.html.erb', __dir__))
71
+ template = Tilt.new(File.expand_path("../../../app/views/index.html.erb", __dir__))
74
72
  template.render self
75
73
  end
76
74
  end
@@ -0,0 +1,48 @@
1
+ module Attractor
2
+ # HTML reporter
3
+ class HtmlReporter < BaseReporter
4
+ def report
5
+ super
6
+
7
+ puts 'Generating an HTML report'
8
+ @serve_static = true
9
+
10
+ FileUtils.mkdir_p './attractor_output'
11
+ FileUtils.mkdir_p './attractor_output/stylesheets'
12
+ FileUtils.mkdir_p './attractor_output/images'
13
+ FileUtils.mkdir_p './attractor_output/javascripts'
14
+
15
+ File.open('./attractor_output/images/attractor_logo.svg', 'w') { |file| file.write(logo) }
16
+ File.open('./attractor_output/stylesheets/main.css', 'w') { |file| file.write(css) }
17
+ File.open('./attractor_output/javascripts/index.js', 'w') { |file| file.write(javascript) }
18
+ File.open('./attractor_output/javascripts/index.pack.js', 'w') { |file| file.write(javascript_pack) }
19
+
20
+ File.open('./attractor_output/index.html', 'w') { |file| file.write(render) }
21
+ puts "Generated HTML report at #{File.expand_path './attractor_output/index.html'}"
22
+
23
+ Launchy.open(File.expand_path('./attractor_output/index.html'))
24
+ end
25
+
26
+ def logo
27
+ File.read(File.expand_path('../../../app/assets/images/attractor_logo.svg', __dir__))
28
+ end
29
+
30
+ def css
31
+ File.read(File.expand_path('../../../app/assets/stylesheets/main.css', __dir__))
32
+ end
33
+
34
+ def javascript_pack
35
+ File.read(File.expand_path('../../../app/assets/javascripts/index.pack.js', __dir__))
36
+ end
37
+
38
+ def javascript
39
+ template = Tilt.new(File.expand_path('../../../app/assets/javascripts/index.js.erb', __dir__))
40
+ template.render self
41
+ end
42
+
43
+ def render
44
+ template = Tilt.new(File.expand_path('../../../app/views/index.html.erb', __dir__))
45
+ template.render self
46
+ end
47
+ end
48
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/livereload'
4
- require 'rack'
5
- require 'sinatra/base'
3
+ require "rack/livereload"
4
+ require "rack"
5
+ require "sinatra/base"
6
6
 
7
7
  module Attractor
8
8
  # skeleton sinatra app
@@ -13,30 +13,31 @@ module Attractor
13
13
  end
14
14
 
15
15
  enable :static
16
- set :public_folder, File.expand_path('../../../app/assets', __dir__)
16
+ set :public_folder, File.expand_path("../../../app/assets", __dir__)
17
17
  set :show_exceptions, :after_handler
18
18
 
19
- get '/' do
19
+ get "/" do
20
20
  @types = @reporter.types
21
- erb File.read(File.expand_path('../../../app/views/index.html.erb', __dir__))
21
+ erb File.read(File.expand_path("../../../app/views/index.html.erb", __dir__))
22
22
  end
23
23
 
24
- get '/file_prefix' do
25
- { file_prefix: @reporter.file_prefix }.to_json
24
+ get "/file_prefix" do
25
+ {file_prefix: @reporter.file_prefix}.to_json
26
26
  end
27
27
 
28
- get '/values' do
29
- type = params[:type] || 'rb'
28
+ get "/values" do
29
+ type = params[:type] || "rb"
30
30
  @reporter.values(type: type).to_json
31
31
  end
32
32
 
33
- get '/suggestions' do
33
+ get "/suggestions" do
34
34
  threshold = params[:t] || 95
35
- @reporter.suggestions(threshold).to_json
35
+ type = params[:type] || "rb"
36
+ @reporter.suggestions(quantile: threshold, type: type).to_json
36
37
  end
37
38
 
38
39
  error NoMethodError do
39
- { error: env['sinatra.error'].message }.to_json
40
+ {error: env["sinatra.error"].message}.to_json
40
41
  end
41
42
  end
42
43
 
@@ -47,10 +48,10 @@ module Attractor
47
48
 
48
49
  app = AttractorApp.new(self)
49
50
 
50
- puts 'Serving attractor at http://localhost:7890'
51
+ puts "Serving attractor at http://localhost:7890"
51
52
 
52
53
  if @open_browser
53
- Launchy.open('http://localhost:7890') if @open_browser
54
+ Launchy.open("http://localhost:7890") if @open_browser
54
55
  puts "Opening browser window..."
55
56
  end
56
57
 
@@ -62,20 +63,13 @@ module Attractor
62
63
 
63
64
  app = AttractorApp.new(self)
64
65
 
65
- puts 'Serving attractor at http://localhost:7890'
66
+ puts "Serving attractor at http://localhost:7890"
66
67
  if @open_browser
67
- Launchy.open('http://localhost:7890')
68
+ Launchy.open("http://localhost:7890")
68
69
  puts "Opening browser window..."
69
70
  end
70
71
 
71
72
  Rack::Handler::WEBrick.run Rack::LiveReload.new(app), Port: 7890
72
73
  end
73
-
74
- def values(type: 'rb')
75
- @values = @calculators[type].calculate
76
- @values
77
- rescue NoMethodError => e
78
- puts "No calculator for type #{type}"
79
- end
80
74
  end
81
75
  end
@@ -3,6 +3,8 @@
3
3
  module Attractor
4
4
  # makes suggestions for refactorings
5
5
  class Suggester
6
+ attr_accessor :values
7
+
6
8
  def initialize(values)
7
9
  @values = values || []
8
10
  end
@@ -13,7 +15,7 @@ module Attractor
13
15
  quantile = products.percentile(threshold.to_i)
14
16
 
15
17
  @values.select { |val| val.churn * val.complexity > quantile }
16
- .sort_by { |val| val.churn * val.complexity }.reverse
18
+ .sort_by { |val| val.churn * val.complexity }.reverse
17
19
  end
18
20
  end
19
21
  end
@@ -0,0 +1 @@
1
+ {"churn":{"changes":[{"file_path":"app/assets/javascripts/index.pack.js","times_changed":21},{"file_path":"src/javascript/functions.js","times_changed":16},{"file_path":"src/javascript/index.js","times_changed":4}],"class_churn":[],"method_churn":[],"changed_files":[".gitignore","Rakefile","attractor.gemspec","lib/attractor.rb","lib/attractor/calculators/js_calculator.rb","lib/attractor/cli.rb","src/javascript/calculator/index.js","src/javascript/calculator/package-lock.json","src/javascript/calculator/package.json","src/javascript/calculator/webpack.config.js"],"changed_classes":[],"changed_methods":[]}}
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require "json"
4
4
 
5
5
  module Attractor
6
6
  # holds a churn/complexity value
7
7
  class Value
8
8
  attr_reader :file_path, :churn, :complexity, :details, :history
9
9
 
10
- def initialize(file_path: '', churn: 1, complexity: 0, details: [], history: [])
10
+ def initialize(file_path: "", churn: 1, complexity: 0, details: [], history: [])
11
11
  @file_path = file_path
12
12
  @churn = churn
13
13
  @complexity = complexity
@@ -15,12 +15,16 @@ module Attractor
15
15
  @history = history
16
16
  end
17
17
 
18
+ def current_commit
19
+ history&.first&.first
20
+ end
21
+
18
22
  def to_s
19
- format('%-64s%8.1f%8i', @file_path, @complexity, @churn)
23
+ format("%-64s%8.1f%8i", @file_path, @complexity, @churn)
20
24
  end
21
25
 
22
26
  def to_h
23
- { file_path: file_path, x: churn, y: complexity, details: details, history: history }
27
+ {file_path: file_path, x: churn, y: complexity, details: details, history: history}
24
28
  end
25
29
 
26
30
  def to_json(_opt)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Attractor
6
+ # holds a churn/complexity value
7
+ class Value
8
+ attr_reader :file_path, :churn, :complexity, :details, :history
9
+
10
+ def initialize(file_path: "", churn: 1, complexity: 0, details: [], history: [])
11
+ @file_path = file_path
12
+ @churn = churn
13
+ @complexity = complexity
14
+ @details = details
15
+ @history = history
16
+ end
17
+
18
+ def to_s
19
+ format("%-64s%8.1f%8i", @file_path, @complexity, @churn)
20
+ end
21
+
22
+ def to_h
23
+ {file_path: file_path, x: churn, y: complexity, details: details, history: history}
24
+ end
25
+
26
+ def to_json(_opt)
27
+ to_h.to_json
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Attractor
2
- VERSION = "2.0.3"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "listen"
4
+
3
5
  module Attractor
4
6
  # functionality for watching file system changes
5
7
  class Watcher
6
- def initialize(file_prefix, callback)
8
+ def initialize(file_prefix, ignores, callback)
7
9
  @file_prefix = file_prefix
8
10
  @callback = callback
11
+ @ignores = ignores.split(",").map(&:strip)
9
12
  end
10
13
 
11
14
  def watch
12
15
  @callback.call
16
+ ignore = @ignores + [/^attractor_output/]
13
17
 
14
- listener = Listen.to(@file_prefix, ignore: /^attractor_output/) do |modified, _added, _removed|
18
+ listener = Listen.to(File.absolute_path(@file_prefix), ignore: ignore) do |modified, _added, _removed|
15
19
  if modified
16
- puts "#{modified.map(&:to_s).join(', ')} modified, recalculating..."
20
+ puts "#{modified.map(&:to_s).join(", ")} modified, recalculating..."
17
21
  @callback.call
18
22
  end
19
23
  end