attractor 1.1.0 → 1.1.1

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: 3a0acbe6a12975ea7a7aef0aceb983b61b45b3ec6bb7f3da95f2aaa992761011
4
- data.tar.gz: 9541fe95c00fd300b2ce44bb9c19eb5c0819bb5eb69959bbe8270d56dd031fb3
3
+ metadata.gz: d67da9392099763d432b8bb74551259f5b6105cbbc4d7b370c41c1fe851102bf
4
+ data.tar.gz: 4fdd7358b4c073b52f517bd6933b2fcfc134549685dc42320ff055358971df86
5
5
  SHA512:
6
- metadata.gz: d25be5b596d5af43eee4ba6827d483f3c9daa6a4be37c9dcb740906b81ea30c9dd8c4a5f7610ff994f38385ce1346f1cccb3c4892fd68214f99057e71b11b057
7
- data.tar.gz: ee49bd39b824ce6918514ecbb9ef913a7bed6d7fdb2dd79982cd097755121ba1af49e94764aafbd2511ed827b085a202f222c187d52162a003c6ae15eb4f4002
6
+ metadata.gz: cd928f5288a2330156ef222277b34a3cfa9554529a8fcdd3fbf85e00ef38c140b6e3eaee990e8d9923a92da922546d0daaf7c139567ba7227245f9fb04d399c6
7
+ data.tar.gz: 341c7ba41ef4ac89021594d534f1c27f30d16e0f381a8e9106d09b68fdbec66c0ac8b738d916ff35323987d898a8a63043f4e74f19c186685c9d548ae58ff6c3
@@ -1,3 +1,7 @@
1
+ ## RELEASE 1.1.1
2
+
3
+ * ENHANCEMENT: `--minimum_churn` and `--start_ago` CLI options
4
+
1
5
  ## RELEASE 1.1.0
2
6
 
3
7
  * FEATURE: Auto-detect ruby and js projects
data/README.md CHANGED
@@ -7,12 +7,15 @@
7
7
 
8
8
  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.
9
9
 
10
+ Here's an [article on medium](https://medium.com/better-programming/why-i-made-my-own-code-quality-tool-c44b40ceaafd) explaining the approach in greater detail.
11
+
10
12
  ## Table of Contents
11
13
 
