semverve 0.3.0 → 0.4.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.
@@ -2,9 +2,10 @@
2
2
 
3
3
  require "rake"
4
4
 
5
+ require_relative "adapters"
5
6
  require_relative "error"
6
- require_relative "presets"
7
7
  require_relative "project_metadata"
8
+ require_relative "version_checks"
8
9
 
9
10
  module Semverve
10
11
  ##
@@ -14,7 +15,13 @@ module Semverve
14
15
  # Version-maintenance surfaces supported by umbrella check and fix tasks.
15
16
  #
16
17
  # @return [Array<Symbol>]
17
- VALID_VERSION_CHECKS = [:doc_references, :code_references, :metadata].freeze
18
+ VALID_VERSION_CHECKS = VersionChecks.names(extra_checks: Adapters.checks).freeze
19
+
20
+ ##
21
+ # Default version-maintenance surfaces for package projects.
22
+ #
23
+ # @return [Array<Symbol>]
24
+ DEFAULT_VERSION_CHECKS = VersionChecks.default_names.freeze
18
25
 
19
26
  ##
20
27
  # Release-readiness surfaces supported by release check tasks.
@@ -26,7 +33,13 @@ module Semverve
26
33
  # Default Ruby pattern for code version literals that are safe to rewrite.
27
34
  #
28
35
  # @return [Regexp]
29
- DEFAULT_VERSION_CODE_REFERENCE_PATTERN = /^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/
36
+ DEFAULT_VERSION_CODE_REFERENCE_PATTERN = VersionCodeReferences::RUBY_ASSIGNMENT_PATTERN
37
+
38
+ ##
39
+ # Framework adapter used to apply project defaults.
40
+ #
41
+ # @return [Symbol, String, nil]
42
+ attr_reader :adapter
30
43
 
31
44
  ##
32
45
  # Whether increments should run +bundle lock+ after writing a version.
@@ -62,7 +75,9 @@ module Semverve
62
75
  # Framework preset used to apply project defaults.
63
76
  #
64
77
  # @return [Symbol, String, nil]
65
- attr_reader :preset
78
+ def preset
79
+ adapter
80
+ end
66
81
 
67
82
  ##
68
83
  # Release-readiness surfaces run by the release check task.
@@ -127,7 +142,7 @@ module Semverve
127
142
  @bundle_lock = false
128
143
  @command_runner = ->(command) { system(command) }
129
144
  @format = :module
130
- @preset = nil
145
+ @adapter = nil
131
146
  @rubygems_host = "https://rubygems.org"
132
147
  @version_code_reference_files = Rake::FileList[]
133
148
  self.version_code_reference_pattern = DEFAULT_VERSION_CODE_REFERENCE_PATTERN
@@ -139,7 +154,7 @@ module Semverve
139
154
  )
140
155
  @version_match_mode = :older
141
156
  self.release_checks = []
142
- self.version_checks = VALID_VERSION_CHECKS
157
+ @version_checks = DEFAULT_VERSION_CHECKS
143
158
  end
144
159
 
145
160
  ##
@@ -178,7 +193,7 @@ module Semverve
178
193
  end
179
194
 
180
195
  ##
181
- # Sets the gem name used for metadata checks.
196
+ # Sets the gem name used for package metadata checks.
182
197
  #
183
198
  # @param [String, nil] gem_name
184
199
  #
@@ -197,6 +212,17 @@ module Semverve
197
212
  set_explicit(:module_name, module_name)
198
213
  end
199
214
 
215
+ ##
216
+ # Sets the framework adapter used for project defaults.
217
+ #
218
+ # @param [Symbol, String, nil] adapter
219
+ #
220
+ # @return [Symbol, nil]
221
+ def adapter=(adapter)
222
+ @adapter_defaults = nil
223
+ @adapter = adapter.nil? ? nil : Adapters.fetch(adapter).name
224
+ end
225
+
200
226
  ##
201
227
  # Sets the framework preset used for project defaults.
202
228
  #
@@ -204,8 +230,7 @@ module Semverve
204
230
  #
205
231
  # @return [Symbol, nil]
206
232
  def preset=(preset)
207
- @preset_defaults = nil
208
- @preset = preset.nil? ? nil : Presets.fetch(preset).name
233
+ self.adapter = preset
209
234
  end
210
235
 
211
236
  ##
@@ -256,7 +281,7 @@ module Semverve
256
281
  #
257
282
  # @return [Array<Symbol>]
258
283
  def version_checks=(checks)
259
- @version_checks = normalize_version_checks(checks)
284
+ set_explicit(:version_checks, normalize_version_checks(checks))
260
285
  end
