attractor 2.3.0 → 2.5.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: b116bbaa914ca032391378e04b8e41f27b28fa4feaca3fc50c93053d1954a15a
4
- data.tar.gz: 3e5620d661d19d3d2308457921389ce78b4ba3b844a6a4d3dd33c1ef9d093a56
3
+ metadata.gz: 1eef3d6e8104428fdc8240ae5161654990b53d5351962bc474f11e450db9cbc2
4
+ data.tar.gz: 6ec7109f7c6aa6e4b5b594863e5bfeff08652f2fdf6c8c2a86a208357c084713
5
5
  SHA512:
6
- metadata.gz: '092ff47f81f9d6f0e7fa025d21a89501413e1dc8c7044d5420d4db8ea04f72fc6554840a9656b0ac002c2f0c25ef3f0fa9c1204214d4b29a284eabb565428f82'
7
- data.tar.gz: 83ad8a7dafa969e68c4db77629663738513137a3f91ad058451d9a37c1ed39aff1cfea785c34042d543b7caac8ec625207cf3701c7003d63a23f631cbba109b5
6
+ metadata.gz: 244a2d549ba546424c7a4c25aaa59c17b4bd1a8fed73cbdd09851c4296631c4b42d907d4b31a76a8a4f6c584143d8f086764299e00e690bce1f0f19cc69d8bcb
7
+ data.tar.gz: d508623821df269d803f3fdb0440fbd76f14a5d24cbd169c23cfabf01e1423171b201b1c3ee2c92a986ada20700c790c67ff75d3928bfbf9e4e1d2419e60be65
data/Rakefile CHANGED
@@ -25,7 +25,7 @@ task :assets do
25
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.write(File.expand_path("./app/assets/stylesheets/main.css"), prefixed)
29
29
 
30
30
  npm_output = `npm run build`
31
31
  puts npm_output
@@ -9,12 +9,13 @@ module Attractor
9
9
  class BaseCalculator
10
10
  attr_reader :type
11
11
 
12
- def initialize(file_prefix: "", ignores: "", 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", verbose: false)
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
17
  @ignores = ignores
18
+ @verbose = verbose
18
19
  end
19
20
 
20
21
  def calculate
@@ -26,7 +27,7 @@ module Attractor
26
27
  ignores: @ignores
27
28
  ).report(false)
28
29
 
29
- puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files"
30
+ puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files" if @verbose
30
31
 
31
32
  values = churn[:churn][:changes].map do |change|
32
33
  history = git_history_for_file(file_path: change[:file_path])
@@ -40,20 +41,20 @@ module Attractor
40
41
  complexity, details = yield(change)
41
42
 
42
43
  value = Value.new(file_path: change[:file_path],
43
- churn: change[:times_changed],
44
- complexity: complexity,
45
- details: details,
46
- history: history)
44
+ churn: change[:times_changed],
45
+ complexity: complexity,
46
+ details: details,
47
+ history: history)
47
48
  Cache.write(file_path: change[:file_path], value: value)
48
49
  end
49
50
 
50
- print "."
51
+ print "." if @verbose
51
52
  value
52
53
  end
53
54
 
54
55
  Cache.persist!
55
56
 
56
- print "\n\n"
57
+ print "\n\n" if @verbose
57
58
 
58
59
  values
59
60
  end
@@ -26,7 +26,9 @@ module Attractor
26
26
  ignores: @ignores
27
27
  ).report(false)
28
28
 
29
- churn[:churn][:changes].map do |change|
29
+ puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files"
30
+
31
+ values = churn[:churn][:changes].map do |change|
30
32
  history = git_history_for_file(file_path: change[:file_path])
31
33
  commit = history&.first&.first
32
34
 
@@ -38,15 +40,22 @@ module Attractor
38
40
  complexity, details = yield(change)
39
41
 
40
42
  value = Value.new(file_path: change[:file_path],
41
- churn: change[:times_changed],
42
- complexity: complexity,
43
- details: details,
44
- history: history)
43
+ churn: change[:times_changed],
44
+ complexity: complexity,
45
+ details: details,
46
+ history: history)
45
47
  Cache.write(file_path: change[:file_path], value: value)
46
48
  end
47
49
 
50
+ print "."
48
51
  value
49
52
  end