12
14
  * [Installation](#installation)
13
15
  * [Usage](#usage)
14
16
  + [Live Reloading](#live-reloading)
15
17
  * [CI Usage](#ci-usage)
18
+ + [Github Action](#github-action)
16
19
  + [Gitlab Example](#gitlab-example)
17
20
  * [CLI Commands and Options](#cli-commands-and-options)
18
21
  * [Development](#development)
@@ -74,6 +77,10 @@ If you have `guard-livereload` (or a similar service) running on your project, y
74
77
 
75
78
  To use this CLI in a CI environment, use the `--ci` option, which will suppress automatic opening of a browser window.
76
79
 
80
+ ### Github Action
81
+
82
+ There is a dedicated [Github Action](https://github.com/julianrubisch/attractor-action) that will compile Attractor's output. Here's the action on the [Marketplace](https://github.com/marketplace/actions/attractor-action).
83
+
77
84
  ### Gitlab Example
78
85
 
79
86
  The simplest use case is to store the `attractor_output` directory as an artifact.
@@ -101,6 +108,8 @@ Print a simple output to console:
101
108
  $ --file_prefix|-p app/models
102
109
  $ --type|-t rb|js
103
110
  $ --watch|-w
111
+ $ --start_ago|-s (e.g. 5y, 3m, 7w)
112
+ $ --minimum_churn|-c (minimum times a file must have changed to be processed)
104
113
 
105
114
  Generate a full report
106
115
 
@@ -109,6 +118,8 @@ Generate a full report
109
118
  $ --type|-t rb|js
110
119
  $ --watch|-w
111
120
  $ --no-open-browser|--ci
121
+ $ --start_ago|-s (e.g. 5y, 3m, 7w)
122
+ $ --minimum_churn|-c (minimum times a file must have changed to be processed)
112
123
 
113
124
  Serve the output on http://localhost:7890
114
125
 
@@ -116,6 +127,8 @@ Serve the output on http://localhost:7890
116
127
  $ --file_prefix|-p app/models
117
128
  $ --watch|-w
118
129
  $ --no-open-browser|--ci
130
+ $ --start_ago|-s (e.g. 5y, 3m, 7w)
131
+ $ --minimum_churn|-c (minimum times a file must have changed to be processed)
119
132
 
120
133
  ## Development
121
134
 
@@ -153,4 +166,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
153
166
  <!-- prettier-ignore-end -->
154
167
  <!-- ALL-CONTRIBUTORS-LIST:END -->
155
168
 
156
- This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
169
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -28,7 +28,10 @@ Gem::Specification.new do |spec|
28
28
  # Specify which files should be added to the gem when it is released.
29
29
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
30
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
31
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(src|tmp|test|spec|features)/}) }
31
+ `git ls-files -z`.split("\x0").reject do |f|
32
+ f.match(%r{^(src|tmp|test|spec|features|\.github)/}) ||
33
+ %w[.all-contributorsrc .rspec .rspec_status .travis.yml].include?(f)
34
+ end
32
35
  end
33
36
  spec.bindir = 'exe'
34
37
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -55,5 +58,6 @@ Gem::Specification.new do |spec|
55
58
  spec.add_development_dependency 'rake', '~> 10.0'
56
59
  spec.add_development_dependency 'rspec', '~> 3.0'
57
60
  spec.add_development_dependency 'sassc'
61
+ spec.add_development_dependency 'standard'
58
62
  spec.add_development_dependency 'structured_changelog'
59
63
  end
@@ -1,32 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'attractor/version'
4
+ require 'attractor/duration_parser'
4
5
  require 'attractor/calculators/base_calculator'
5
- require 'attractor/calculators/ruby_calculator'
6
- require 'attractor/calculators/js_calculator'
7
6
  require 'attractor/detectors/base_detector'
8
- require 'attractor/detectors/ruby_detector'
9
- require 'attractor/detectors/js_detector'
10
7
  require 'attractor/reporters/base_reporter'
11
- require 'attractor/reporters/console_reporter'
12
- require 'attractor/reporters/html_reporter'
13
- require 'attractor/reporters/sinatra_reporter'
14
8
  require 'attractor/suggester'
15
9
  require 'attractor/watcher'
16
10
 
11
+ Dir[File.join(__dir__, 'attractor', 'calculators', '*.rb')].each do |file|
12
+ next if file.start_with?('base')
13
+
14
+ require file
15
+ end
16
+
17
+ Dir[File.join(__dir__, 'attractor', 'detectors', '*.rb')].each do |file|
18
+ next if file.start_with?('base')
19
+
20
+ require file
21
+ end
22
+
23
+ Dir[File.join(__dir__, 'attractor', 'reporters', '*.rb')].each do |file|
24
+ next if file.start_with?('base')
25
+
26
+ require file
27
+ end
28
+
17
29
  module Attractor
18
30
  class Error < StandardError; end
19
31
 
20
- def calculators_for_type(type, file_prefix)
32
+ def calculators_for_type(type, **options)
21
33
  case type
22
34
  when 'js'
23
- { 'js' => JsCalculator.new(file_prefix: file_prefix) }
35
+ { 'js' => JsCalculator.new(**options) }
24
36
  when 'rb'
25
- { 'rb' => RubyCalculator.new(file_prefix: file_prefix) }
37
+ { 'rb' => RubyCalculator.new(**options) }
26
38
  else
27
39
  {}.tap do |hash|
28
- hash['rb'] = RubyCalculator.new(file_prefix: file_prefix) if RubyDetector.new.detect
29
- hash['js'] = JsCalculator.new(file_prefix: file_prefix) if JsDetector.new.detect
40
+ hash['rb'] = RubyCalculator.new(**options) if RubyDetector.new.detect
41
+ hash['js'] = JsCalculator.new(**options) if JsDetector.new.detect
30
42
  end
31
43
  end
32
44
  end
@@ -9,10 +9,11 @@ module Attractor
9
9
  class BaseCalculator
10
10
  attr_reader :type
11
11
 
12
- def initialize(file_prefix: '', file_extension: 'rb', minimum_churn_count: 3)
12
+ def initialize(file_prefix: '', file_extension: 'rb', minimum_churn_count: 3, start_ago: '5y')
13
13
  @file_prefix = file_prefix
14
14
  @file_extension = file_extension
15
15
  @minimum_churn_count = minimum_churn_count
16
+ @start_date = Date.today - Attractor::DurationParser.new(start_ago).duration
16
17
  end
17
18
 
18
19
  def calculate
@@ -20,7 +21,7 @@ module Attractor
20
21
  file_extension: @file_extension,
21
22
  file_prefix: @file_prefix,
22
23
  minimum_churn_count: @minimum_churn_count,
23
- start_date: Date.today - 365 * 5
24
+ start_date: @start_date
24
25
  ).report(false)
25
26
 
26
27
  churn[:churn][:changes].map do |change|
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Attractor
4
4
  class JsCalculator < BaseCalculator
5
- def initialize(file_prefix: '', minimum_churn_count: 3)
6
- super(file_prefix: file_prefix, file_extension: '(js|jsx)', minimum_churn_count: minimum_churn_count)
5
+ def initialize(file_prefix: '', minimum_churn_count: 3, start_ago: 365 * 5)
6
+ super(file_prefix: file_prefix, file_extension: '(js|jsx)', minimum_churn_count: minimum_churn_count, start_ago: start_ago)
7
7
  @type = "JavaScript"
8
8
  end
9
9
 
@@ -4,8 +4,8 @@ require 'flog'
4
4
 
5
5
  module Attractor
6
6
  class RubyCalculator < BaseCalculator
7
- def initialize(file_prefix: '', minimum_churn_count: 3)
8
- super(file_prefix: file_prefix, file_extension: 'rb', minimum_churn_count: minimum_churn_count)
7
+ def initialize(file_prefix: '', minimum_churn_count: 3, start_ago: 365 * 5)
8
+ super(file_prefix: file_prefix, file_extension: 'rb', minimum_churn_count: minimum_churn_count, start_ago: start_ago)
9
9
  @type = "Ruby"
10
10
  end
11
11
 
@@ -7,43 +7,48 @@ require 'attractor'
7
7
  module Attractor
8
8
  # contains methods implementing the CLI
9
9
  class CLI < Thor
10
+ shared_options = [[:file_prefix, aliases: :p],
11
+ [:watch, aliases: :w, type: :boolean],
12
+ [:minimum_churn, aliases: :c, type: :numeric, default: 3],
13
+ [:start_ago, aliases: :s, type: :string, default: '5y'],
14
+ [:type, aliases: :t]]
15
+
16
+ advanced_options = [[:format, aliases: :f, default: 'html'],
17
+ [:no_open_browser, type: :boolean],
18
+ [:ci, type: :boolean]]
19
+
10
20
  desc 'calc', 'Calculates churn and complexity for all ruby files in current directory'
11
- option :file_prefix, aliases: :p
12
- option :watch, aliases: :w, type: :boolean
13
- option :type, aliases: :t
21
+ shared_options.each do |shared_option|
22
+ option(*shared_option)
23
+ end
14
24
  def calc
15
25
  file_prefix = options[:file_prefix]
16
- calculators = Attractor.calculators_for_type(options[:type], file_prefix)
17
26
  if options[:watch]
18
27
  puts 'Listening for file changes...'
19
- Attractor::ConsoleReporter.new(file_prefix: file_prefix, calculators: calculators).watch
28
+ Attractor::ConsoleReporter.new(file_prefix: file_prefix, calculators: calculators(options)).watch
20
29
  else
21
- Attractor::ConsoleReporter.new(file_prefix: file_prefix, calculators: calculators).report
30
+ Attractor::ConsoleReporter.new(file_prefix: file_prefix, calculators: calculators(options)).report
22
31
  end
23
32
  rescue RuntimeError => e
24
33
  puts "Runtime error: #{e.message}"
25
34
  end
26
35
 
27
36
  desc 'report', 'Generates an HTML report'
28
- option :format, aliases: :f, default: 'html'
29
- option :file_prefix, aliases: :p
30
- option :watch, aliases: :w, type: :boolean
31
- option :type, aliases: :t
32
- option :no_open_browser, type: :boolean
33
- option :ci, type: :boolean
37
+ (shared_options + advanced_options).each do |option|
38
+ option(*option)
39
+ end
34
40
  def report
35
41
  file_prefix = options[:file_prefix]
36
- calculators = Attractor.calculators_for_type(options[:type], file_prefix)
37
- open_browser = !(options[:no_open_browser] || options[:ci])
42
+ open_browser = !(options[:no_open_browser] || options[:ci])
38
43
  if options[:watch]
39
44
  puts 'Listening for file changes...'
40
- Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators, open_browser: open_browser).watch
45
+ Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).watch
41
46
  else
42
47
  case options[:format]
43
48
  when 'html'
44
- Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators, open_browser: open_browser).report
49
+ Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
45
50
  else
46
- Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators, open_browser: open_browser).report
51
+ Attractor::HtmlReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
47
52
  end
48
53
  end
49
54
  rescue RuntimeError => e
@@ -51,27 +56,32 @@ module Attractor
51
56
  end
52
57
 
53
58
  desc 'serve', 'Serves the report on localhost'
54
- option :format, aliases: :f, default: 'html'
55
- option :file_prefix, aliases: :p
56
- option :watch, aliases: :w, type: :boolean
57
- option :type, aliases: :t
58
- option :no_open_browser, type: :boolean
59
- option :ci, type: :boolean
59
+ (shared_options + advanced_options).each do |option|
60
+ option(*option)
61
+ end
60
62
  def serve
61
63
  file_prefix = options[:file_prefix]
62
- open_browser = !(options[:no_open_browser] || options[:ci])
63
- calculators = Attractor.calculators_for_type(options[:type], file_prefix)
64
+ open_browser = !(options[:no_open_browser] || options[:ci])
64
65
  if options[:watch]
65
66
  puts 'Listening for file changes...'
66
- Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators, open_browser: open_browser).watch
67
+ Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).watch
67
68
  else
