Class: QB::Package::Version

Inherits:
Util::Resource show all
Includes:
Util::DockerMixin
Defined in:
lib/qb/package/version.rb

Overview

An attempt to unify NPM and Gem version schemes to a reasonable extend, and hopefully cover whatever else the cat may drag in.

Intended to be immutable for practical purposes.

Constant Summary collapse

NUMBER_SEGMENT =

Constants

t.non_neg_int
NAME_SEGMENT =
t.str
MIXED_SEGMENT =
t.union NUMBER_SEGMENT, NAME_SEGMENT

Constants included from Util::DockerMixin

Util::DockerMixin::DOCKER_TAG_MAX_CHARACTERS, Util::DockerMixin::DOCKER_TAG_VALID_RE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Util::DockerMixin

included

Constructor Details

#initialize(**values) ⇒ Version

Construct a new Version



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/qb/package/version.rb', line 194

def initialize **values
  super **values
  
  @release = [major, minor, patch].join '.'
  
  @level = t.match prerelease[0], {
    t.is(nil) => ->(_) {
      if build.empty?
        'release'
      end
    },
    
    NAME_SEGMENT => ->(str) { str },
    
    NUMBER_SEGMENT => ->(int) { nil },
  }
end

Instance Attribute Details

#levelObject (readonly)

Attributes



82
83
84
# File 'lib/qb/package/version.rb', line 82

def level
  @level
end

#releaseObject (readonly)

Attributes



82
83
84
# File 'lib/qb/package/version.rb', line 82

def release
  @release
end

Class Method Details

.from_docker_tag(version) ⇒ QB::Package::Version

Parse Docker image tag version into a string. Reverse of #docker_tag.

Parameters:

  • version (String)

    String version to parse.

Returns:



166
167
168
# File 'lib/qb/package/version.rb', line 166

def self.from_docker_tag version
  from_string(version.gsub('_', '+')).merge raw: version
end

.from_gem_version(version) ⇒ QB::Package::Version

Create a Version instance from a QB::Package::Version.

Parameters:

Returns:



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/qb/package/version.rb', line 109

def self.from_gem_version version
  # release segments are everything before a string
  release_segments = version.segments.take_while { |seg|
    !seg.is_a?(String)
  }
  
  # We don't support > 3 release segments to make life somewhat
  # reasonable. Yeah, I think I've seen projects do it. We'll cross that
  # bridge if and when we get to it.
  if release_segments.length > 3
    raise ArgumentError,
          "We don't handle releases with more than 3 segments " +
          "(found #{ release_segments.inspect } in #{ version })"
  end
  
  prerelease_segments = version.segments[release_segments.length..-1]
  
  new raw: version.to_s,
      major: release_segments[0] || 0,
      minor: release_segments[1] || 0,
      patch: release_segments[2] || 0,
      prerelease: prerelease_segments,
      build: []
end

.from_npm_version(version) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/qb/package/version.rb', line 134

