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.
- 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
|