attractor 0.1.2 → 0.2.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/.gitignore +1 -0
- data/CHANGELOG.md +3 -0
- data/Guardfile +20 -0
- data/README.md +7 -3
- data/Rakefile +1 -0
- data/attractor.gemspec +4 -0
- data/lib/attractor.rb +3 -1
- data/lib/attractor/calculator.rb +11 -36
- data/lib/attractor/cli.rb +19 -8
- data/lib/attractor/reporter.rb +62 -0
- data/lib/attractor/suggester.rb +18 -0
- data/lib/attractor/version.rb +1 -1
- data/lib/attractor/watcher.rb +24 -0
- metadata +63 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 907c4dcfeef010d2233dec58fd6a619ee0b6f4dcca2a69149180c8511e177aa6
|
4
|
+
data.tar.gz: 3ed065a23ee2b46ed6028f6e226d9ce75c04f0b7d4e3b46e12b7ac50db9fa538
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d37d2106a2c6675fc1132b04265376533230a34e4fc6a43e923f92b161451374d3b0db548a46c2a6fca62f721b1fa9d8bcb9a8f52833307a7d4734a240d1cac
|
7
|
+
data.tar.gz: 51e134b833023b6087516d75abed90eff25f47471f2635f27c3be0cdb2c3e6f130e199a5b9509f78db8ece25ccc1b5f7013403ba11add8510d9c7b0078beb275
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
guard 'rake', :task => 'install' do
|
19
|
+
watch(%r{^lib/.+\.rb})
|
20
|
+
end
|
data/README.md
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
|
3
3
|