261
286
 
262
287
  ##
@@ -291,17 +316,25 @@ module Semverve
291
316
  # @return [Object]
292
317
  def resolved_value(attribute)
293
318
  return public_send(attribute) if explicit_attribute?(attribute)
294
- return preset_defaults[attribute] if preset_defaults.key?(attribute)
319
+ return adapter_defaults[attribute] if adapter_defaults.key?(attribute)
295
320
 
296
321
  public_send(attribute)
297
322
  end
298
323
 
324
+ ##
325
+ # Whether package names should be inferred from project files.
326
+ #
327
+ # @return [Boolean]
328
+ def infer_package_name?
329
+ Adapters.infer_package_name?(adapter)
330
+ end
331
+
299
332
  ##
300
333
  # Configured version checks normalized for lookup.
301
334
  #
302
335
  # @return [Array<Symbol>]
303
336
  def normalized_version_checks
304
- normalize_version_checks(version_checks)
337
+ normalize_version_checks(resolved_value(:version_checks))
305
338
  end
306
339
 
307
340
  ##
@@ -319,15 +352,7 @@ module Semverve
319
352
  #
320
353
  # @return [Array<Symbol>]
321
354
  def normalize_version_checks(checks)
322
- normalized_checks = Array(checks).map do |check|
323
- check.respond_to?(:to_sym) ? check.to_sym : check
324
- end
325
- return normalized_checks if normalized_checks.all? { |check| VALID_VERSION_CHECKS.include?(check) }
326
-
327
- invalid_checks = normalized_checks.reject { |check| VALID_VERSION_CHECKS.include?(check) }
328
- valid_check_names = VALID_VERSION_CHECKS.map(&:inspect)
329
- valid_checks = "#{valid_check_names[0...-1].join(", ")}, or #{valid_check_names.last}"
330
- raise Error, "Unknown version check #{invalid_checks.map(&:inspect).join(", ")}. Use #{valid_checks}."
355
+ VersionChecks.normalize(checks, extra_checks: Adapters.checks)
331
356
  end
332
357
 
333
358
  ##
@@ -382,17 +407,17 @@ module Semverve
382
407
  #
383
408
  # @return [Object]
384
409
  def set_explicit(attribute, value)
385
- @preset_defaults = nil
410
+ @adapter_defaults = nil
386
411
  @explicit_attributes << attribute unless explicit_attribute?(attribute)
387
412
  instance_variable_set(:"@#{attribute}", value)
388
413
  end
389
414
 
390
415
  ##
391
- # Defaults supplied by the configured preset.
416
+ # Defaults supplied by the configured adapter.
392
417
  #
393
418
  # @return [Hash]
394
- def preset_defaults
395
- @preset_defaults ||= Presets.defaults_for(preset, self)
419
+ def adapter_defaults
420
+ @adapter_defaults ||= Adapters.defaults_for(adapter, self)
396
421
  end
397
422
  end
398
423
 
@@ -420,7 +445,7 @@ module Semverve
420
445
  ##
421
446
  # Resolved gem name.
422
447
  #
423
- # @return [String]
448
+ # @return [String, nil]
424
449
  attr_reader :gem_name
425
450
 
426
451
  ##
@@ -489,7 +514,7 @@ module Semverve
489
514
  # @param [Boolean] bundle_lock
490
515
  # @param [#call] command_runner
491
516
  # @param [Symbol] format
492
- # @param [String] gem_name
517
+ # @param [String, nil] gem_name
493
518
  # @param [String] module_name
494
519
  # @param [Array<Symbol>] release_checks