53
+
54
+ Cache.persist!
55
+
56
+ print "\n\n"
57
+
58
+ values
50
59
  end
51
60
 
52
61
  private
data/lib/attractor/cli.rb CHANGED
@@ -8,6 +8,7 @@ module Attractor
8
8
  # contains methods implementing the CLI
9
9
  class CLI < Thor
10
10
  shared_options = [[:file_prefix, aliases: :p],
11
+ [:verbose, aliases: :v, type: :boolean],
11
12
  [:ignore, aliases: :i, default: ""],
12
13
  [:watch, aliases: :w, type: :boolean],
13
14
  [:minimum_churn, aliases: :c, type: :numeric, default: 3],
@@ -45,10 +46,12 @@ module Attractor
45
46
  shared_options.each do |shared_option|
46
47
  option(*shared_option)
47
48
  end
49
+ option(:format, aliases: :f, default: :table)
48
50
  def calc
49
51
  file_prefix = options[:file_prefix]
52
+ output_format = options[:format]
50
53
 
51
- report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options))
54
+ report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), format: output_format)
52
55
  rescue RuntimeError => e
53
56
  puts "Runtime error: #{e.message}"
54
57
  end
@@ -84,7 +87,8 @@ module Attractor
84
87
  file_prefix: options[:file_prefix],
85
88
  minimum_churn_count: options[:minimum_churn],
86
89
  ignores: options[:ignore],
87
- start_ago: options[:start_ago])
90
+ start_ago: options[:start_ago],
91
+ verbose: options[:verbose])
88
92
  end
89
93
 
90
94
  def report!(reporter)
@@ -26,14 +26,31 @@ module Attractor
26
26
  puts "Runtime error: #{e.message}"
27
27
  end
28
28
 
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
+
29
44
  desc "calc", "Calculates churn and complexity for all ruby files in current directory"
30
45
  shared_options.each do |shared_option|
31
46
  option(*shared_option)
32
47
  end
48
+ option(:format, aliases: :f, default: :table)
33
49
  def calc
34
50
  file_prefix = options[:file_prefix]
51
+ output_format = options[:format]
35
52
 
36
- report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options))
53
+ report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), format: output_format)
37
54
  rescue RuntimeError => e
38
55
  puts "Runtime error: #{e.message}"
39
56
  end
@@ -19,7 +19,7 @@ module Attractor
19
19
  @file_prefix = file_prefix || ""
20
20
  @calculators = calculators
21
21
  @open_browser = open_browser
22
- @suggester = Suggester.new(values)
22
+ @suggester = Suggester.new
23
23
 
24
24
  @watcher = Watcher.new(@file_prefix, ignores, lambda do
25
25
  report
@@ -36,7 +36,7 @@ module Attractor
36
36
 
37
37
  def report
38
38
  @suggestions = @suggester.suggest
39
- @types = Hash[@calculators.map { |calc| [calc.first, calc.last.type] }]
39
+ @types = @calculators.map { |calc| [calc.first, calc.last.type] }.to_h
40
40
  end
41
41
 
42
42
  def render
@@ -19,7 +19,6 @@ module Attractor
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
24
  @watcher = Watcher.new(@file_prefix, ignores, lambda do
@@ -37,7 +36,7 @@ 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
@@ -3,26 +3,92 @@
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
26
+ end
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
25
49
  end
26
50
  end
51
+
52
+ class JSONFormatter
53
+ def call(calculators)
54
+ result = calculators.map do |calc|
55
+ type = calc.last.type
56
+ values = calc.last.calculate
57
+ suggester = Suggester.new(values)
58
+ to_be_refactored = suggester.suggest.map(&:file_path)
59
+
60
+ [
61
+ type, values.map do |value|
62
+ {
63
+ file_path: value.file_path,
64
+ score: value.score,
65
+ complexity: value.complexity,
66
+ churn: value.churn,
67
+ refactor: to_be_refactored.include?(value.file_path)
68
+ }
69
+ end
70
+ ]
71
+ end
72
+
73
+ puts result.to_h.to_json
74
+ end
75
+ end
76
+
77
+ def initialize(format:, **other)
78
+ super(**other)
79
+ @formatter = case format.to_sym
80
+ when :csv
81
+ CSVFormatter.new
82
+ when :json
83
+ JSONFormatter.new
84
+ else
85
+ TableFormatter.new
86
+ end
87
+ end
88
+
89
+ def report
90
+ super
91
+ @formatter.call(@calculators)
92
+ end
27
93
  end
28
94
  end
@@ -1,21 +1,67 @@
1
- require 'base_reporter'
1
+ # frozen_string_literal: true
2
2
 
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
+
13
+ calculators.each do |calc|
14
+ # e.g. ['js', JsCalculator']
15
+ puts calc.last.type
16
+
17
+ values = calc.last.calculate
18
+ suggester = Suggester.new(values)
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
25
+ end
26
+ end
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]
12
35
 
13
- puts @values.map(&:to_s)
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)
14
41
 
