attractor 2.4.0 → 2.6.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: c1b66250e293edf3d4298ee78c92a928b92128d0f8525a012ba4c99b8e16a77e
4
- data.tar.gz: 354068cdc6c91978e80e3860ac65255811daf364d64c15dcd7221db2bac79f22
3
+ metadata.gz: 31bd7aa261b4e29b98a2a0af93e653d6dac08d0187505b57ff6df1dc60ad37c4
4
+ data.tar.gz: 602324b18e247c6410f9d6206b089c0ae11cfe2d6a99feb5d0a0cf3d9a77f88e
5
5
  SHA512:
6
- metadata.gz: 456416f564b3e7e5efa7874f901fea6a0c26c23e59bf157dd5c78cb58014f4bcee58f0172cebdc83061d94195d94631e1818eba905835799e1664527fc26caa9
7
- data.tar.gz: 472072d37700e794de8ee98d48f2252186598f709094c9046acc97deb91ef34dfbc089a4c7b70f0965ce8fc4621877c63d41a167f4d2968f2b734b9f5e4e18de
6
+ metadata.gz: 04a0fb63d5f198b8a5f412615e0fcc48ccb3cf6ee89edc1448ce0213cd9bc5f224bd4d435bf1c114c19789461cc6d4ccea13f55dc7cd39a6132bc0fe3d388342
7
+ data.tar.gz: bb2aa36a13ca5a1615853e6eb29bc13e2f5b49e46e18242fc10ef78ea4d57cd5dbbd5ed2c9bd01e5139c191a36d61ff5d7ca5f1645bcdce98783c194740425a1
data/README.md CHANGED
@@ -10,7 +10,6 @@
10
10
  [license-url]: https://github.com/julianrubisch/attractor/blob/master/LICENSE
11
11
  [twitter-url]: https://twitter.com/AttractorGem
12
12
  [twitter-shield]: https://img.shields.io/twitter/follow/AttractorGem?style=social
13
- [build-status]: https://travis-ci.org/julianrubisch/attractor.svg?branch=master
14
13
  [twitter-shield]: https://img.shields.io/twitter/follow/AttractorGem?style=social
15
14
  [ruby-tests-action-shield]: https://github.com/julianrubisch/attractor/workflows/Ruby%20Tests/badge.svg
16
15
  <!-- Media -->
@@ -39,7 +38,6 @@
39
38
  ---
40
39
 
41
40
  <!-- PROJECT SHIELDS -->
42
- ![Build Status][build-status]
43
41
  ![Ruby Tests Action Status][ruby-tests-action-shield]
44
42
  [![Forks][forks-shield]][forks-url]
45
43
  [![Stargazers][stars-shield]][stars-url]
data/Rakefile CHANGED
@@ -25,7 +25,7 @@ task :assets do
25
25
  sass = File.read(File.expand_path("./src/stylesheets/main.scss"))
26
26
  css = SassC::Engine.new(sass, style: :compressed).render
27
27
  prefixed = AutoprefixerRails.process(css)
28
- File.open(File.expand_path("./app/assets/stylesheets/main.css"), "w") { |file| file.write(prefixed) }
28
+ File.write(File.expand_path("./app/assets/stylesheets/main.css"), prefixed)
29
29
 
30
30
  npm_output = `npm run build`
31
31
  puts npm_output
@@ -9,12 +9,13 @@ module Attractor
9
9
  class BaseCalculator
10
10
  attr_reader :type
11
11
 
12
- def initialize(file_prefix: "", ignores: "", file_extension: "rb", minimum_churn_count: 3, start_ago: "5y")
12
+ def initialize(file_prefix: "", ignores: "", file_extension: "rb", minimum_churn_count: 3, start_ago: "5y", verbose: false)
13
13
  @file_prefix = file_prefix
14
14
  @file_extension = file_extension
15
15
  @minimum_churn_count = minimum_churn_count
16
16
  @start_date = Date.today - Attractor::DurationParser.new(start_ago).duration
17
17
  @ignores = ignores
18
+ @verbose = verbose
18
19
  end
19
20
 
20
21
  def calculate
@@ -26,7 +27,7 @@ module Attractor
26
27
  ignores: @ignores
27
28
  ).report(false)
28
29
 
29
- puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files"
30
+ puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files" if @verbose
30
31
 
31
32
  values = churn[:churn][:changes].map do |change|
32
33
  history = git_history_for_file(file_path: change[:file_path])
@@ -47,13 +48,13 @@ module Attractor
47
48
  Cache.write(file_path: change[:file_path], value: value)
