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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +36 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +119 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +199 -0
- data/Rakefile +92 -0
- data/exe/gem-version-boss +6 -0
- data/lib/version_boss/gem/command_line.rb +443 -0
- data/lib/version_boss/gem/incrementable_version.rb +216 -0
- data/lib/version_boss/gem/regexp.rb +29 -0
- data/lib/version_boss/gem/version.rb +449 -0
- data/lib/version_boss/gem/version_file.rb +103 -0
- data/lib/version_boss/gem/version_file_factory.rb +40 -0
- data/lib/version_boss/gem/version_file_sources/base.rb +56 -0
- data/lib/version_boss/gem/version_file_sources/gemspec.rb +40 -0
- data/lib/version_boss/gem/version_file_sources/version.rb +36 -0
- data/lib/version_boss/gem/version_file_sources/version_rb.rb +39 -0
- data/lib/version_boss/gem/version_file_sources.rb +14 -0
- data/lib/version_boss/gem.rb +14 -0
- data/lib/version_boss/semver/command_line.rb +474 -0
- data/lib/version_boss/semver/incrementable_version.rb +192 -0
- data/lib/version_boss/semver/regexp.rb +40 -0
- data/lib/version_boss/semver/version.rb +374 -0
- data/lib/version_boss/semver.rb +9 -0
- data/lib/version_boss/version.rb +6 -0
- data/lib/version_boss.rb +10 -0
- data/version_boss.gemspec +56 -0
- metadata +243 -0
@@ -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
|