version_boss 0.1.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.
@@ -0,0 +1,449 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'regexp'
4
+
5
+ module VersionBoss
6
+ module Gem
7
+ # Parse and compare Ruby Gem version strings
8
+ #
9
+ # This class will parse and validate a Ruby Gem version string
10
+ #
11
+ # Two GemVersion objects can be compared using the spaceship operator (<=>)
12
+ # according to the rules of precedence defined in
13
+ # [the Gem::Version class](https://rubydoc.info/gems/rubygems-update/Gem/Version).
14
+ #
15
+ # @example Basic version parsing
16
+ # gem_version = VersionBoss::GemVersion.new('1.2.3')
17
+ # gem_version.major # => '1'
18
+ # gem_version.minor # => '2'
19
+ # gem_version.patch # => '3'
20
+ #
21
+ # @example Parsing a version with a pre-release identifier
22
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1')
23
+ # gem_version.pre_release # => 'alpha.1'
24
+ # gem_version.pre_release_identifiers # => ['alpha', '1']
25
+ #
26
+ # @example Separators are optional between pre-release identifiers
27
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3.alpha.1')
28
+ # gem_version2 = VersionBoss::GemVersion.new('1.2.3.alpha1')
29
+ # gem_version3 = VersionBoss::GemVersion.new('1.2.3alpha1')
30
+ #
31
+ # gem_version1 == gem_version2 # => true
32
+ # gem_version1 == gem_version3 # => true
33
+ # gem_version2 == gem_version3 # => true
34
+ #
35
+ # gem_version1.pre_release1 # => '.alpha.1'
36
+ # gem_version1.pre_release2 # => '.alpha1'
37
+ # gem_version1.pre_release3 # => 'alpha1'
38
+ #
39
+ # gem_version1.pre_release_identifiers # => ['alpha', '1']
40
+ # gem_version2.pre_release_identifiers # => ['alpha', '1']
41
+ # gem_version3.pre_release_identifiers # => ['alpha', '1']
42
+ #
43
+ # @example Comparing versions
44
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3')
45
+ # gem_version2 = VersionBoss::GemVersion.new('1.2.4')
46
+ # gem_version1 <=> gem_version2 # => -1
47
+ # gem_version2 <=> gem_version3 # => 1
48
+ # gem_version1 <=> gem_version1 # => 0
49
+ #
50
+ # @see https://rubydoc.info/gems/rubygems-update/Gem/Version the Gem::Version class
51
+ #
52
+ # @api public
53
+ #
54
+ class Version
55
+ include Comparable
56
+
57
+ # Create a new GemVersion object
58
+ #
59
+ # @example
60
+ # version = VersionBoss::GemVersion.new('1.2.3.alpha.1')
61
+ #
62
+ # @param version [String] The version string to parse
63
+ #
64
+ # @raise [VersionBoss::Error] version is not a string or not a valid gem_version version
65
+ #
66
+ def initialize(version)
67
+ assert_version_must_be_a_string(version)
68
+ @version = version
69
+ parse
70
+ assert_valid_version
71
+ end
72
+
73
+ # @!attribute version [r]
74
+ #
75
+ # The complete version string
76
+ #
77
+ # @example
78
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001')
79
+ # gem_version.version #=> '1.2.3.alpha.1+build.001'
80
+ #
81
+ # @return [String]
82
+ #
83
+ # @api public
84
+ #
85
+ attr_reader :version
86
+
87
+ # @attribute major [r]
88
+ #
89
+ # The major part of the version
90
+ #
91
+ # @example
92
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001')
93
+ # gem_version.major #=> '1'
94
+ #
95
+ # @return [String]
96
+ #
97
+ # @api public
98
+ #
99
+ attr_reader :major
100
+
101
+ # @attribute minor [r]
102
+ #
103
+ # The minor part of the version
104
+ #
105
+ # @example
106
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001')
107
+ # gem_version.minor #=> '2'
108
+ #
109
+ # @return [String]
110
+ #
111
+ # @api public
112
+ #
113
+ attr_reader :minor
114
+
115
+ # @attribute patch [r]
116
+ #
117
+ # The patch part of the version
118
+ #
119
+ # @example
120
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001')
121
+ # gem_version.patch #=> '3'
122
+ #
123
+ # @return [String]
124
+ #
125
+ # @api public
126
+ #
127
+ attr_reader :patch
128
+
129
+ # @attribute pre_release [r]
130
+ #
131
+ # The pre_release part of the version
132
+ #
133
+ # Will be an empty string if the version has no pre_release part.
134
+ #
135
+ # @example
136
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001')
137
+ # gem_version.pre_release #=> 'alpha.1'
138
+ #
139
+ # @example When the version has no pre_release part
140
+ # gem_version = VersionBoss::GemVersion.new('1.2.3')
141
+ # gem_version.pre_release #=> ''
142
+ #
143
+ # @return [String]
144
+ #
145
+ # @api public
146
+ #
147
+ attr_reader :pre_release
148
+
149
+ # @attribute pre_release_identifiers [r]
150
+ #
151
+ # The pre_release identifiers of the version
152
+ #
153
+ # @example
154
+ # gem_version = VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001')
155
+ # gem_version.pre_release_identifiers #=> ['alpha', '1']
156
+ #
157
+ # @example When the version has no pre_release part
158
+ # gem_version = VersionBoss::GemVersion.new('1.2.3')
159
+ # gem_version.pre_release_identifiers #=> []
160
+ #
161
+ # @return [Array<String>]
162
+ #
163
+ # @api public
164
+ #
165
+ attr_reader :pre_release_identifiers
166
+
167
+ # Compare two GemVersion objects
168
+ #
169
+ # See the [Precedence Rules](https://gem_version.org/spec/v2.0.0.html#spec-item-11)
170
+ # in the Semantic Versioning 2.0.0 Specification for more details.
171
+ #
172
+ # @example
173
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3')
174
+ # gem_version2 = VersionBoss::GemVersion.new('1.2.4')
175
+ # gem_version1 <=> gem_version2 # => -1
176
+ # gem_version2 <=> gem_version1 # => 1
177
+ #
178
+ # @example A GemVersion is equal to itself
179
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3')
180
+ # gem_version1 <=> gem_version1 # => 0
181
+ #
182
+ # @example A pre-release version is always older than a normal version
183
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3.alpha.1')
184
+ # gem_version2 = VersionBoss::GemVersion.new('1.2.3')
185
+ # gem_version1 <=> gem_version2 # => -1
186
+ #
187
+ # @example Pre-releases are compared by the parts of the pre-release version
188
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3.alpha.1')
189
+ # gem_version2 = VersionBoss::GemVersion.new('1.2.3.alpha.2')
190
+ # gem_version1 <=> gem_version2 # => -1
191
+ #
192
+ # @example Build metadata is ignored when comparing versions
193
+ # gem_version1 = VersionBoss::GemVersion.new('1.2.3+build.100')
194
+ # gem_version2 = VersionBoss::GemVersion.new('1.2.3+build.101')
195
+ # gem_version1 <=> gem_version2 # => 0
196
+ #
197
+ # @param other [GemVersion] the other GemVersion to compare to
198
+ #
199
+ # @return [Integer] -1 if self < other, 0 if self == other, or 1 if self > other
200
+ #
201
+ # @raise [VersionBoss::Error] other is not a gem_version
202
+ #
203
+ def <=>(other)
204
+ assert_other_is_a_gem_version(other)
205
+
206
+ core_comparison = compare_core_parts(other)
207
+ pre_release_comparison = compare_pre_release_part(other)
208
+
209
+ return core_comparison unless core_comparison.zero? && !pre_release_comparison.zero?
210
+
211
+ return 1 if pre_release.empty?
212
+ return -1 if other.pre_release.empty?
213
+
214
+ pre_release_comparison
215
+ end
216
+
217
+ # Determine if the version string is a valid gem_version
218
+ #
219
+ # Override this method in a subclass to provide extra or custom validation.
220
+ #
221
+ # @example
222
+ # VersionBoss::GemVersion.new('1.2.3').valid? # => true
223
+ # VersionBoss::GemVersion.new('1.2.3.alpha.1+build.001').valid? # => true
224
+ # VersionBoss::GemVersion.new('bogus').valid? # => raises VersionBoss::Error
225
+ #
226
+ # @return [Boolean] true if the version string is a valid gem_version
227
+ #
228
+ def valid?
229
+ # If major is set, then so is everything else
230
+ !major.nil?
231
+ end
232
+
233
+ # Two versions are equal if their version strings are equal
234
+ #
235
+ # @example
236
+ # VersionBoss::GemVersion.new('1.2.3') == '1.2.3' # => true
237
+ #
238
+ # @param other [GemVersion] the other GemVersion to compare to
239
+ #
240
+ # @return [Boolean] true if the version strings are equal
241
+ #
242
+ def ==(other)
243
+ version == other.to_s
244
+ end
245
+
246
+ # The string representation of a GemVersion is its version string
247
+ #
248
+ # @example
249
+ # VersionBoss::GemVersion.new('1.2.3').to_s # => '1.2.3'
250
+ #
251
+ # @return [String] the version string
252
+ #
253
+ def to_s
254
+ version
255
+ end
256
+
257
+ private
258
+
259
+ # Parse @version into its parts
260
+ # @return [void]
261
+ # @api private
262
+ def parse
263
+ return unless (match_data = version.match(REGEXP_FULL))
264
+
265
+ core_parts(match_data)
266
+ pre_release_part(match_data)
267
+ end
268
+
269
+ # Compare the major, minor, and patch parts of this GemVersion to other
270
+ # @param other [GemVersion] the other GemVersion to compare to
271
+ # @return [Integer] -1 if self < other, 0 if self == other, or 1 if self > other
272
+ # @api private
273
+ def compare_core_parts(other)
274
+ identifiers = [major.to_i, minor.to_i, patch.to_i]
275
+ other_identifiers = [other.major.to_i, other.minor.to_i, other.patch.to_i]
276
+
277
+ identifiers <=> other_identifiers
278
+ end
279
+
280
+ # Compare two pre-release identifiers
281
+ #
282
+ # Implements the rules for precedence for comparing two pre-release identifiers
283
+ # from the Semantic Versioning 2.0.0 Specification.
284
+ #
285
+ # @param identifier [String, Integer] the identifier to compare
286
+ # @param other_identifier [String, Integer] the other identifier to compare
287
+ # @return [Integer] -1, 0, or 1
288
+ # @api private
289
+ def compare_identifiers(identifier, other_identifier)
290
+ return 1 if other_identifier.nil?
291
+ return -1 if identifier.is_a?(Integer) && other_identifier.is_a?(String)
292
+ return 1 if other_identifier.is_a?(Integer) && identifier.is_a?(String)
293
+
294
+ identifier <=> other_identifier
295
+ end
296
+
297
+ # Compare two pre-release version parts
298
+ #
299
+ # Implements the rules for precedence for comparing the pre-release part of
300
+ # one version with the pre-release part of another version from the Semantic
301
+ # Versioning 2.0.0 Specification.
302
+ #
303
+ # @param other [GemVersion] the other GemVersion to compare to
304
+ # @return [Integer] -1, 0, or 1
305
+ # @api private
306
+ def compare_pre_release_part(other)
307
+ pre_release_identifiers.zip(other.pre_release_identifiers).each do |field, other_field|
308
+ result = compare_identifiers(field&.identifier, other_field&.identifier)
309
+ return result unless result.zero?
310
+ end
311
+ pre_release_identifiers.size < other.pre_release_identifiers.size ? -1 : 0
312
+ end
313
+
314
+ # Raise a error if other is not a valid Gem version
315
+ # @param other [GemVersion] the other to check
316
+ # @return [void]
317
+ # @raise [VersionBoss::Error] if other is not a valid Gem version
318
+ # @api private
319
+ def assert_other_is_a_gem_version(other)
320
+ raise VersionBoss::Error, 'other must be a VersionBoss::Gem::Version' unless other.is_a?(Version)
321
+ end
322
+
323
+ # Raise a error if the given version is not a string
324
+ # @param version [GemVersion] the version to check
325
+ # @return [void]
326
+ # @raise [VersionBoss::Error] if the given version is not a string
327
+ # @api private
328
+ def assert_version_must_be_a_string(version)
329
+ raise VersionBoss::Error, 'Version must be a string' unless version.is_a?(String)
330
+ end
331
+
332
+ # Raise a error if this version object is not a valid Gem version
333
+ # @return [void]
334
+ # @raise [VersionBoss::Error] if other is not a valid Gem version
335
+ # @api private
336
+ def assert_valid_version
337
+ raise VersionBoss::Error, "Not a valid version string: #{version}" unless valid?
338
+ end
339
+
340
+ # Set the major, minor, and patch parts of this version
341
+ # @param match_data [MatchData] the match data from the version string
342
+ # @return [void]
343
+ # @api private
344
+ def core_parts(match_data)
345
+ @major = match_data[:major]
346
+ @minor = match_data[:minor]
347
+ @patch = match_data[:patch]
348
+ end
349
+
350
+ # Set the pre-release of this version
351
+ # @param match_data [MatchData] the match data from the version string
352
+ # @return [void]
353
+ # @api private
354
+ def pre_release_part(match_data)
355
+ @pre_release = match_data[:pre_release] || ''
356
+ @pre_release_identifiers = tokenize_pre_release_part(@pre_release)
357
+ end
358
+
359
+ # A pre-release part of a version which consists of an optional prefix and an identifier
360
+ # @api private
361
+ class PreReleaseIdentifier
362
+ # @attribute prefix [r]
363
+ #
364
+ # Gem versions can optionally prefix pre-release identifiers with a period
365
+ #
366
+ # The prefix is not significant when sorting.
367
+ #
368
+ # The prefix is saved so that the pre-release part can be reconstructed
369
+ # exactly as it was given.
370
+ #
371
+ # @return [String] '.' if the pre-release type is prefixed with a period, otherwise ''
372
+ #
373
+ # @api private
374
+ #
375
+ attr_reader :prefix
376
+
377
+ # @attribute identifier [r]
378
+ #
379
+ # The pre-release identifier
380
+ #
381
+ # The identifier is converted to an integer if it consists only of digits.
382
+ # Otherwise, it is left as a string.
383
+ #
384
+ # @return [String, Integer]
385
+ #
386
+ # @api private
387
+ #
388
+ attr_reader :identifier
389
+
390
+ # Create a new PreReleaseIdentifier keeping track of the optional prefix
391
+ #
392
+ # @example
393
+ # PreReleaseIdentifier.new('.pre') #=> #<PreReleaseIdentifier @identifier="pre", @prefix=".">
394
+ # PreReleaseIdentifier.new('pre') #=> #<PreReleaseIdentifier @identifier="pre", @prefix="">
395
+ # PreReleaseIdentifier.new('.1') #=> #<PreReleaseIdentifier @identifier=1, @prefix=".">
396
+ # PreReleaseIdentifier.new('1') #=> #<PreReleaseIdentifier @identifier=1, @prefix="">
397
+ #
398
+ # @param pre_release_part_str [String] the pre-release part string to parse
399
+ #
400
+ # @return [String]
401
+ #
402
+ # @api private
403
+ #
404
+ def initialize(pre_release_part_str)
405
+ @prefix = pre_release_part_str.start_with?('.') ? '.' : ''
406
+ @identifier = determine_part(pre_release_part_str)
407
+ end
408
+
409
+ private
410
+
411
+ # Determine if the pre-release part is a number or a string
412
+ #
413
+ # @param pre_release_part_str [String] the pre-release part string to parse
414
+ #
415
+ # @return [String, Integer]
416
+ #
417
+ # @api private
418
+ #
419
+ def determine_part(pre_release_part_str)
420
+ clean_str = pre_release_part_str.delete_prefix('.')
421
+ clean_str.match?(/\A\d+\z/) ? clean_str.to_i : clean_str
422
+ end
423
+ end
424
+
425
+ # Parse the pre-release part of the version into an array of identifiers
426
+ #
427
+ # A pre-release version is denoted by appending one or more pre-release identifiers
428
+ # to the core version. The first pre-release identifier must consist of only ASCII letters.
429
+ # Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes.
430
+ # Each identifier may be prefixed with a period '.'.
431
+ #
432
+ # @example
433
+ # tokenize_pre_release_part('.alpha.1') #=> [
434
+ # #<PreReleaseIdentifier @identifier="alpha", @prefix=".">,
435
+ # #<PreReleaseIdentifier @identifier=1, @prefix=".">
436
+ # ]
437
+ #
438
+ # @param str [String] the pre-release part string to parse (for example: '.pre.1')
439
+ #
440
+ # @return [Array<PreReleaseIdentifier>]
441
+ #
442
+ # @api private
443
+ #
444
+ def tokenize_pre_release_part(str)
445
+ str.scan(/\.?\d+|\.?[a-zA-Z]+/).map { |pre_release_part_str| PreReleaseIdentifier.new(pre_release_part_str) }
446
+ end
447
+ end
448
+ end
449
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VersionBoss
4
+ module Gem
5
+ # Represents a file that contains the gem's version and can update the version
6
+ #
7
+ # Use VersionFileFactory.find create a VersionFile instance.
8
+ #
9
+ # @api public
10
+ #
11
+ class VersionFile
12
+ # Create an VersionFile instance
13
+ #
14
+ # Use VersionFileFactory.find create a VersionFile instance.
15
+ #
16
+ # @example
17
+ # version_file = VersionFile.new('VERSION', 'VERSION = "', '1.2.3', '"')
18
+ #
19
+ # @param path [String] the path to the file relative to the current directory
20
+ # @param content_before [String] the content before the version
21
+ # @param version [String] the version
22
+ # @param content_after [String] the content after the version
23
+ #
24
+ # @raise [VersionBoss::Error] if the version is not an IncrementableVersion
25
+ #
26
+ # @api private
27
+ #
28
+ def initialize(path, content_before, version, content_after)
29
+ raise VersionBoss::Error, 'version must be an IncrementableVersion' unless
30
+ version.is_a?(VersionBoss::Gem::IncrementableVersion)
31
+
32
+ @path = path
33
+ @content_before = content_before
34
+ @version = version
35
+ @content_after = content_after
36
+ end
37
+
38
+ # @!attribute [r]
39
+ #
40
+ # The path to the file relative to the current directory
41
+ #
42
+ # @example
43
+ # version_file = VersionFile.new('lib/version_boss/version.rb', 'VERSION = "', '1.2.3', '"')
44
+ # version_file.path # => 'lib/version_boss/version.rb'
45
+ # @return [String]
46
+ # @api public
47
+ attr_reader :path
48
+
49
+ # @!attribute [r]
50
+ #
51
+ # The content in the version file before the version
52
+ #
53
+ # @example
54
+ # version_file = VersionFile.new('lib/version_boss/version.rb', 'VERSION = "', '1.2.3', '"')
55
+ # version_file.content_before # => 'VERSION = "'
56
+ # @return [String]
57
+ # @api public
58
+ attr_reader :content_before
59
+
60
+ # @!attribute [r]
61
+ #
62
+ # The version from the version file
63
+ #
64
+ # @example
65
+ # version = VersionBoss::IncrementableVersion.new('1.2.3')
66
+ # version_file = VersionFile.new('lib/version_boss/version.rb', 'VERSION = "', version, '"')
67
+ # version_file.version.to_s # => '1.2.3'
68
+ # @return [VersionBoss::IncrementableVersion]
69
+ # @raise [VersionBoss::Error] if the version is not an IncrementableVersion
70
+ # @api public
71
+ attr_reader :version
72
+
73
+ # @!attribute [r]
74
+ #
75
+ # The content in the version file before the version
76
+ #
77
+ # @example
78
+ # version_file = VersionFile.new('lib/version_boss/version.rb', 'VERSION = "', '1.2.3', '"')
79
+ # version_file.content_after # => '"'
80
+ # @return [String]
81
+ # @api public
82
+ attr_reader :content_after
83
+
84
+ # Update the version in the version file
85
+ #
86
+ # @param new_version [VersionBoss::IncrementableVersion] the new version
87
+ # @example
88
+ # version_file = VersionFile.new('lib/version_boss/version.rb', 'VERSION = "', '1.2.3', '"')
89
+ # version_file.version = '1.2.4'
90
+ # @return [Void]
91
+ # @raise [VersionBoss::Error] if new_version is not an IncrementableVersion
92
+ # @api public
93
+ #
94
+ def version=(new_version)
95
+ raise VersionBoss::Error, 'new_version must be an IncrementableVersion' unless
96
+ new_version.is_a?(VersionBoss::Gem::IncrementableVersion)
97
+
98
+ @version = version
99
+ File.write(path, content_before + new_version.to_s + content_after)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'version_file_sources'
4
+
5
+ module VersionBoss
6
+ module Gem
7
+ # Finds the file that contains the gem's version and returns a VersionFile instance
8
+ #
9
+ # @api public
10
+ #
11
+ class VersionFileFactory
12
+ # The list of VersionFileSources to check for the version file
13
+ #
14
+ # The order of the list is important. The first VersionFileSource that finds a version file will be used.
15
+ #
16
+ VERSION_FILE_SOURCES = [
17
+ VersionBoss::Gem::VersionFileSources::Version,
18
+ VersionBoss::Gem::VersionFileSources::VersionRb,
19
+ VersionBoss::Gem::VersionFileSources::Gemspec
20
+ ].freeze
21
+
22
+ # Finds the version file for the gem
23
+ #
24
+ # @example
25
+ # version_file = VersionBoss::Gem::VersionFileFactory.find
26
+ # version_file.path # => 'lib/version_boss/version.rb'
27
+ # version_file.version # => '1.2.3'
28
+ #
29
+ # @return [VersionBoss::VersionFile, nil] the version file or nil if no version file was found
30
+ #
31
+ def self.find
32
+ VERSION_FILE_SOURCES.each do |version_file_source|
33
+ version_file = version_file_source.find
34
+ return version_file if version_file
35
+ end
36
+ nil
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../version_file'
4
+
5
+ module VersionBoss
6
+ module Gem
7
+ module VersionFileSources
8
+ # Base class for a version file source which implements #find
9
+ #
10
+ # @api public
11
+ #
12
+ class Base
13
+ # The first file from #glob whose content matches #content_regexp
14
+ #
15
+ # @example
16
+ # version_file = VersionBoss::Gem::VersionFileSources::Gemspec.find
17
+ #
18
+ # @return [VersionBoss::Gem::VersionFile, nil] the version file or nil if no version file was found
19
+ #
20
+ def self.find
21
+ Dir[glob].filter_map do |path|
22
+ if (match = File.read(path).match(content_regexp))
23
+ version = VersionBoss::Gem::IncrementableVersion.new(match[:version])
24
+ VersionBoss::Gem::VersionFile.new(path, match[:content_before], version, match[:content_after])
25
+ end
26
+ end.first
27
+ end
28
+
29
+ private
30
+
31
+ # The version file regexp
32
+ #
33
+ # A regular expression that matches the version file and has three named captures:
34
+ # - content_before: the content before the version
35
+ # - version: the version
36
+ # - content_after: the content after the version
37
+ #
38
+ # @return [Regexp]
39
+ # @api private
40
+ private_class_method def self.content_regexp
41
+ raise NotImplementedError, 'You must implement #content_regexp in a subclass'
42
+ end
43
+
44
+ # A glob that matches potential version files
45
+ #
46
+ # Files matching this glob will be checked to see if they match #version_file_regexp
47
+ #
48
+ # @return [String]
49
+ # @api private
50
+ private_class_method def self.glob
51
+ raise NotImplementedError, 'You must implement #glob in a subclass'
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../regexp'
5
+
6
+ module VersionBoss
7
+ module Gem
8
+ module VersionFileSources
9
+ # Checks for the gem's version in a file named *.gemspec
10
+ #
11
+ # @api public
12
+ #
13
+ class Gemspec < Base
14
+ # The regexp to find the version and surrounding content within the gemspec
15
+ VERSION_REGEXP = /
16
+ \A
17
+ (?<content_before>
18
+ .*
19
+ \.version\s*=\s*(?<quote>['"])
20
+ )
21
+ (?<version>#{REGEXP.source})
22
+ (?<content_after>\k<quote>.*)
23
+ \z
24
+ /xm
25
+
26
+ private
27
+
28
+ # The version file regexp
29
+ # @return [Regexp]
30
+ # @api private
31
+ private_class_method def self.content_regexp = VERSION_REGEXP
32
+
33
+ # A glob that matches potential version files
34
+ # @return [String]
35
+ # @api private
36
+ private_class_method def self.glob = '*.gemspec'
37
+ end
38
+ end
39
+ end
40
+ end