15
- puts
16
- puts 'Suggestions for refactorings:'
17
- @suggestions.each { |sug| puts sug.file_path }
18
- puts
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)
19
65
  end
20
66
  end
21
67
  end
@@ -14,10 +14,10 @@ module Attractor
14
14
  FileUtils.mkdir_p "./attractor_output/images"
15
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.write("./attractor_output/images/attractor_logo.svg", logo)
18
+ File.write("./attractor_output/images/attractor_favicon.png", favicon)
19
+ File.write("./attractor_output/stylesheets/main.css", css)
20
+ File.write("./attractor_output/javascripts/index.pack.js", javascript_pack)
21
21
 
22
22
  if @calculators.size > 1
23
23
  @calculators.each do |calc|
@@ -25,8 +25,8 @@ module Attractor
25
25
  suggester = Suggester.new(values(type: @short_type))
26
26
  @suggestions = suggester.suggest
27
27
 
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) }
28
+ File.write("./attractor_output/javascripts/index.#{@short_type}.js", javascript)
29
+ File.write("./attractor_output/index.#{@short_type}.html", render)
30
30
  puts "Generated HTML report at #{File.expand_path "./attractor_output/"}/index.#{@short_type}.html"
31
31
  end
32
32
 
@@ -35,8 +35,8 @@ module Attractor
35
35
  puts "Opening browser window..."
36
36
  end
37
37
  else
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) }
38
+ File.write("./attractor_output/javascripts/index.js", javascript)
39
+ File.write("./attractor_output/index.html", render)
40
40
  puts "Generated HTML report at #{File.expand_path "./attractor_output/index.html"}"
41
41
 
42
42
  if @open_browser
@@ -5,17 +5,17 @@ module Attractor
5
5
  class Suggester
6
6
  attr_accessor :values
7
7
 
8
- def initialize(values)
9
- @values = values || []
8
+ def initialize(values = [])
9
+ @values = values
10
10
  end
11
11
 
12
12
  def suggest(threshold = 95)
13
- products = @values.map { |val| val.churn * val.complexity }
13
+ products = @values.map(&:score)
14
14
  products.extend(DescriptiveStatistics)
15
15
  quantile = products.percentile(threshold.to_i)
16
16
 
17
- @values.select { |val| val.churn * val.complexity > quantile }
18
- .sort_by { |val| val.churn * val.complexity }.reverse
17
+ @values.select { |val| val.score > quantile }
18
+ .sort_by { |val| val.score }.reverse
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Attractor
4
+ # makes suggestions for refactorings
5
+ class Suggester
6
+ attr_accessor :values
7
+
8
+ def initialize(values)
9
+ @values = values || []
10
+ end
11
+
12
+ def suggest(threshold = 95)
13
+ products = @values.map(&:score)
14
+ products.extend(DescriptiveStatistics)
15
+ quantile = products.percentile(threshold.to_i)
16
+
17
+ @values.select { |val| val.score > quantile }
18
+ .sort_by { |val| val.score }.reverse
19
+ end
20
+ end
21
+ end
@@ -19,6 +19,10 @@ module Attractor
19
19
  history&.first&.first
20
20
  end
21
21
 
22
+ def score
23
+ @complexity * @churn
24
+ end
25
+
22
26
  def to_s
23
27
  format("%-64s%8.1f%8i", @file_path, @complexity, @churn)
24
28
  end
@@ -1,3 +1,3 @@
1
1
  module Attractor
2
- VERSION = "2.3.0"
2
+ VERSION = "2.5.0"
3
3
  end
