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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be5d6855eaab1440883504f3814c62f5cfae2dad252cdd567624ad8faaeed15e
4
- data.tar.gz: 68a7a71d2ef7736c780b906765748ff3fd8e0ca8733ef5c8ffa033f97ff10214
3
+ metadata.gz: 907c4dcfeef010d2233dec58fd6a619ee0b6f4dcca2a69149180c8511e177aa6
4
+ data.tar.gz: 3ed065a23ee2b46ed6028f6e226d9ce75c04f0b7d4e3b46e12b7ac50db9fa538
5
5
  SHA512:
6
- metadata.gz: f0d7d82657c9a6f82c28ccb5f413875d3a82b9d7d36bd52a5c5cf5df2f831e5022bf5ae465c2214c84013115c85ca85c956680fb3ccb2739260bad1a7da60347
7
- data.tar.gz: b026b51c657207e7ce95c350c0ab98488ad211f3ed22a64a3be038316643def27dbdd1ff3731decc0690c32d8b4e8603c55055239c3a94df1d0d01588175dbd8
6
+ metadata.gz: 2d37d2106a2c6675fc1132b04265376533230a34e4fc6a43e923f92b161451374d3b0db548a46c2a6fca62f721b1fa9d8bcb9a8f52833307a7d4734a240d1cac
7
+ data.tar.gz: 51e134b833023b6087516d75abed90eff25f47471f2635f27c3be0cdb2c3e6f130e199a5b9509f78db8ece25ccc1b5f7013403ba11add8510d9c7b0078beb275
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  .rspec_status
14
14
 
15
15
  *.swp
16
+ Gemfile.lock
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## RELEASE 0.2.0
2
+
3
+ * FEATURE: add file watching functionality
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
  ![image](https://user-images.githubusercontent.com/4352208/64156443-1573a600-ce35-11e9-9422-265012e93a91.png)
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/attractor`. To experiment with that code, run `bin/console` for an interactive prompt.
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
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require 'structured_changelog/tasks'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
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
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'attractor/version'
4
4
  require 'attractor/calculator'
5
- # require_relative 'attractor/calculator.rb'
5
+ require 'attractor/reporter'
6
+ require 'attractor/suggester'
7
+ require 'attractor/watcher'
6
8
 
7
9
  module Attractor
8
10
  class Error < StandardError; end
@@ -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 'fileutils'
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 self.calculate(file_extension: 'rb', minimum_churn_count: 3, file_prefix: '')
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
- puts 'Calculated churn and complexity'
14
- puts
15
- puts "file_path#{' ' * 53}complexity churn"
16
- puts '-' * 80
17
- Attractor::Calculator.output_console(file_prefix: options[:file_prefix])
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
- puts 'Generating an HTML report'
25
- Attractor::Calculator.report(format: options[:format], file_prefix: options[:file_prefix])
26
- puts "Generated HTML report at #{File.expand_path './attractor_output/index.html'}"
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
@@ -1,3 +1,3 @@
1
1
  module Attractor
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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.1.2
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-03 00:00:00.000000000 Z
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: []