495
520
  # @param [String] root
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Semverve
4
+ ##
5
+ # A version mismatch found by a Semverve check.
6
+ class Finding
7
+ ##
8
+ # Path relative to the configured project root.
9
+ #
10
+ # @return [String]
11
+ attr_reader :path
12
+
13
+ ##
14
+ # One-based line number.
15
+ #
16
+ # @return [Integer]
17
+ attr_reader :line
18
+
19
+ ##
20
+ # One-based column number.
21
+ #
22
+ # @return [Integer]
23
+ attr_reader :column
24
+
25
+ ##
26
+ # Referenced semantic version.
27
+ #
28
+ # @return [Semverve::SemanticVersion]
29
+ attr_reader :version
30
+
31
+ ##
32
+ # Optional output label for findings that carry their own label.
33
+ #
34
+ # @return [String, nil]
35
+ attr_reader :label
36
+
37
+ ##
38
+ # Initializes a finding.
39
+ #
40
+ # @param [String] path
41
+ # @param [Integer] line
42
+ # @param [Integer] column
43
+ # @param [Semverve::SemanticVersion] version
44
+ # @param [String, nil] label
45
+ #
46
+ # @return [Semverve::Finding]
47
+ def initialize(path:, line:, column:, version:, label: nil)
48
+ @path = path
49
+ @line = line
50
+ @column = column
51
+ @version = version
52
+ @label = label
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Semverve
4
+ ##
5
+ # Result of fixing one Semverve check surface.
6
+ class FixResult
7
+ ##
8
+ # Files changed by the fix.
9
+ #
10
+ # @return [Array<String>]
11
+ attr_reader :changed_files
12
+
13
+ ##
14
+ # Number of replacements made.
15
+ #
16
+ # @return [Integer]
17
+ attr_reader :replacement_count
18
+
19
+ ##
20
+ # Whether the fix ran +bundle lock+.
21
+ #
22
+ # @return [Boolean]
23
+ attr_reader :bundle_lock_ran
24
+
25
+ ##
26
+ # Initializes a fix result.
27
+ #
28
+ # @param [Array<String>] changed_files
29
+ # @param [Integer] replacement_count
30
+ # @param [Boolean] bundle_lock_ran
31
+ #
32
+ # @return [Semverve::FixResult]
33
+ def initialize(changed_files:, replacement_count:, bundle_lock_ran: false)
34
+ @changed_files = changed_files
35
+ @replacement_count = replacement_count
36
+ @bundle_lock_ran = bundle_lock_ran
37
+ end
38
+ end
39
+ end
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler"
4
3
  require "rubygems"
5
4
 
5
+ require_relative "finding"
6
+ require_relative "fix_result"
6
7
  require_relative "semantic_version"
7
8
 
8
9
  module Semverve
9
10
  ##
10
- # Checks generated gem metadata against the configured version file.
11
- class VersionMetadata
11
+ # Checks generated package metadata against the configured version file.
12
+ class PackageMetadata
12
13
  ##
13
14
  # Literal gemspec version assignments that can be safely rewritten.
14
15
  #
