attractor 2.0.3 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +33 -6
- data/Rakefile +13 -13
- data/app/assets/javascripts/index.pack.js +1 -1
- data/app/assets/stylesheets/main.css +1 -1
- data/exe/attractor +1 -1
- data/lib/attractor.rb +29 -13
- data/lib/attractor.rb~ +44 -0
- data/lib/attractor/cache.rb +82 -0
- data/lib/attractor/cache.rb~ +67 -0
- data/lib/attractor/calculators/base_calculator.rb +38 -14
- data/lib/attractor/calculators/base_calculator.rb~ +64 -0
- data/lib/attractor/cli.rb +48 -44
- data/lib/attractor/cli.rb~ +84 -0
- data/lib/attractor/detectors/base_detector.rb~ +4 -0
- data/lib/attractor/duration_parser.rb +7 -5
- data/lib/attractor/duration_parser.rb~ +27 -0
- data/lib/attractor/gem_names.rb +3 -1
- data/lib/attractor/gem_names.rb~ +23 -0
- data/lib/attractor/registry_entry.rb +0 -1
- data/lib/attractor/registry_entry.rb~ +2 -0
- data/lib/attractor/reporters/base_reporter.rb +22 -14
- data/lib/attractor/reporters/base_reporter.rb~ +54 -0
- data/lib/attractor/reporters/console_reporter.rb +4 -4
- data/lib/attractor/reporters/console_reporter.rb~ +21 -0
- data/lib/attractor/reporters/html_reporter.rb +23 -25
- data/lib/attractor/reporters/html_reporter.rb~ +48 -0
- data/lib/attractor/reporters/sinatra_reporter.rb +18 -24
- data/lib/attractor/suggester.rb +3 -1
- data/lib/attractor/tmp/churn/9f86bc9b7ec6bf59cbf7a111ce8b1159ae128347.json +1 -0
- data/lib/attractor/value.rb +8 -4
- data/lib/attractor/value.rb~ +30 -0
- data/lib/attractor/version.rb +1 -1
- data/lib/attractor/watcher.rb +7 -3
- data/lib/attractor/watcher.rb~ +24 -0
- metadata +29 -31
- data/.babelrc +0 -4
- data/.eslintrc.json +0 -25
- data/.gitignore +0 -20
- data/.rubocop.yml +0 -2
- data/CHANGELOG.md +0 -99
- data/Gemfile +0 -4
- data/Guardfile +0 -27
- data/attractor.gemspec +0 -65
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/standardize +0 -4
- data/bin/test +0 -6
- data/dist/calculator.bundle.js +0 -1
- data/lib/attractor/reporters/.keep +0 -0
- data/package-lock.json +0 -6906
- data/package.json +0 -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
|
3
|
+
require "thor"
|
4
4
|
|
5
|
-
require
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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:
|
17
|
-
|
18
|
-
|
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
|
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
|
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
|
-
|
35
|
-
|
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
|
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
|
-
|
52
|
-
|
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
|
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
|
-
|
74
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
@@ -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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
data/lib/attractor/gem_names.rb
CHANGED
@@ -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-/,
|
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
|
@@ -1,34 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
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 :
|
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:
|
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 =>
|
28
|
-
raise
|
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
|
-
|
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
|