def self.from_npm_version version
  stmt = NRSER.squish <<-END
    var Semver = require('semver');
    
    console.log(
      JSON.stringify(
        Semver(#{ JSON.dump version })
      )
    );
  END
  
  parse = JSON.load Cmds.new(
    "node --eval %s", args: [stmt], chdir: QB::ROOT
  ).out!
  
  new raw: version,
      major: parse['major'],
      minor: parse['minor'],
      patch: parse['patch'],
      prerelease: parse['prerelease'],
      build: parse['build']
end

.from_string(string) ⇒ QB::Package::Version

Parse string version into an instance. Accept Semver, Ruby Gem and Docker image tag formats.

Parameters:

  • String (String)

    version to parse.

Returns:



179
180
181
182
183
184
185
186
187
# File 'lib/qb/package/version.rb', line 179

def self.from_string string
  if string.include? '_'
    self.from_docker_tag string
  elsif string.include?( '-' ) || string.include?( '+' )
    self.from_npm_version string
  else
    self.from_gem_version Gem::Version.new(string)
  end
end

.to_time_segment(time) ⇒ String

Returns Time formatted to be stuck in a version segment per Semver spec. We also strip out '-' to avoid possible parsing weirdness.

Returns:

  • (String)

    Time formatted to be stuck in a version segment per Semver spec. We also strip out '-' to avoid possible parsing weirdness.



95
96
97
# File 'lib/qb/package/version.rb', line 95

def self.to_time_segment time
  time.utc.iso8601.gsub /[^0-9A-Za-z]/, ''
end

Instance Method Details

#==(other) ⇒ Boolean

Test for equality.

Compares classes then #to_a results.

Parameters:

  • other (Object)

    Object to compare to self.

Returns:

  • (Boolean)

    True if self and other are considered equal.



405
406
407
408
# File 'lib/qb/package/version.rb', line 405

def == other
  other.class == self.class &&
  other.to_a == self.to_a 
end

#build?Boolean

"Gem" version format, so this will always be false when loading a Gem version.

Returns:

  • (Boolean)

    True if any build segments are present (stuff after '+' character in SemVer / "NPM" format). Tests if @build is empty.

    As of writing, we don't have a way to convey build segments in



246
247
248
# File 'lib/qb/package/version.rb', line 246

def build?
  !build.empty?
end

#build_commitreturn_type

TODO:

Document commit method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



325
326
327
328
329
# File 'lib/qb/package/version.rb', line 325

def build_commit
  if build?
    build.find { |seg| seg =~ /[0-9a-f]{7}/ }
  end
end

#build_dirty?return_type

TODO:

Document build_dirty? method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



259
260
261
262
263
# File 'lib/qb/package/version.rb', line 259

def build_dirty?
  if build?
    build.include? 'dirty'
  end
end

#build_version(branch: nil, ref: nil, time: nil, dirty: nil) ⇒ QB::Package::Version

Return a new QB::Package::Version with build information added.



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/qb/package/version.rb', line 363

def build_version branch: nil, ref: nil, time: nil, dirty: nil
  time = self.class.to_time_segment(time) unless time.nil?
  
  segments = [
    branch,
    ref,
    ('dirty' if dirty),
    time,
  ].reject &:nil?
  
  if segments.empty?
    raise ArgumentError,
          "Need to provide at least one of branch, ref, time."
  end
  
  merge raw: nil, build: segments
end

#dev?Boolean

Returns True if this version is a dev prerelease (first prerelease element is 'dev').

Returns:

  • (Boolean)

    True if this version is a dev prerelease (first prerelease element is 'dev').



279
280
281
# File 'lib/qb/package/version.rb', line 279

def dev?
  level == 'dev'
end

#docker_tagString

Docker image tag for the version.

See Util::DockerMixin::ClassMethods#to_docker_tag.

Returns:

  • (String)


338
339
340
# File 'lib/qb/package/version.rb', line 338

def docker_tag
  self.class.to_docker_tag semver
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


448
449
450
# File 'lib/qb/package/version.rb', line 448

def eql? other
  self == other && self.hash == other.hash
end

#hashObject



443
444
445
# File 'lib/qb/package/version.rb', line 443

def hash
  to_a.hash
end

#level?Boolean

Returns True if self is a prerelease version that starts with a string that we consider the 'level'.

Returns:

  • (Boolean)

    True if self is a prerelease version that starts with a string that we consider the 'level'.



270
271
272
# File 'lib/qb/package/version.rb', line 270

def level?
  !level.nil?
end

#prerelease?Boolean

Returns True if any prerelease segments are present (stuff after '-' in SemVer / "NPM" format, or the first string segment and anything following it in "Gem" format). Tests if @prerelease is not empty.

Returns:

  • (Boolean)

    True if any prerelease segments are present (stuff after '-' in SemVer / "NPM" format, or the first string segment and anything following it in "Gem" format). Tests if @prerelease is not empty.



233
234
235
# File 'lib/qb/package/version.rb', line 233

def prerelease?
  !prerelease.empty?
end

#prerelease_versionQB::Package::Version

Returns A new QB::Package::Version created from #release and #prerelease data, but without any build information.

Returns:



386
387
388
# File 'lib/qb/package/version.rb', line 386

def prerelease_version
  merge raw: nil, build: []
end

#rc?Boolean

Returns True if this version is a release candidate (first prerelease element is 'rc').

Returns:

  • (Boolean)

    True if this version is a release candidate (first prerelease element is 'rc').



288
289
290
# File 'lib/qb/package/version.rb', line 288

def rc?
  level == 'rc'
end

#release?Boolean

Returns True if this version is a release (no prerelease or build values).

Returns:

  • (Boolean)

    True if this version is a release (no prerelease or build values).



222
223
224
# File 'lib/qb/package/version.rb', line 222

def release?
  prerelease.empty? && build.empty?
end

#release_versionQB::Package::Version

Returns A new QB::Package::Version created from #release. Even if self is a release version already, still returns a new instance.

Returns:



354
355
356
# File 'lib/qb/package/version.rb', line 354

def release_version
  self.class.from_string release
end

#semverString Also known as: normalized

Returns The Semver version string (Major.minor.patch-prerelease+build format).

Returns:

  • (String)

    The Semver version string (Major.minor.patch-prerelease+build format).



300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/qb/package/version.rb', line 300

def semver
  result = release
  
  unless prerelease.empty?
    result += "-#{ prerelease.join '.' }"
  end
  
  unless build.empty?
    result += "+#{ build.join '.' }"
  end
  
  result
end

#to_aArray

Return array of the version elements in order from greatest to least precedence.

This is considered the representative structure for the object's data, from which all other values are dependently derived, and is used in #==, #hash and #eql?.

Examples:


version = QB::Package::Version.from_string(
  "0.1.2-rc.10+master.0ab1c3d"
)

version.to_a
# => [0, 1, 2, ['rc', 10], ['master', '0ab1c3d']]

QB::Package::Version.from_string('1').to_a
# => [1, nil, nil, [], []]

Returns:

  • (Array)


432
433
434
435
436
437
438
439
440
# File 'lib/qb/package/version.rb', line 432

def to_a
  [
    major,
    minor,
    patch,
    prerelease,
    build,
  ]
end

#to_sObject



453
454
455
# File 'lib/qb/package/version.rb', line 453

def to_s
  "#<QB::Package::Version #{ @raw }>"
end