48
49
  end
49
50
 
50
- print "."
51
+ print "." if @verbose
51
52
  value
52
53
  end
53
54
 
54
55
  Cache.persist!
55
56
 
56
- print "\n\n"
57
+ print "\n\n" if @verbose
57
58
 
58
59
  values
59
60
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "churn/calculator"
4
+
5
+ require "attractor/value"
6
+
7
+ module Attractor
8
+ # calculates churn and complexity
9
+ class BaseCalculator
10
+ attr_reader :type
11
+
12
+ def initialize(file_prefix: "", ignores: "", file_extension: "rb", minimum_churn_count: 3, start_ago: "5y")
13
+ @file_prefix = file_prefix
14
+ @file_extension = file_extension
15
+ @minimum_churn_count = minimum_churn_count
16
+ @start_date = Date.today - Attractor::DurationParser.new(start_ago).duration
17
+ @ignores = ignores
18
+ end
19
+
20
+ def calculate
21
+ churn = ::Churn::ChurnCalculator.new(
22
+ file_extension: @file_extension,
23
+ file_prefix: @file_prefix,
24
+ minimum_churn_count: @minimum_churn_count,
25
+ start_date: @start_date,
26
+ ignores: @ignores
27
+ ).report(false)
28
+
29
+ puts "Calculating churn and complexity values for #{churn[:churn][:changes].size} #{type} files"
30
+
31
+ values = churn[:churn][:changes].map do |change|
32
+ history = git_history_for_file(file_path: change[:file_path])
33
+ commit = history&.first&.first
34
+
35
+ cached_value = Cache.read(file_path: change[:file_path])
36
+
37
+ if !cached_value.nil? && !cached_value.current_commit.nil? && cached_value.current_commit == commit
38
+ value = cached_value
39
+ else
40
+ complexity, details = yield(change)
41
+
42
+ value = Value.new(file_path: change[:file_path],
43
+ churn: change[:times_changed],
44
+ complexity: complexity,
45
+ details: details,
46
+ history: history)
47
+ Cache.write(file_path: change[:file_path], value: value)
48
+ end
49
+
50
+ print "."
51
+ value
52
+ end
53
+
54
+ Cache.persist!
55
+
56
+ print "\n\n"
57
+
58
+ values
59
+ end
60
+
61
+ private
62
+
63
+ def git_history_for_file(file_path:, limit: 10)
64
+ history = `git log --oneline -n #{limit} -- #{file_path}`
65
+ history.split("\n")
66
+ .map do |log_entry|
67
+ log_entry.partition(/\A(\S+)\s/)
68
+ .map(&:strip)
69
+ .reject(&:empty?)
70
+ end
71
+ end
72
+ end
73
+ end
data/lib/attractor/cli.rb CHANGED
@@ -8,6 +8,7 @@ module Attractor
8
8
  # contains methods implementing the CLI
9
9
  class CLI < Thor
10
10
  shared_options = [[:file_prefix, aliases: :p],
11
+ [:verbose, aliases: :v, type: :boolean],
11
12
  [:ignore, aliases: :i, default: ""],
12
13
  [:watch, aliases: :w, type: :boolean],
13
14
  [:minimum_churn, aliases: :c, type: :numeric, default: 3],
@@ -86,7 +87,8 @@ module Attractor
86
87
  file_prefix: options[:file_prefix],
87
88
  minimum_churn_count: options[:minimum_churn],
88
89
  ignores: options[:ignore],
89
- start_ago: options[:start_ago])
90
+ start_ago: options[:start_ago],
91
+ verbose: options[:verbose])
90
92
  end
91
93
 
92
94
  def report!(reporter)