@@ -0,0 +1,3 @@
1
+ module Attractor
2
+ VERSION = "2.4.0"
3
+ end
data/lib/attractor.rb CHANGED
@@ -43,9 +43,9 @@ module Attractor
43
43
  end
44
44
 
45
45
  def all_registered_calculators(options = {})
46
- Hash[@registry_entries.map do |type, entry|
46
+ @registry_entries.map do |type, entry|
47
47
  [type, entry.calculator_class.new(**options)] if entry.detector_class.new.detect
48
- end.compact]
48
+ end.compact.to_h
49
49
  end
50
50
 
51
51
  module_function :calculators_for_type
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attractor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Rubisch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-04-12 00:00:00.000000000 Z
11
+ date: 2023-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: churn
@@ -156,28 +156,28 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.2.0
159
+ version: 0.3.0
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.2.0
166
+ version: 0.3.0
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: attractor-ruby
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: 0.2.0
173
+ version: 0.3.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: 0.2.0
180
+ version: 0.3.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: autoprefixer-rails
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -371,35 +371,27 @@ files:
371
371
  - app/views/index.html.erb
372
372
  - exe/attractor
373
373
  - lib/attractor.rb
374
- - lib/attractor.rb~
375
374
  - lib/attractor/cache.rb
376
- - lib/attractor/cache.rb~
377
375
  - lib/attractor/calculators/base_calculator.rb
378
376
  - lib/attractor/calculators/base_calculator.rb~
379
377
  - lib/attractor/cli.rb
380
378
  - lib/attractor/cli.rb~
381
379
  - lib/attractor/detectors/base_detector.rb
382
- - lib/attractor/detectors/base_detector.rb~
383
380
  - lib/attractor/duration_parser.rb
384
- - lib/attractor/duration_parser.rb~
385
381
  - lib/attractor/gem_names.rb
386
- - lib/attractor/gem_names.rb~
387
382
  - lib/attractor/registry_entry.rb
388
- - lib/attractor/registry_entry.rb~
389
383
  - lib/attractor/reporters/base_reporter.rb
390
384
  - lib/attractor/reporters/base_reporter.rb~
391
385
  - lib/attractor/reporters/console_reporter.rb
392
386
  - lib/attractor/reporters/console_reporter.rb~
393
387
  - lib/attractor/reporters/html_reporter.rb
394
- - lib/attractor/reporters/html_reporter.rb~
395
388
  - lib/attractor/reporters/sinatra_reporter.rb
396
389
  - lib/attractor/suggester.rb
397
- - lib/attractor/tmp/churn/9f86bc9b7ec6bf59cbf7a111ce8b1159ae128347.json
390
+ - lib/attractor/suggester.rb~
398
391
  - lib/attractor/value.rb
399
- - lib/attractor/value.rb~
400
392
  - lib/attractor/version.rb
393
+ - lib/attractor/version.rb~
401
394
  - lib/attractor/watcher.rb
402
- - lib/attractor/watcher.rb~
403
395
  homepage: https://github.com/julianrubisch/attractor
404
396
  licenses:
405
397
  - MIT
@@ -423,7 +415,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
423
415
  - !ruby/object:Gem::Version
424
416
  version: '0'
425
417
  requirements: []
426
- rubygems_version: 3.2.3
418
+ rubygems_version: 3.4.12
427
419
  signing_key:
428
420
  specification_version: 4
429
421
  summary: Churn vs Complexity Chart Generator