68
69
  case options[:format]
69
70
  when 'html'
70
- Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators, open_browser: open_browser).report
71
+ Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
71
72
  else
72
- Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators, open_browser: open_browser).report
73
+ Attractor::SinatraReporter.new(file_prefix: file_prefix, calculators: calculators(options), open_browser: open_browser).report
73
74
  end
74
75
  end
75
76
  end
77
+
78
+ private
79
+
80
+ def calculators(options)
81
+ Attractor.calculators_for_type(options[:type],
82
+ file_prefix: options[:file_prefix],
83
+ minimum_churn_count: options[:minimum_churn],
84
+ start_ago: options[:start_ago])
85
+ end
76
86
  end
77
87
  end
@@ -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
@@ -74,6 +74,8 @@ module Attractor
74
74
  def values(type: 'rb')
75
75
  @values = @calculators[type].calculate
76
76
  @values
77
+ rescue NoMethodError => e
78
+ puts "No calculator for type #{type}"
77
79
  end
78
80
  end
79
81
  end
@@ -4,7 +4,7 @@ module Attractor
4
4
  # makes suggestions for refactorings
5
5
  class Suggester
6
6
  def initialize(values)
7
- @values = values
7
+ @values = values || []
8
8
  end
9
9
 
10
10
  def suggest(threshold = 95)