@@ -16,117 +17,29 @@ module Semverve
16
17
  GEMSPEC_LITERAL_PATTERN = /^(\s*\w+\.version\s*=\s*)(["'])(\d+\.\d+\.\d+)(\2)/
17
18
 
18
19
  ##
19
- # A metadata version mismatch.
20
- class Finding
21
- ##
22
- # Path relative to the configured project root.
23
- #
24
- # @return [String]
25
- attr_reader :path
26
-
27
- ##
28
- # One-based line number.
29
- #
30
- # @return [Integer]
31
- attr_reader :line
32
-
33
- ##
34
- # One-based column number.
35
- #
36
- # @return [Integer]
37
- attr_reader :column
38
-
39
- ##
40
- # Metadata semantic version.
41
- #
42
- # @return [Semverve::SemanticVersion]
43
- attr_reader :version
44
-
45
- ##
46
- # Output label for the finding.
47
- #
48
- # @return [String]
49
- attr_reader :label
50
-
51
- ##
52
- # Initializes a finding.
53
- #
54
- # @param [String] path
55
- # @param [Integer] line
56
- # @param [Integer] column
57
- # @param [Semverve::SemanticVersion] version
58
- # @param [String] label
59
- #
60
- # @return [Semverve::VersionMetadata::Finding]
61
- def initialize(path:, line:, column:, version:, label:)
62
- @path = path
63
- @line = line
64
- @column = column
65
- @version = version
66
- @label = label
67
- end
68
- end
69
-
70
- ##
71
- # Result of fixing metadata version mismatches.
72
- class FixResult
73
- ##
74
- # Files changed by the fix.
75
- #
76
- # @return [Array<String>]
77
- attr_reader :changed_files
78
-
79
- ##
80
- # Number of literal replacements made.
81
- #
82
- # @return [Integer]
83
- attr_reader :replacement_count
84
-
85
- ##
86
- # Whether bundle lock was run.
87
- #
88
- # @return [Boolean]
89
- attr_reader :bundle_lock_ran
90
-
91
- ##
92
- # Initializes a fix result.
93
- #
94
- # @param [Array<String>] changed_files
95
- # @param [Integer] replacement_count
96
- # @param [Boolean] bundle_lock_ran
97
- #
98
- # @return [Semverve::VersionMetadata::FixResult]
99
- def initialize(changed_files:, replacement_count:, bundle_lock_ran:)
100
- @changed_files = changed_files
101
- @replacement_count = replacement_count
102
- @bundle_lock_ran = bundle_lock_ran
103
- end
104
- end
105
-
106
- ##
107
- # Initializes metadata version checking.
20
+ # Initializes package metadata version checking.
108
21
  #
109
22
  # @param [Semverve::ResolvedConfiguration] configuration
110
23
  # @param [Semverve::SemanticVersion] current_version
111
24
  #
112
- # @return [Semverve::VersionMetadata]
25
+ # @return [Semverve::PackageMetadata]
113
26
  def initialize(configuration, current_version)
114
27
  @configuration = configuration
115
28
  @current_version = current_version
116
29
  end
117
30
 
118
31
  ##
119
- # Metadata mismatches.
32
+ # Package metadata mismatches.
120
33
  #
121
- # @return [Array<Semverve::VersionMetadata::Finding>]
34
+ # @return [Array<Semverve::Finding>]
122
35
  def findings
123
36
  [gemspec_finding, lockfile_finding].compact
124
37
  end
125
38
 
126
39
  ##
127
- # Fixes safe metadata mismatches.
40
+ # Fixes safe package metadata mismatches.
128
41
  #
129
- # @return [Semverve::VersionMetadata::FixResult]
42
+ # @return [Semverve::FixResult]
130
43
  def fix
131
44
  changed_files = []
132
45
  replacement_count = fix_gemspec_literal
@@ -135,7 +48,7 @@ module Semverve
135
48
 
136
49
  bundle_lock_ran = lockfile_finding ? run_bundle_lock : false
137
50
 
138
- FixResult.new(
51
+ Semverve::FixResult.new(
139
52
  changed_files: changed_files,
140
53
  replacement_count: replacement_count,
141
54
  bundle_lock_ran: bundle_lock_ran
@@ -151,7 +64,7 @@ module Semverve
151
64
  attr_reader :configuration
152
65
 
153
66
  ##
154
- # Current gem version.
67
+ # Current package version.
155
68
  #
156
69
  # @return [Semverve::SemanticVersion]
157
70
  attr_reader :current_version
@@ -167,14 +80,14 @@ module Semverve
167
80
  ##
168
81
  # Finding for a gemspec mismatch.
169
82
  #
170
- # @return [Semverve::VersionMetadata::Finding, nil]
83
+ # @return [Semverve::Finding, nil]
171
84
  def gemspec_finding
172
85
  return unless gemspec_file && gemspec_version
173
86
  return if gemspec_version == current_version
174
87
 
175
88
  line, column = gemspec_position
176
89
 
177
- Finding.new(
90
+ Semverve::Finding.new(
178
91
  path: relative_path(gemspec_file),
179
92
  line: line,
180
93
  column: column,
@@ -186,7 +99,7 @@ module Semverve
186
99
  ##
187
100
  # Finding for a lockfile mismatch.
188
101
  #
189
- # @return [Semverve::VersionMetadata::Finding, nil]
102
+ # @return [Semverve::Finding, nil]
190
103
  def lockfile_finding
191
104
  return unless File.file?(lockfile_path)
192
105
 
@@ -196,7 +109,7 @@ module Semverve
196
109
 
197
110
  line, column = lockfile_position(version)
198
111
 
199
- Finding.new(
112
+ Semverve::Finding.new(
200
113
  path: "Gemfile.lock",
201
114
  line: line,
202
115
  column: column,
@@ -210,6 +123,8 @@ module Semverve
210
123
  #
211
124
  # @return [String, nil]
212
125
  def gemspec_file
126
+ return unless configuration.gem_name
127
+
213
128
  @gemspec_file ||= begin
214
129
  files = Dir.glob(File.join(root, "*.gemspec"))
215
130
 
@@ -271,18 +186,14 @@ module Semverve
271
186
  #
272
187
  # @return [Semverve::SemanticVersion, nil]
273
188
  def lockfile_version
274
- spec = lockfile_parser.specs.find { |candidate| candidate.name == configuration.gem_name }
275
- return unless spec
189
+ return unless configuration.gem_name
276
190
 
277
- SemanticVersion.parse(spec.version.to_s)
278
- end
191
+ File.readlines(lockfile_path).each do |line|
192
+ match = line.match(lockfile_line_pattern)
193
+ return SemanticVersion.parse(match[:version]) if match
194
+ end
279
195
 
280
- ##
281
- # Parsed Gemfile.lock.
282
- #
283
- # @return [Bundler::LockfileParser]
284
- def lockfile_parser
285
- Bundler::LockfileParser.new(File.read(lockfile_path))
196
+ nil
286
197
  end
287
198
 
288
199
  ##
@@ -292,16 +203,25 @@ module Semverve
292
203
  #
293
204
  # @return [Array(Integer, Integer)]
294
205
  def lockfile_position(version)
295
- pattern = /^\s*#{Regexp.escape(configuration.gem_name)}\s+\(#{Regexp.escape(version.to_s)}\)/
296
-
297
206
  File.readlines(lockfile_path).each_with_index do |line, index|
298
- match = line.match(pattern)
207
+ match = line.match(lockfile_line_pattern(version))
299
208
  return [index + 1, line.index(version.to_s) + 1] if match
300
209
  end
301
210
 
302
211
  [1, 1]
303
212
  end
304
213
 
214
+ ##
215
+ # Gemfile.lock line pattern for the configured package.
216
+ #
217
+ # @param [Semverve::SemanticVersion, nil] version
218
+ #
219
+ # @return [Regexp]
220
+ def lockfile_line_pattern(version = nil)
221
+ version_pattern = version ? Regexp.escape(version.to_s) : "\\d+\\.\\d+\\.\\d+"
222
+ /^\s*#{Regexp.escape(configuration.gem_name)}\s+\((?<version>#{version_pattern})\)/
223
+ end
224
+
305
225
  ##
306
226
  # Fixes a literal gemspec version assignment.
307
227
  #
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "error"
3
+ require_relative "adapters"
4
4
 
5
5
  module Semverve
6
6
  ##
7
- # Framework-specific default configuration adapters.
7
+ # Backward-compatible framework preset lookup.
8
8
  module Presets
9
9
  ##
10
10
  # Returns defaults for a configured preset.
@@ -14,9 +14,7 @@ module Semverve
14
14
  #
15
15
  # @return [Hash]
16
16
  def self.defaults_for(name, configuration)
17
- return {} unless name
18
-
19
- fetch(name).defaults(configuration)
17
+ Adapters.defaults_for(name, configuration)
20
18
  end
21
19
 
22
20
  ##
@@ -26,101 +24,7 @@ module Semverve
26
24
  #
27
25
  # @return [#name, #defaults]
28
26
  def self.fetch(name)
29
- case name.to_sym
30
- when :rails
31
- Rails.new
32
- else
33
- raise Error, "Unknown preset #{name.inspect}. Use :rails."
34
- end
35
- end
36
-
37
- ##
38
- # Rails application defaults.
39
- class Rails
40
- ##
41
- # Preset name.
42
- #
43
- # @return [Symbol]
44
- def name
45
- :rails
46
- end
47
-
48
- ##
49
- # Rails-specific Semverve defaults.
50
- #
51
- # @param [Semverve::Configuration] _configuration
52
- #
53
- # @return [Hash]
54
- def defaults(_configuration)
55
- root = rails_root || Dir.pwd
56
-
57
- {
58
- format: :simple,
59
- module_name: rails_module_name || project_module_name(root),
60
- root: root,
61
- version_file: File.join("config", "version.rb")
62
- }
63
- end
64
-
65
- private
66
-
67
- ##
68
- # Rails.root when Rails is available.
69
- #
70
- # @return [String, nil]
71
- def rails_root
72
- return unless rails_defined?
73
- return unless ::Rails.respond_to?(:root)
74
- return unless ::Rails.root
75
-
76
- ::Rails.root.to_s
77
- end
78
-
79
- ##
80
- # Application module name inferred from Rails.application.
81
- #
82
- # @return [String, nil]
83
- def rails_module_name
84
- return unless rails_defined?
85
- return unless ::Rails.respond_to?(:application)
86
- return unless ::Rails.application
87
-
88
- application_class = ::Rails.application.class
89
- return application_class.module_parent_name if application_class.respond_to?(:module_parent_name)
90
-
91
- class_name = application_class.name
92
- return unless class_name&.include?("::")
93
-
94
- class_name.split("::").first
95
- end
96
-
97
- ##
98
- # Module name fallback based on the project directory.
99
- #
100
- # @param [String] root
101
- #
102
- # @return [String]
103
- def project_module_name(root)
104
- camelize(File.basename(File.expand_path(root)))
105
- end
106
-
107
- ##
108
- # Whether Rails is loaded.
109
- #
110
- # @return [Boolean]
111
- def rails_defined?
112
- Object.const_defined?(:Rails)
113
- end
114
-
115
- ##
116
- # Converts a project directory into a Ruby module name.
117
- #
118
- # @param [String] value
119
- #
120
- # @return [String]
121
- def camelize(value)
122
- value.split(/[_-]/).map(&:capitalize).join
123
- end
27
+ Adapters.fetch(name)
124
28
  end
125
29
  end
126
30
  end