attractor 2.0.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "churn/calculator"
4
+
5
+ require "attractor/value"
6
+
7
+ module Attractor
8
+ # calculates churn and complexity
9
+ class BaseCalculator
10
+ attr_reader :type
11
+
12
+ def initialize(file_prefix: "", ignores: "", file_extension: "rb", minimum_churn_count: 3, start_ago: "5y")
13
+ @file_prefix = file_prefix
14
+ @file_extension = file_extension
15
+ @minimum_churn_count = minimum_churn_count
16
+ @start_date = Date.today - Attractor::DurationParser.new(start_ago).duration
17
+ @ignores = ignores
18
+ end
19
+
20
+ def calculate
21
+ churn = ::Churn::ChurnCalculator.new(
22
+ file_extension: @file_extension,
23
+ file_prefix: @file_prefix,
24
+ minimum_churn_count: @minimum_churn_count,
25
+ start_date: @start_date,
26
+ ignores: @ignores
27
+ ).report(false)
28
+
29
+ churn[:churn][:changes].map do |change|
30
+ history = git_history_for_file(file_path: change[:file_path])
31
+ commit = history&.first&.first
32
+
33
+ cached_value = Cache.read(file_path: change[:file_path])
34
+
35
+ if !cached_value.nil? && !cached_value.current_commit.nil? && cached_value.current_commit == commit
36
+ value = cached_value
37
+ else
38
+ complexity, details = yield(change)
39
+
40
+ value = Value.new(file_path: change[:file_path],
41
+ churn: change[:times_changed],
42
+ complexity: complexity,
43
+ details: details,
44
+ history: history)
45
+ Cache.write(file_path: change[:file_path], value: value)
46
+ end
47
+
48
+ value
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def git_history_for_file(file_path:, limit: 10)
55
+ history = `git log --oneline -n #{limit} -- #{file_path}`
56
+ history.split("\n")
57
+ .map do |log_entry|
58
+ log_entry.partition(/\A(\S+)\s/)
59
+ .map(&:strip)
60
+ .reject(&:empty?)
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/attractor/cli.rb CHANGED
@@ -1,95 +1,99 @@
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
32
48
  def calc
33
49
  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
50
+
51
+ report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options))
40
52
  rescue RuntimeError => e
41
53
  puts "Runtime error: #{e.message}"
42
54
  end
43
55
 
44
- desc 'report', 'Generates an HTML report'
56
+ desc "report", "Generates an HTML report"
45
57
  (shared_options + advanced_options).each do |option|
46
58
  option(*option)
47
59
  end
48
60
  def report
49
61
  file_prefix = options[:file_prefix]
50
62
  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
63
+
64
+ report! Attractor::HtmlReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
62
65
  rescue RuntimeError => e
63
66
  puts "Runtime error: #{e.message}"
64
67
  end
65
68
 
66
- desc 'serve', 'Serves the report on localhost'
69
+ desc "serve", "Serves the report on localhost"
67
70
  (shared_options + advanced_options).each do |option|
68
71
  option(*option)
69
72
  end
70
73
  def serve
71
74
  file_prefix = options[:file_prefix]
72
75
  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
76
+
77
+ report! Attractor::SinatraReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
84
78
  end
85
79
 
86
80
  private
87
81
 
88
82
  def calculators(options)
89
83
  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])
84
+ file_prefix: options[:file_prefix],
85
+ minimum_churn_count: options[:minimum_churn],
86
+ ignores: options[:ignore],
87
+ start_ago: options[:start_ago])
88
+ end
89
+
90
+ def report!(reporter)
91
+ if options[:watch]
92
+ puts "Listening for file changes..."
93
+ reporter.watch
94
+ else
95
+ reporter.report
96
+ end
93
97
  end
94
98
  end