@@ -0,0 +1,101 @@
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 "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
+
44
+ desc "calc", "Calculates churn and complexity for all ruby files in current directory"
45
+ shared_options.each do |shared_option|
46
+ option(*shared_option)
47
+ end
48
+ option(:format, aliases: :f, default: :table)
49
+ def calc
50
+ file_prefix = options[:file_prefix]
51
+ output_format = options[:format]
52
+
53
+ report! Attractor::ConsoleReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), format: output_format)
54
+ rescue RuntimeError => e
55
+ puts "Runtime error: #{e.message}"
56
+ end
57
+
58
+ desc "report", "Generates an HTML report"
59
+ (shared_options + advanced_options).each do |option|
60
+ option(*option)
61
+ end
62
+ def report
63
+ file_prefix = options[:file_prefix]
64
+ open_browser = !(options[:no_open_browser] || options[:ci])
65
+
66
+ report! Attractor::HtmlReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
67
+ rescue RuntimeError => e
68
+ puts "Runtime error: #{e.message}"
69
+ end
70
+
71
+ desc "serve", "Serves the report on localhost"
72
+ (shared_options + advanced_options).each do |option|
73
+ option(*option)
74
+ end
75
+ def serve
76
+ file_prefix = options[:file_prefix]
77
+ open_browser = !(options[:no_open_browser] || options[:ci])
78
+
79
+ report! Attractor::SinatraReporter.new(file_prefix: file_prefix, ignores: options[:ignore], calculators: calculators(options), open_browser: open_browser)
80
+ end
81
+
82
+ private
83
+
84
+ def calculators(options)
85
+ Attractor.calculators_for_type(options[:type],
86
+ file_prefix: options[:file_prefix],
87
+ minimum_churn_count: options[:minimum_churn],
88
+ ignores: options[:ignore],
89
+ start_ago: options[:start_ago])
90
+ end
91
+
92
+ def report!(reporter)
93
+ if options[:watch]
94
+ puts "Listening for file changes..."
95
+ reporter.watch
96
+ else
97
+ reporter.report
98
+ end
99
+ end
100
+ end
101
+ end
@@ -19,7 +19,7 @@ module Attractor
19
19
  @file_prefix = file_prefix || ""
20
20
  @calculators = calculators
21
21
  @open_browser = open_browser
22
- @suggester = Suggester.new(values)
22
+ @suggester = Suggester.new
23
23
 