@@ -1,3 +1,3 @@
1
1
  module Attractor
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  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: 1.1.0
4
+ version: 1.1.1
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-12-23 00:00:00.000000000 Z
11
+ date: 2019-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: churn
@@ -290,6 +290,20 @@ dependencies:
290
290
  - - ">="
291
291
  - !ruby/object:Gem::Version
292
292
  version: '0'
293
+ - !ruby/object:Gem::Dependency
294
+ name: standard
295
+ requirement: !ruby/object:Gem::Requirement
296
+ requirements:
297
+ - - ">="
298
+ - !ruby/object:Gem::Version
299
+ version: '0'
300
+ type: :development
301
+ prerelease: false
302
+ version_requirements: !ruby/object:Gem::Requirement
303
+ requirements:
304
+ - - ">="
305
+ - !ruby/object:Gem::Version
306
+ version: '0'
293
307
  - !ruby/object:Gem::Dependency
294
308
  name: structured_changelog
295
309
  requirement: !ruby/object:Gem::Requirement
@@ -316,14 +330,10 @@ executables:
316
330
  extensions: []
317
331
  extra_rdoc_files: []
318
332
  files:
319
- - ".all-contributorsrc"
320
333
  - ".babelrc"
321
334
  - ".eslintrc.json"
322
- - ".github/workflows/ruby.yml"
323
335
  - ".gitignore"