@@ -1,67 +0,0 @@
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
- private
18
-
19
- def adapter
20
- @@adapter ||= CacheAdapter::JSON.instance
21
- end
22
- end
23
- end
24
-
25
- module CacheAdapter
26
- class Base
27
- include Singleton
28
- end
29
-
30
- class JSON < Base
31
- def initialize
32
- super
33
-
34
- @data_directory = "tmp"
35
- FileUtils.mkdir_p @data_directory
36
- FileUtils.touch filename
37
-
38
- begin
39
- @store = ::JSON.parse(File.read(filename))
40
- rescue ::JSON::ParserError
41
- @store = {}
42
- end
43
- end
44
-
45
- def read(file_path:)
46
- value_hash = @store[file_path]
47
-
48
- Value.new(**value_hash.values.first.transform_keys(&:to_sym)) unless value_hash.nil?
49
- rescue ArgumentError => e
50
- puts "Couldn't rehydrate value from cache: #{e.message}"
51
- nil
52
- end
53
-
54
- def write(file_path:, value:)
55
- mappings = {x: :churn, y: :complexity}
56
-
57
- transformed_value = value.to_h.transform_keys { |k| mappings[k] || k }
58
- @store[file_path] = {value.current_commit => transformed_value}
59
- File.write(filename, ::JSON.dump(@store))
60
- end
61
-
62
- def filename
63
- "#{@data_directory}/attractor-cache.json"
64
- end
65
- end
66
- end
67
- end
@@ -1,4 +0,0 @@
1
- module Attractor
2
- class BaseDetector
3
- end
4
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Attractor
4
- # converts a duration string into an amount of days
5
- class DurationParser
6
- TOKENS = {
7
- "d" => 1,
8
- "w" => 7,
9
- "m" => 30,
10
- "y" => 365
11
- }.freeze
12
-
13
- attr_reader :duration
14
-
15
- def initialize(input)
16
- @input = input
17
- @duration = 0
18
- parse
19
- end
20
-
21
- def parse
22
- @input.scan(/(\d+)(\w)/).each do |amount, measure|
23
- @duration += amount.to_i * TOKENS[measure]
24
- end
25
- end
26
- end
27
- end
@@ -1,23 +0,0 @@
1
- module Attractor
2
-
3
- # from https://github.com/prontolabs/pronto/blob/master/lib/pronto/gem_names.rb
4
- class GemNames
5
- def to_a
6
- gems.map { |gem| gem.name.sub(/^pronto-/, '') }.uniq.sort
7
- end
8
-
9
- private
10
-
11
- def gems
12
- Gem::Specification.find_all.select do |gem|
13
- if gem.name =~ /^pronto-/
14
- true
15
- elsif gem.name != 'pronto'
16
- runner_path = File.join(gem.full_gem_path,
17
- "lib/pronto/#{gem.name}.rb")
18
- File.exist?(runner_path)
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,2 +0,0 @@
1
- RegistryEntry = Struct.new :type, :detector, :calculator
2
-
@@ -1,48 +0,0 @@
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 +0,0 @@
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,30 +0,0 @@
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,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Attractor
4
- # functionality for watching file system changes
5
- class Watcher
6
- def initialize(file_prefix, callback)
7
- @file_prefix = file_prefix
8
- @callback = callback
9
- end
10
-
11
- def watch
12
- @callback.call
13
-
14
- listener = Listen.to(@file_prefix, ignore: /^attractor_output/) do |modified, _added, _removed|
15
- if modified
16
- puts "#{modified.map(&:to_s).join(', ')} modified, recalculating..."
17
- @callback.call
18
- end
19
- end
20
- listener.start
21
- sleep
22
- end
23
- end
24
- end
data/lib/attractor.rb~ DELETED
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "attractor/version"
4
- require "attractor/gem_names"
5
- require "attractor/duration_parser"
6
- require "attractor/calculators/base_calculator"
7
- require "attractor/detectors/base_detector"
8
- require "attractor/reporters/base_reporter"
9
- require "attractor/suggester"
10
- require "attractor/watcher"
11
- require "attractor/cache"
12
-
13
- Dir[File.join(__dir__, "attractor", "reporters", "*.rb")].sort.each do |file|
14
- next if file.start_with?("base")
15
-
16
- require file
17
- end
18
-
19
- module Attractor
20
- class Error < StandardError; end
21
-
22
- @registry_entries = {}
23
-
24
- def register(registry_entry)
25
- @registry_entries[registry_entry.type] = registry_entry
26
- end
27
-
28
- def calculators_for_type(type, **options)
29
- registry_entry_for_type = @registry_entries[type]
30
-
31
- return {type => registry_entry_for_type.calculator_class.new(**options)} if type
32
-
33
- Hash[@registry_entries.map do |type, entry|
34
- [type, entry.calculator_class.new(**options)] if entry.detector_class.new.detect
35
- end.compact]
36
- end
37
-
38
- module_function :calculators_for_type
39
- module_function :register
40
- end
41
-
42
- Attractor::GemNames.new.to_a.each do |gem_name|
43
- require "attractor/#{gem_name}"
44
- end