24
24
  @watcher = Watcher.new(@file_prefix, ignores, lambda do
25
25
  report
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "descriptive_statistics/safe"
4
+ require "fileutils"
5
+ require "forwardable"
6
+ require "launchy"
7
+ require "tilt"
8
+
9
+ module Attractor
10
+ # base reporter
11
+ class BaseReporter
12
+ extend Forwardable
13
+ attr_accessor :file_prefix
14
+ attr_reader :types
15
+ attr_writer :values
16
+ def_delegator :@watcher, :watch
17
+
18
+ def initialize(calculators:, file_prefix: "", ignores: "", open_browser: true)
19
+ @file_prefix = file_prefix || ""
20
+ @calculators = calculators
21
+ @open_browser = open_browser
22
+ @suggester = Suggester.new(values)
23
+
24
+ @watcher = Watcher.new(@file_prefix, ignores, lambda do
25
+ report
26
+ end)
27
+ rescue NoMethodError => _e
28
+ raise "There was a problem gathering churn changes"
29
+ end
30
+
31
+ def suggestions(quantile:, type: "rb")
32
+ @suggester.values = values(type: type)
33
+ @suggestions = @suggester.suggest(quantile)
34
+ @suggestions
35
+ end
36
+
37
+ def report
38
+ @suggestions = @suggester.suggest
39
+ @types = @calculators.map { |calc| [calc.first, calc.last.type] }.to_h
40
+ end
41
+
42
+ def render
43
+ "Attractor"
44
+ end
45
+
46
+ def values(type: "rb")
47
+ @values = @calculators[type].calculate
48
+ @values
49
+ rescue NoMethodError => _e
50
+ puts "No calculator for type #{type}"
51
+ end
52
+ end
53
+ end
@@ -49,13 +49,42 @@ module Attractor
49
49
  end
50
50
  end
51
51
 
52
+ class JSONFormatter
53
+ def call(calculators)
54
+ result = calculators.map do |calc|
55
+ type = calc.last.type
56
+ values = calc.last.calculate
57
+ suggester = Suggester.new(values)
58
+ to_be_refactored = suggester.suggest.map(&:file_path)
59
+
60
+ [
61
+ type, values.map do |value|
62
+ {
63
+ file_path: value.file_path,
64
+ score: value.score,
65
+ complexity: value.complexity,
66
+ churn: value.churn,
67
+ refactor: to_be_refactored.include?(value.file_path),
68
+ details: value.details,
69
+ history: value.history
70
+ }
71
+ end
72
+ ]
73
+ end
74
+
75
+ puts result.to_h.to_json
76
+ end
77
+ end
78
+
52
79
  def initialize(format:, **other)
53
80
  super(**other)
54
81
  @formatter = case format.to_sym
55
- when :csv
56
- CSVFormatter.new
57
- else
58
- TableFormatter.new
82
+ when :csv
83
+ CSVFormatter.new
84
+ when :json
85
+ JSONFormatter.new
86
+ else
87
+ TableFormatter.new
59
88
  end
60
89
  end
61
90
 
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Attractor
4
+ # console reporter
5
+ class ConsoleReporter < BaseReporter
6
+ class TableFormatter
7
+ def call(calculators)
8
+ puts "Calculated churn and complexity"
9
+ puts
10
+ puts "file_path#{" " * 53}complexity churn"
11
+ puts "-" * 80
12
+
13
+ calculators.each do |calc|
14
+ # e.g. ['js', JsCalculator']
15
+ puts calc.last.type
16
+
17
+ values = calc.last.calculate
18
+ suggester = Suggester.new(values)
19
+
20
+ puts values&.map(&:to_s)
21
+ puts
22
+ puts "Suggestions for refactorings:"
23
+ suggester.suggest&.each { |sug| puts sug.file_path }
24
+ puts
25
+ end
26
+ end
27
+ end
28
+
29
+ class CSVFormatter
30
+ def call(calculators)
31
+ require "csv"
32
+
33
+ result = CSV.generate do |csv|
34
+ csv << %w[file_path score complexity churn type refactor]
35
+
36
+ calculators.each do |calc|
37
+ type = calc.last.type
38
+ values = calc.last.calculate
39
+ suggester = Suggester.new(values)
40
+ to_be_refactored = suggester.suggest.map(&:file_path)
41
+
42
+ values.each do |value|
43
+ csv << [value.file_path, value.score, value.complexity, value.churn, type, to_be_refactored.include?(value.file_path)]
44
+ end
45
+ end
46
+ end
47
+
48
+ puts result
49
+ end
50
+ end
51
+
52
+ class JSONFormatter
53
+ def call(calculators)
54
+ result = calculators.map do |calc|
55
+ type = calc.last.type
56
+ values = calc.last.calculate
57
+ suggester = Suggester.new(values)
58
+ to_be_refactored = suggester.suggest.map(&:file_path)
59
+
60
+ [
61
+ type, values.map do |value|
62
+ {
63
+ file_path: value.file_path,
64
+ score: value.score,
65
+ complexity: value.complexity,
66
+ churn: value.churn,
67
+ refactor: to_be_refactored.include?(value.file_path)
68
+ }
69
+ end
70
+ ]
71
+ end
72
+
73
+ puts result.to_h.to_json
74
+ end
75
+ end
76
+
77
+ def initialize(format:, **other)
78
+ super(**other)
79
+ @formatter = case format.to_sym
80
+ when :csv
81
+ CSVFormatter.new
82
+ when :json
83
+ JSONFormatter.new
84
+ else
85
+ TableFormatter.new
86
+ end
87
+ end
88
+
89
+ def report
90
+ super
91
+ @formatter.call(@calculators)
92
+ end
93
+ end
94
+ end
@@ -14,10 +14,10 @@ module Attractor
14
14
  FileUtils.mkdir_p "./attractor_output/images"
15
15
  FileUtils.mkdir_p "./attractor_output/javascripts"
16
16
 
17
- File.open("./attractor_output/images/attractor_logo.svg", "w") { |file| file.write(logo) }
18
- File.open("./attractor_output/images/attractor_favicon.png", "w") { |file| file.write(favicon) }
19
- File.open("./attractor_output/stylesheets/main.css", "w") { |file| file.write(css) }
20
- File.open("./attractor_output/javascripts/index.pack.js", "w") { |file| file.write(javascript_pack) }
17
+ File.write("./attractor_output/images/attractor_logo.svg", logo)
18
+ File.write("./attractor_output/images/attractor_favicon.png", favicon)
19
+ File.write("./attractor_output/stylesheets/main.css", css)
20
+ File.write("./attractor_output/javascripts/index.pack.js", javascript_pack)
21
21
 
22
22
  if @calculators.size > 1
23
23
  @calculators.each do |calc|
@@ -25,8 +25,8 @@ module Attractor
25
25
  suggester = Suggester.new(values(type: @short_type))
26
26
  @suggestions = suggester.suggest
27
27
 
28
- File.open("./attractor_output/javascripts/index.#{@short_type}.js", "w") { |file| file.write(javascript) }
29
- File.open("./attractor_output/index.#{@short_type}.html", "w") { |file| file.write(render) }
28
+ File.write("./attractor_output/javascripts/index.#{@short_type}.js", javascript)
29
+ File.write("./attractor_output/index.#{@short_type}.html", render)
30
30
  puts "Generated HTML report at #{File.expand_path "./attractor_output/"}/index.#{@short_type}.html"
31
31
  end
32
32
 
@@ -35,8 +35,8 @@ module Attractor
35
35
  puts "Opening browser window..."
36
36
  end
37
37
  else
38
- File.open("./attractor_output/javascripts/index.js", "w") { |file| file.write(javascript) }
39
- File.open("./attractor_output/index.html", "w") { |file| file.write(render) }
38
+ File.write("./attractor_output/javascripts/index.js", javascript)
39
+ File.write("./attractor_output/index.html", render)
40
40
  puts "Generated HTML report at #{File.expand_path "./attractor_output/index.html"}"
41
41
 
42
42
  if @open_browser
@@ -5,8 +5,8 @@ module Attractor
5
5
  class Suggester
6
6
  attr_accessor :values
7
7
 
8
- def initialize(values)
9
- @values = values || []
8
+ def initialize(values = [])
9
+ @values = values
10
10
  end
11
11
 
12
12
  def suggest(threshold = 95)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Attractor
4
+ # makes suggestions for refactorings
5
+ class Suggester
6
+ attr_accessor :values
7
+
8
+ def initialize(values)
9
+ @values = values || []
10
+ end
11
+
12
+ def suggest(threshold = 95)
13
+ products = @values.map(&:score)
14
+ products.extend(DescriptiveStatistics)
15
+ quantile = products.percentile(threshold.to_i)
16
+
17
+ @values.select { |val| val.score > quantile }
18
+ .sort_by { |val| val.score }.reverse
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Attractor
2
- VERSION = "2.4.0"
2
+ VERSION = "2.6.0"
3
3
  end
@@ -1,7 +1,3 @@
1
1
  module Attractor
2
- <<<<<<< HEAD
3
- VERSION = "2.4.0"
4
- =======
5
- VERSION = "2.3.0"
6
- >>>>>>> 1b4d274a6c7f6634ac4b6c2627f5de500788cef4
2
+ VERSION = "2.5.0"
7
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: 2.4.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Rubisch
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-31 00:00:00.000000000 Z
11
+ date: 2023-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: churn
@@ -156,28 +156,28 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.2.0
159
+ version: 0.3.0
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.2.0
166
+ version: 0.3.0
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: attractor-ruby
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: 0.2.0
173
+ version: 0.3.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: 0.2.0
180
+ version: 0.3.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: autoprefixer-rails
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -373,16 +373,21 @@ files:
373
373
  - lib/attractor.rb
374
374
  - lib/attractor/cache.rb
375
375
  - lib/attractor/calculators/base_calculator.rb
376
+ - lib/attractor/calculators/base_calculator.rb~
376
377
  - lib/attractor/cli.rb
378
+ - lib/attractor/cli.rb~
377
379
  - lib/attractor/detectors/base_detector.rb
378
380
  - lib/attractor/duration_parser.rb
379
381
  - lib/attractor/gem_names.rb
380
382
  - lib/attractor/registry_entry.rb
381
383
  - lib/attractor/reporters/base_reporter.rb
384
+ - lib/attractor/reporters/base_reporter.rb~
382
385
  - lib/attractor/reporters/console_reporter.rb
386
+ - lib/attractor/reporters/console_reporter.rb~
383
387
  - lib/attractor/reporters/html_reporter.rb
384
388
  - lib/attractor/reporters/sinatra_reporter.rb
385
389
  - lib/attractor/suggester.rb
390
+ - lib/attractor/suggester.rb~
386
391
  - lib/attractor/value.rb
387
392
  - lib/attractor/version.rb
388
393
  - lib/attractor/version.rb~
@@ -395,7 +400,7 @@ metadata:
395
400
  source_code_uri: https://github.com/julianrubisch/attractor
396
401
  bug_tracker_uri: https://github.com/julianrubisch/attractor/issues
397
402
  changelog_uri: https://github.com/julianrubisch/attractor/CHANGELOG.md
398
- post_install_message:
403
+ post_install_message:
399
404
  rdoc_options: []
400
405
  require_paths:
401
406
  - lib
@@ -410,8 +415,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
410
415
  - !ruby/object:Gem::Version
411
416
  version: '0'
412
417
  requirements: []
413
- rubygems_version: 3.1.4
414
- signing_key:
418
+ rubygems_version: 3.4.19
419
+ signing_key:
415
420
  specification_version: 4
416
421
  summary: Churn vs Complexity Chart Generator
417
422
  test_files: []