attractor 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []