attractor 2.2.0 → 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 +18 -0
- data/lib/attractor.rb +16 -0
- data/lib/attractor.rb~ +1 -0
- data/lib/attractor/cache.rb +15 -0
- data/lib/attractor/cache.rb~ +67 -0
- data/lib/attractor/calculators/base_calculator.rb +10 -1
- data/lib/attractor/calculators/base_calculator.rb~ +18 -6
- data/lib/attractor/cli.rb +15 -0
- data/lib/attractor/cli.rb~ +84 -0
- data/lib/attractor/duration_parser.rb +3 -1
- data/lib/attractor/duration_parser.rb~ +20 -16
- data/lib/attractor/reporters/base_reporter.rb +0 -1
- data/lib/attractor/version.rb +1 -1
- metadata +3 -3
- data/lib/attractor/#duration_parser.rb# +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b116bbaa914ca032391378e04b8e41f27b28fa4feaca3fc50c93053d1954a15a
|
4
|
+
data.tar.gz: 3e5620d661d19d3d2308457921389ce78b4ba3b844a6a4d3dd33c1ef9d093a56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '092ff47f81f9d6f0e7fa025d21a89501413e1dc8c7044d5420d4db8ea04f72fc6554840a9656b0ac002c2f0c25ef3f0fa9c1204214d4b29a284eabb565428f82'
|
7
|
+
data.tar.gz: 83ad8a7dafa969e68c4db77629663738513137a3f91ad058451d9a37c1ed39aff1cfea785c34042d543b7caac8ec625207cf3701c7003d63a23f631cbba109b5
|
data/README.md
CHANGED
@@ -186,6 +186,17 @@ attractor:
|
|
186
186
|
|
187
187
|
## CLI Commands and Options
|
188
188
|
|
189
|
+
Initialize the local cache:
|
190
|
+
|
191
|
+
```sh
|
192
|
+
attractor init
|
193
|
+
--file_prefix|-p app/models
|
194
|
+
--type|-t rb|js
|
195
|
+
--start_ago|-s (e.g. 5y, 3m, 7w)
|
196
|
+
--minimum_churn|-c (minimum times a file must have changed to be processed)
|
197
|
+
--ignore|-i 'spec/*_spec.rb,db/schema.rb,tmp'
|
198
|
+
```
|
199
|
+
|
189
200
|
Print a simple output to console:
|
190
201
|
|
191
202
|
```sh
|
@@ -223,6 +234,13 @@ attractor serve
|
|
223
234
|
--ignore|-i 'spec/*_spec.rb,db/schema.rb,tmp'
|
224
235
|
```
|
225
236
|
|
237
|
+
Clear the local cache:
|
238
|
+
|
239
|
+
```sh
|
240
|
+
attractor clean
|
241
|
+
```
|
242
|
+
|
243
|
+
|
226
244
|
## Development
|
227
245
|
|
228
246
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/attractor.rb
CHANGED
@@ -21,6 +21,15 @@ module Attractor
|
|
21
21
|
|
22
22
|
@registry_entries = {}
|
23
23
|
|
24
|
+
def init(calculators)
|
25
|
+
calculators ||= all_registered_calculators
|
26
|
+
calculators.to_a.map(&:last).each(&:calculate)
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear
|
30
|
+
Cache.clear
|
31
|
+
end
|
32
|
+
|
24
33
|
def register(registry_entry)
|
25
34
|
@registry_entries[registry_entry.type] = registry_entry
|
26
35
|
end
|
@@ -30,13 +39,20 @@ module Attractor
|
|
30
39
|
|
31
40
|
return {type => registry_entry_for_type.calculator_class.new(**options)} if type
|
32
41
|
|
42
|
+
all_registered_calculators(**options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def all_registered_calculators(options = {})
|
33
46
|
Hash[@registry_entries.map do |type, entry|
|
34
47
|
[type, entry.calculator_class.new(**options)] if entry.detector_class.new.detect
|
35
48
|
end.compact]
|
36
49
|
end
|
37
50
|
|
38
51
|
module_function :calculators_for_type
|
52
|
+
module_function :all_registered_calculators
|
39
53
|
module_function :register
|
54
|
+
module_function :init
|
55
|
+
module_function :clear
|
40
56
|
end
|
41
57
|
|
42
58
|
Attractor::GemNames.new.to_a.each do |gem_name|
|
data/lib/attractor.rb~
CHANGED
@@ -8,6 +8,7 @@ require "attractor/detectors/base_detector"
|
|
8
8
|
require "attractor/reporters/base_reporter"
|
9
9
|
require "attractor/suggester"
|
10
10
|
require "attractor/watcher"
|
11
|
+
require "attractor/cache"
|
11
12
|
|
12
13
|
Dir[File.join(__dir__, "attractor", "reporters", "*.rb")].sort.each do |file|
|
13
14
|
next if file.start_with?("base")
|
data/lib/attractor/cache.rb
CHANGED
@@ -14,6 +14,14 @@ module Attractor
|
|
14
14
|
adapter.write(file_path: file_path, value: value)
|
15
15
|
end
|
16
16
|
|
17
|
+
def persist!
|
18
|
+
adapter.persist!
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear
|
22
|
+
adapter.clear
|
23
|
+
end
|
24
|
+
|
17
25
|
private
|
18
26
|
|
19
27
|
def adapter
|
@@ -56,9 +64,16 @@ module Attractor
|
|
56
64
|
|
57
65
|
transformed_value = value.to_h.transform_keys { |k| mappings[k] || k }
|
58
66
|
@store[file_path] = {value.current_commit => transformed_value}
|
67
|
+
end
|
68
|
+
|
69
|
+
def persist!
|
59
70
|
File.write(filename, ::JSON.dump(@store))
|
60
71
|
end
|
61
72
|
|
73
|
+
def clear
|
74
|
+
FileUtils.rm filename
|
75
|
+
end
|
76
|
+
|
62
77
|
def filename
|
63
78
|
"#{@data_directory}/attractor-cache.json"
|
64
79
|
end
|
data/lib/attractor/cache.rb~
CHANGED
@@ -0,0 +1,67 @@
|
|
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
|
@@ -26,7 +26,9 @@ module Attractor
|
|
26
26
|
ignores: @ignores
|
27
27
|
).report(false)
|
28
28
|
|
29
|
-
churn[:churn][:changes].
|
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
|
|
@@ -45,8 +47,15 @@ module Attractor
|
|
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
|
@@ -27,12 +27,24 @@ module Attractor
|
|
27
27
|
).report(false)
|
28
28
|
|
29
29
|
churn[:churn][:changes].map do |change|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
+
|
36
48
|
value
|
37
49
|
end
|
38
50
|
end
|
data/lib/attractor/cli.rb
CHANGED
@@ -26,6 +26,21 @@ 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)
|
@@ -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
|
@@ -1,23 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attractor
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
8
12
|
|
9
|
-
|
13
|
+
attr_reader :duration
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def initialize(input)
|
16
|
+
@input = input
|
17
|
+
@duration = 0
|
18
|
+
parse
|
19
|
+
end
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
def parse
|
22
|
+
@input.scan(/(\d+)(\w)/).each do |amount, measure|
|
23
|
+
@duration += amount.to_i * TOKENS[measure]
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
23
|
-
end
|
data/lib/attractor/version.rb
CHANGED
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.
|
4
|
+
version: 2.3.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-
|
11
|
+
date: 2021-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: churn
|
@@ -372,12 +372,12 @@ files:
|
|
372
372
|
- exe/attractor
|
373
373
|
- lib/attractor.rb
|
374
374
|
- lib/attractor.rb~
|
375
|
-
- lib/attractor/#duration_parser.rb#
|
376
375
|
- lib/attractor/cache.rb
|
377
376
|
- lib/attractor/cache.rb~
|
378
377
|
- lib/attractor/calculators/base_calculator.rb
|
379
378
|
- lib/attractor/calculators/base_calculator.rb~
|
380
379
|
- lib/attractor/cli.rb
|
380
|
+
- lib/attractor/cli.rb~
|
381
381
|
- lib/attractor/detectors/base_detector.rb
|
382
382
|
- lib/attractor/detectors/base_detector.rb~
|
383
383
|
- lib/attractor/duration_parser.rb
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Attractor
|
2
|
-
class DurationParser
|
3
|
-
TOKENS = {
|
4
|
-
'd" => 1,
|
5
|
-
w" => 7,
|
6
|
-
m" => 30,
|
7
|
-
y" => 365
|
8
|
-
}
|
9
|
-
|
10
|
-
attr_reader :duration
|
11
|
-
|
12
|
-
def initialize(input)
|
13
|
-
@input = input
|
14
|
-
@duration = 0
|
15
|
-
parse
|
16
|
-
end
|
17
|
-
|
18
|
-
def parse
|
19
|
-
@input.scan(/(\d+)(\w)/).each do |amount, measure|
|
20
|
-
@duration += amount.to_i * TOKENS[measure]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|