324
- - ".rspec"
325
336
  - ".rubocop.yml"
326
- - ".travis.yml"
327
337
  - CHANGELOG.md
328
338
  - Gemfile
329
339
  - Guardfile
@@ -350,6 +360,7 @@ files:
350
360
  - lib/attractor/detectors/base_detector.rb
351
361
  - lib/attractor/detectors/js_detector.rb
352
362
  - lib/attractor/detectors/ruby_detector.rb
363
+ - lib/attractor/duration_parser.rb
353
364
  - lib/attractor/reporters/.keep
354
365
  - lib/attractor/reporters/base_reporter.rb
355
366
  - lib/attractor/reporters/console_reporter.rb
@@ -1,25 +0,0 @@
1
- {
2
- "files": [
3
- "README.md"
4
- ],
5
- "imageSize": 100,
6
- "commit": false,
7
- "contributors": [
8
- {
9
- "login": "julianrubisch",
10
- "name": "Julian Rubisch",
11
- "avatar_url": "https://avatars0.githubusercontent.com/u/4352208?v=4",
12
- "profile": "http://www.julianrubisch.at",
13
- "contributions": [
14
- "code",
15
- "doc"
16
- ]
17
- }
18
- ],
19
- "contributorsPerLine": 7,
20
- "projectName": "attractor",
21
- "projectOwner": "julianrubisch",
22
- "repoType": "github",
23
- "repoHost": "https://github.com",
24
- "skipCi": true
25
- }
@@ -1,25 +0,0 @@
1
- name: Ruby
2
-
3
- on: [push]
4
-
5
- jobs:
6
- build:
7
-
8
- runs-on: ubuntu-latest
9
-
10
- strategy:
11
- matrix:
12
- ruby-version: [2.4.x, 2.5.x, 2.6.x]
13
-
14
- steps:
15
- - uses: actions/checkout@v1
16
- - name: Set up Ruby ${{ matrix.ruby-version }}
17
- uses: actions/setup-ruby@v1
18
- with:
19
- ruby-version: ${{ matrix.ruby-version }}
20
- - name: Build and test with Rake
21
- run: |
22
- gem install bundler
23
- bundle install --jobs 4 --retry 3
24
- bundle exec rspec spec
25
- bundle exec cucumber features
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
@@ -1,10 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 2.0.2
8
- script:
9
- - bundle exec cucumber features
10
- - bundle exec rspec spec