95
99
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ require "attractor"
6
+
7
+ module Attractor
8
+ # contains methods implementing the CLI
9
+ class CLI < Thor
10
+ shared_options = [[:file_prefix, aliases: :p],
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]]
16
+
17
+ advanced_options = [[:format, aliases: :f, default: "html"],
18
+ [:no_open_browser, type: :boolean],
19
+ [:ci, type: :boolean]]
20
+
21
+ desc "version", "Prints Attractor's version information"
22
+ map %w[-v --version] => :version
23
+ def version
24
+ puts "Attractor version #{Attractor::VERSION}"
25
+ rescue RuntimeError => e
26
+ puts "Runtime error: #{e.message}"
27
+ end
28
+
29
+ desc "calc", "Calculates churn and complexity for all ruby files in current directory"
30
+ shared_options.each do |shared_option|
31
+ option(*shared_option)
32
+ end
33
+ def calc
34
+ file_prefix = options[:file_prefix]
35
+
36
+ report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options))
37
+ rescue RuntimeError => e
38
+ puts "Runtime error: #{e.message}"
39
+ end
40
+
41
+ desc "report", "Generates an HTML report"
42
+ (shared_options + advanced_options).each do |option|
43
+ option(*option)
44
+ end
45
+ def report
46
+ file_prefix = options[:file_prefix]
47
+ open_browser = !(options[:no_open_browser] || options[:ci])
48
+
49
+ report! Attractor::HtmlReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
50
+ rescue RuntimeError => e
51
+ puts "Runtime error: #{e.message}"
52
+ end
53
+
54
+ desc "serve", "Serves the report on localhost"
55
+ (shared_options + advanced_options).each do |option|
56
+ option(*option)
57
+ end
58
+ def serve
59
+ file_prefix = options[:file_prefix]
60
+ open_browser = !(options[:no_open_browser] || options[:ci])
61
+
62
+ report! Attractor::SinatraReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
63
+ end
64
+
65
+ private
66
+
67
+ def calculators(options)
68
+ Attractor.calculators_for_type(options[:type],
69
+ file_prefix: options[:file_prefix],
70
+ minimum_churn_count: options[:minimum_churn],
71
+ ignores: options[:ignore],
72
+ start_ago: options[:start_ago])
73
+ end
74
+
75
+ def report!(reporter)
76
+ if options[:watch]
77
+ puts "Listening for file changes..."
78
+ reporter.watch
79
+ else
80
+ reporter.report
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,4 @@
1
+ module Attractor
2
+ class BaseDetector
3
+ end
4
+ 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
 
@@ -0,0 +1,27 @@
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
@@ -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
@@ -10,6 +10,8 @@ module Attractor
10
10
  def gems
11
11
  Gem::Specification.find_all.select do |gem|
12
12
  gem.name =~ /^attractor-/
13
+ end.reject do |gem|
14
+ gem.name =~ /attractor-rails/
13
15
  end
14
16
  end
15
17
  end
@@ -0,0 +1,23 @@
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
@@ -9,4 +9,3 @@ module Attractor
9
9
  end
10
10
  end
11
11
  end
12
-
@@ -0,0 +1,2 @@
1
+ RegistryEntry = Struct.new :type, :detector, :calculator
2
+
@@ -1,34 +1,35 @@
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
11
11
  class BaseReporter
12
12
  extend Forwardable
13
- attr_accessor :values, :file_prefix
13
+ attr_accessor :file_prefix
14
14
  attr_reader :types
15
+ attr_writer :values
15
16
  def_delegator :@watcher, :watch
16
17
 
17
- def initialize(file_prefix: '', calculators:, open_browser: true)
18
- @file_prefix = file_prefix
18
+ def initialize(calculators:, file_prefix: "", ignores: "", open_browser: true)
19
+ @file_prefix = file_prefix || ""
19
20
  @calculators = calculators
20
21
  @open_browser = open_browser
21
- @values = @calculators.first.last.calculate
22
22
  @suggester = Suggester.new(values)
23
23
 
24
- @watcher = Watcher.new(file_prefix, lambda do
24
+ @watcher = Watcher.new(@file_prefix, ignores, lambda do
25
25
  report
26
26
  end)
27
- rescue NoMethodError => e
28
- raise 'There was a problem gathering churn changes'
27
+ rescue NoMethodError => _e
28
+ raise "There was a problem gathering churn changes"
29
29
  end
30
30
 
31
- def suggestions(quantile)
31
+ def suggestions(quantile:, type: "rb")
32
+ @suggester.values = values(type: type)
32
33
  @suggestions = @suggester.suggest(quantile)
33
34
  @suggestions
34
35
  end
@@ -39,7 +40,14 @@ module Attractor
39
40
  end
40
41
 
41
42
  def render
42
- 'Attractor'
43
+ "Attractor"
44
+ end
45
+
46
+ def values(type: "rb")
47
+ @values = @calculators[type].calculate
48
+ @values
49
+ rescue NoMethodError => _e
50
+ puts "No calculator for type #{type}"
43
51
  end
44
52
  end
45
53
  end