|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
TODO: Delete this and the text above, and describe your gem
|
5
|
+
Many authors ([Michael Feathers](https://www.agileconnection.com/article/getting-empirical-about-refactoring), [Sandi Metz](https://www.sandimetz.com/blog/2017/9/13/breaking-up-the-behemoth)) have shown that an evaluation of churn vs complexity of files in software projects provide a valuable metric towards code quality. This is another take on the matter, for ruby code, using the `churn` and `flog` projects.
|
8
6
|
|
9
7
|
## Installation
|
10
8
|
|
@@ -36,17 +34,23 @@ Or shorter:
|
|
36
34
|
|
37
35
|
$ attractor report -p app/models
|
38
36
|
|
37
|
+
Watch for file changes:
|
38
|
+
|
39
|
+
$ attractor report -p app/models --watch
|
40
|
+
|
39
41
|
## CLI Commands and Options
|
40
42
|
|
41
43
|
Print a simple output to console:
|
42
44
|
|
43
45
|
$ attractor calc
|
44
46
|
$ --file_prefix|-p app/models
|
47
|
+
$ --watch|-w
|
45
48
|
|
46
49
|
Generate a full report
|
47
50
|
|
48
51
|
$ attractor report
|
49
52
|
$ --file_prefix|-p app/models
|
53
|
+
$ --watch|-w
|
50
54
|
|
51
55
|
## Development
|
52
56
|
|
data/Rakefile
CHANGED
data/attractor.gemspec
CHANGED
@@ -36,13 +36,17 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency 'churn', '>= 1.0.4'
|
37
37
|
spec.add_dependency 'descriptive_statistics'
|
38
38
|
spec.add_dependency 'flog', '~> 4.0'
|
39
|
+
spec.add_dependency 'listen', '~> 3.0'
|
39
40
|
spec.add_dependency 'thor'
|
40
41
|
spec.add_dependency 'tilt'
|
41
42
|
|
42
43
|
spec.add_development_dependency 'aruba'
|
43
44
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
44
45
|
spec.add_development_dependency 'cucumber'
|
46
|
+
spec.add_development_dependency 'guard'
|
47
|
+
spec.add_development_dependency 'guard-rake'
|
45
48
|
spec.add_development_dependency 'pry'
|
46
49
|
spec.add_development_dependency 'rake', '~> 10.0'
|
47
50
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
51
|
+
spec.add_development_dependency 'structured_changelog'
|
48
52
|
end
|
data/lib/attractor.rb
CHANGED
data/lib/attractor/calculator.rb
CHANGED
@@ -3,21 +3,25 @@
|
|
3
3
|
require 'churn/calculator'
|
4
4
|
require 'csv'
|
5
5
|
require 'date'
|
6
|
-
require 'descriptive_statistics/safe'
|
7
6
|
require 'flog'
|
8
|
-
require '
|
9
|
-
require 'tilt'
|
7
|
+
require 'listen'
|
10
8
|
|
11
9
|
require 'attractor/value'
|
12
10
|
|
13
11
|
module Attractor
|
14
12
|
# calculates churn and complexity
|
15
13
|
class Calculator
|
16
|
-
def
|
14
|
+
def initialize(file_prefix: '')
|
15
|
+
@file_prefix = file_prefix
|
16
|
+
@file_extension = 'rb'
|
17
|
+
@minimum_churn_count = 3
|
18
|
+
end
|
19
|
+
|
20
|
+
def calculate
|
17
21
|
churn = ::Churn::ChurnCalculator.new(
|
18
|
-
file_extension: file_extension,
|
19
|
-
file_prefix: file_prefix,
|
20
|
-
minimum_churn_count: minimum_churn_count,
|
22
|
+
file_extension: @file_extension,
|
23
|
+
file_prefix: @file_prefix,
|
24
|
+
minimum_churn_count: @minimum_churn_count,
|
21
25
|
start_date: Date.today - 365 * 5
|
22
26
|
).report(false)
|
23
27
|
|
@@ -28,34 +32,5 @@ module Attractor
|
|
28
32
|
Value.new(file_path: change[:file_path], churn: change[:times_changed], complexity: complexity)
|
29
33
|
end
|
30
34
|
end
|
31
|
-
|
32
|
-
def self.output_console(file_prefix: '')
|
33
|
-
values = calculate(file_prefix: file_prefix)
|
34
|
-
puts values.map(&:to_s)
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.report(format: 'html', file_prefix: '')
|
38
|
-
@values = calculate(file_prefix: file_prefix)
|
39
|
-
|
40
|
-
@suggestions = get_suggestions(@values)
|
41
|
-
|
42
|
-
template = Tilt.new(File.expand_path('../templates/index.html.erb', __dir__))
|
43
|
-
output = template.render self
|
44
|
-
|
45
|
-
FileUtils.mkdir_p './attractor_output'
|
46
|
-
|
47
|
-
case format
|
48
|
-
when 'html'
|
49
|
-
File.open('./attractor_output/index.html', 'w') { |file| file.write(output) }
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.get_suggestions(values)
|
54
|
-
products = values.map { |val| val.churn * val.complexity }
|
55
|
-
products.extend(DescriptiveStatistics)
|
56
|
-
top_95_quantile = products.percentile(95)
|
57
|
-
|
58
|
-
values.select { |val| val.churn * val.complexity > top_95_quantile }
|
59
|
-
end
|
60
35
|
end
|
61
36
|
end
|
data/lib/attractor/cli.rb
CHANGED
@@ -9,21 +9,32 @@ module Attractor
|
|
9
9
|
class CLI < Thor
|
10
10
|
desc 'calc', 'Calculates churn and complexity for all ruby files in current directory'
|
11
11
|
option :file_prefix, aliases: :p
|
12
|
+
option :watch, aliases: :w, type: :boolean
|
12
13
|
def calc
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
if options[:watch]
|
15
|
+
puts 'Listening for file changes...'
|
16
|
+
Attractor::ConsoleReporter.new(file_prefix: options[:file_prefix]).watch
|
17
|
+
else
|
18
|
+
Attractor::ConsoleReporter.new(file_prefix: options[:file_prefix]).report
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
desc 'report', 'Generates an HTML report'
|
21
23
|
option :format, aliases: :f, default: 'html'
|
22
24
|
option :file_prefix, aliases: :p
|
25
|
+
option :watch, aliases: :w, type: :boolean
|
23
26
|
def report
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
if options[:watch]
|
28
|
+
puts 'Listening for file changes...'
|
29
|
+
Attractor::HtmlReporter.new(file_prefix: options[:file_prefix]).watch
|
30
|
+
else
|
31
|
+
case options[:format]
|
32
|
+
when 'html'
|
33
|
+
Attractor::HtmlReporter.new(file_prefix: options[:file_prefix]).report
|
34
|
+
else
|
35
|
+
Attractor::HtmlReporter.new(file_prefix: options[:file_prefix]).report
|
36
|
+
end
|
37
|
+
end
|
27
38
|
end
|
28
39
|
end
|
29
40
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'descriptive_statistics/safe'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tilt'
|
6
|
+
|
7
|
+
module Attractor
|
8
|
+
# base reporter
|
9
|
+
class Reporter
|
10
|
+
extend Forwardable
|
11
|
+
attr_accessor :values
|
12
|
+
def_delegator :@watcher, :watch
|
13
|
+
|
14
|
+
def initialize(file_prefix: '')
|
15
|
+
@calculator = Calculator.new(file_prefix: file_prefix)
|
16
|
+
@values = @calculator.calculate
|
17
|
+
@suggester = Suggester.new(values)
|
18
|
+
|
19
|
+
@watcher = Watcher.new(file_prefix, lambda do
|
20
|
+
report
|
21
|
+
end)
|
22
|
+
end
|
23
|
+
|
24
|
+
def report
|
25
|
+
@suggestions = @suggester.suggest
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# console reporter
|
30
|
+
class ConsoleReporter < Reporter
|
31
|
+
def report
|
32
|
+
super
|
33
|
+
puts 'Calculated churn and complexity'
|
34
|
+
puts
|
35
|
+
puts "file_path#{' ' * 53}complexity churn"
|
36
|
+
puts '-' * 80
|
37
|
+
|
38
|
+
puts @values.map(&:to_s)
|
39
|
+
|
40
|
+
puts
|
41
|
+
puts 'Suggestions for refactorings:'
|
42
|
+
@suggestions.each { |sug| puts sug.file_path }
|
43
|
+
puts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# HTML reporter
|
48
|
+
class HtmlReporter < Reporter
|
49
|
+
def report
|
50
|
+
super
|
51
|
+
|
52
|
+
puts 'Generating an HTML report'
|
53
|
+
template = Tilt.new(File.expand_path('../templates/index.html.erb', __dir__))
|
54
|
+
output = template.render self
|
55
|
+
|
56
|
+
FileUtils.mkdir_p './attractor_output'
|
57
|
+
|
58
|
+
File.open('./attractor_output/index.html', 'w') { |file| file.write(output) }
|
59
|
+
puts "Generated HTML report at #{File.expand_path './attractor_output/index.html'}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attractor
|
4
|
+
# makes suggestions for refactorings
|
5
|
+
class Suggester
|
6
|
+
def initialize(values)
|
7
|
+
@values = values
|
8
|
+
end
|
9
|
+
|
10
|
+
def suggest
|
11
|
+
products = @values.map { |val| val.churn * val.complexity }
|
12
|
+
products.extend(DescriptiveStatistics)
|
13
|
+
top_95_quantile = products.percentile(95)
|
14
|
+
|
15
|
+
@values.select { |val| val.churn * val.complexity > top_95_quantile }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/attractor/version.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
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
|
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: 0.
|
4
|
+
version: 0.2.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: 2019-09-
|
11
|
+
date: 2019-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: churn
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: listen
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: thor
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,34 @@ dependencies:
|
|
122
136
|
- - ">="
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: guard
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: guard-rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
125
167
|
- !ruby/object:Gem::Dependency
|
126
168
|
name: pry
|
127
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +206,20 @@ dependencies:
|
|
164
206
|
- - "~>"
|
165
207
|
- !ruby/object:Gem::Version
|
166
208
|
version: '3.0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: structured_changelog
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
167
223
|
description: |2
|
168
224
|
Many authors (Michael Feathers, Sandi Metz) have shown that an evaluation of
|
169
225
|
churn vs complexity of files in software projects provide a valuable metric
|
@@ -180,7 +236,9 @@ files:
|
|
180
236
|
- ".rspec"
|
181
237
|
- ".rubocop.yml"
|
182
238
|
- ".travis.yml"
|
239
|
+
- CHANGELOG.md
|
183
240
|
- Gemfile
|
241
|
+
- Guardfile
|
184
242
|
- LICENSE
|
185
243
|
- README.md
|
186
244
|
- Rakefile
|
@@ -191,8 +249,11 @@ files:
|
|
191
249
|
- lib/attractor.rb
|
192
250
|
- lib/attractor/calculator.rb
|
193
251
|
- lib/attractor/cli.rb
|
252
|
+
- lib/attractor/reporter.rb
|
253
|
+
- lib/attractor/suggester.rb
|
194
254
|
- lib/attractor/value.rb
|
195
255
|
- lib/attractor/version.rb
|
256
|
+
- lib/attractor/watcher.rb
|
196
257
|
- lib/templates/index.html.erb
|
197
258
|
homepage: https://github.com/julianrubisch/attractor
|
198
259
|
licenses: []
|