version_boss 0.1.0

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