Class: Version::Number

Inherits:
Object
  • Object
show all
Includes:
Comparable, Enumerable
Defined in:
lib/versus/number.rb

Overview

Represents a versiou number. Developer SHOULD use three point SemVer standard, but this class is mildly flexible in it’s support for variations.

See Also:

Constant Summary collapse

STATES =

Recognized build states in order of completion. This is only used when by #bump_state.

['alpha', 'beta', 'pre', 'rc']

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*points) ⇒ Number

Creates a new version.

Parameters:

  • points (Array)

    version points



29
30
31
32
33
34
35
# File 'lib/versus/number.rb', line 29

def initialize(*points)
  @crush = false
  points.map! do |point|
    sane_point(point)
  end
  @tuple = points.flatten.compact
end

Instance Attribute Details

#tupleObject (readonly)

Return the undelying segments array.



479
480
481
# File 'lib/versus/number.rb', line 479

def tuple
  @tuple
end

Class Method Details

.[](*args) ⇒ Object

Shortcut for creating a new verison number given segmented elements.

VersionNumber[1,0,0].to_s
#=> "1.0.0"

VersionNumber[1,0,0,:pre,2].to_s
#=> "1.0.0.pre.2"


53
54
55
# File 'lib/versus/number.rb', line 53

def self.[](*args)
  new(*args)
end

.cmp(version1, version2) ⇒ Object



78
79
80
# File 'lib/versus/number.rb', line 78

def self.cmp(version1, version2)
  # TODO: class level compare might be handy
end

.parse(version) ⇒ Version

Parses a version string.

Parameters:

  • string (String)

    The version string.

Returns:

  • (Version)

    The parsed version.



66
67
68
69
70
71
72
73
74
75
# File 'lib/versus/number.rb', line 66

def self.parse(version)
  case version
  when String
    new(*version.split('.'))
  when Number #self.class
    new(*version.to_a)
  else
    new(*version.to_ary)  #to_a) ?
  end
end

Instance Method Details

#<=>(other) ⇒ Object

Compare versions.



270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/versus/number.rb', line 270

def <=>(other)
  [@tuple.size, other.size].max.times do |i|
    p1, p2 = (@tuple[i] || 0), (other[i] || 0)
    # this is bit tricky, basically a string < integer.
    if p1.class != p2.class
      cmp = p2.to_s <=> p1.to_s
    else
      cmp = p1 <=> p2
    end
    return cmp unless cmp == 0
  end
  #(@tuple.size <=> other.size) * -1
  return 0
end

#=~(other) ⇒ Object

For pessimistic constraint (like ‘~>’ in gems).

FIXME: Ensure it can handle trailing state.



288
289
290
291
292
293
294
295
296
# File 'lib/versus/number.rb', line 288

def =~(other)
  upver = other.bump(:last)
  if other.size > 1
    upver = other.bump(-2)
  else

  end
  self >= other and self < upver
end

#[](index) ⇒ Object

Fetch a sepecific segement by index number. In no value is found at that position than zero (0) is returned instead.

v = Version::Number[1,2,0]
v[0]  #=> 1
v[1]  #=> 2
v[3]  #=> 0
v[4]  #=> 0

Zero is returned instead of nil to make different version numbers easier to compare.



199
200
201
# File 'lib/versus/number.rb', line 199

def [](index)
  @tuple.fetch(index,0)
end

#alpha?Boolean

Returns:

  • (Boolean)


166
167
168
169
# File 'lib/versus/number.rb', line 166

def alpha?
  s = status.dowcase
  s == 'alpha' or s == 'a'
end

#beta?Boolean

Returns:

  • (Boolean)


172
173
174
175
# File 'lib/versus/number.rb', line 172

def beta?
  s = status.dowcase
  s == 'beta' or s == 'b'
end

#buildObject

The build.



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/versus/number.rb', line 98

def build
  if b = state_index
    str = @tuple[b..-1].join('.')
    str = crush_point(str) if crush?
    str
  elsif @tuple[3].nil?
    nil
  else
    str = @tuple[3..-1].join('.')
    str = crush_point(str) if crush?
    str
  end
end

#build=(point) ⇒ Object

Parameters:

  • build (Integer, nil)

    (nil) The build version number.



154
155
156
# File 'lib/versus/number.rb', line 154

def build=(point)
  @tuple = @tuple[0...state_index] + sane_point(point)
end

#build_numberObject

Return the state revision count. This is the number that occurs after the state.

Version::Number[1,2,0,:rc,4].build_number
#=> 4


126
127
128
129
130
131
132
# File 'lib/versus/number.rb', line 126

def build_number #revision
  if i = state_index
    self[i+1] || 0
  else
    nil
  end
end

#bump(which = :patch) ⇒ Object

Bump the version returning a new version number object. Select which segement to bump by name: major, minor, patch, state, build and also last.

Version::Number[1,2,0].bump(:patch).to_s
#=> "1.2.1"

Version::Number[1,2,1].bump(:minor).to_s
#=> "1.3.0"

Version::Number[1,3,0].bump(:major).to_s
#=> "2.0.0"

Version::Number[1,3,0,:pre,1].bump(:build).to_s
#=> "1.3.0.pre.2"

Version::Number[1,3,0,:pre,2].bump(:state).to_s
#=> "1.3.0.rc.1"


339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/versus/number.rb', line 339

def bump(which=:patch)
  which = which.to_sym unless Integer === which

  case which
  when Integer
    bump_at(which)
  when :major, :first
    bump_major
  when :minor
    bump_minor
  when :patch
    bump_patch
  when :state, :status
    bump_state
  when :build
    bump_build
  when :revision
    bump_revision
  when :last
    bump_last
  else
    # TODO: why is this not an error?
    self.class.new(@tuple.dup.compact)
  end
end

#bump_at(index) ⇒ Object



381
382
383
384
385
386
387
388
# File 'lib/versus/number.rb', line 381

def bump_at(index)
  i = index
  if n = inc(@tuple[i])
    v = @tuple[0...i] + [n] + (@tuple[i+1] ? [1] : [])
  else
    v = @tuple[0...i]
  end
end

#bump_buildObject



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/versus/number.rb', line 408

def bump_build
  if i = state_index
    if i == @tuple.size - 1
      v = @tuple + [1]
    else
      v = @tuple[0...-1] + [inc(@tuple.last)]
    end
  else
    if @tuple.size <= 3
      v = @tuple + [1]
    else
      v = @tuple[0...-1] + [inc(@tuple.last)]
    end
  end
  self.class.new(v.compact)
end

#bump_build_numberObject

revision



426
427
428
429
430
431
432
433
# File 'lib/versus/number.rb', line 426

def bump_build_number #revision
  if i = state_index
    v = @tuple[0...-1] + [inc(@tuple.last)]
  else
    v = @tuple[0..2] + ['alpha', 1]
  end
  self.class.new(v.compact)
end

#bump_lastObject



436
437
438
439
# File 'lib/versus/number.rb', line 436

def bump_last
  v = @tuple[0...-1] + [inc(@tuple.last)]
  self.class.new(v.compact)
end

#bump_majorObject



366
367
368
# File 'lib/versus/number.rb', line 366

def bump_major
  self.class[inc(major), 0, 0]
end

#bump_minorObject



371
372
373
# File 'lib/versus/number.rb', line 371

def bump_minor
  self.class[major, inc(minor), 0]
end

#bump_patchObject



376
377
378
# File 'lib/versus/number.rb', line 376

def bump_patch
  self.class[major, minor, inc(patch)]
end

#bump_stateObject Also known as: bump_status



391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/versus/number.rb', line 391

def bump_state
  if i = state_index
    if n = inc(@tuple[i])
      v = @tuple[0...i] + [n] + (@tuple[i+1] ? [1] : [])
    else
      v = @tuple[0...i]
    end
  else
    v = @tuple.dup
  end
  self.class.new(v.compact)
end

#crush?Boolean

Does the version string representation compact string segments with the subsequent number segement?

Returns:

  • (Boolean)


461
462
463
# File 'lib/versus/number.rb', line 461

def crush?
  @crush
end

#crush_point(string) ⇒ Object (private)

Take a point string rendering of a version and crush it!



505
506
507
# File 'lib/versus/number.rb', line 505

def crush_point(string)
  string.gsub(/(^|\.)(\D+)\.(\d+)(\.|$)/, '\2\3')
end

#each(&block) ⇒ Object

Iterate of each segment of the version. This allows all enumerable methods to be used.

Version::Number[1,2,3].map{|i| i + 1}
#=> [2,3,4]

Though keep in mind that the state segment is not a number (and techincally any segment can be a string instead of an integer).



307
308
309
# File 'lib/versus/number.rb', line 307

def each(&block)
  @tuple.each(&block)
end

#eql?(other) ⇒ Boolean

Strict equality.

Returns:

  • (Boolean)


260
261
262
# File 'lib/versus/number.rb', line 260

def eql?(other)
  @tuple = other.tuple
end

#hashObject



40
41
42
# File 'lib/versus/number.rb', line 40

def hash
  @tuple.hash
end

#inc(val) ⇒ Object (private)

Segement incrementor.



525
526
527
528
529
530
531
# File 'lib/versus/number.rb', line 525

def inc(val)
  if i = STATES.index(val.to_s)
    STATES[i+1]
  else
    val.succ
  end
end

#inspectObject

Returns a String detaling the version number. Essentially it is the same as #to_s.

VersionNumber[1,2,0].inspect
#=> "1.2.0"


237
238
239
# File 'lib/versus/number.rb', line 237

def inspect
  to_s
end

#majorObject

Major version number



83
84
85
# File 'lib/versus/number.rb', line 83

def major
  (state_index && state_index == 0) ? nil : self[0]
end

#major=(number) ⇒ Object

Parameters:

  • major (Integer)

    The major version number.



136
137
138
# File 'lib/versus/number.rb', line 136

def major=(number)
  @tuple[0] = number.to_i
end

#match?(*constraints) ⇒ Boolean

Does this version match a given constraint? The constraint is a String in the form of “operator number”. – TODO: match? will change as Constraint class is improved. ++

Returns:

  • (Boolean)


470
471
472
473
474
# File 'lib/versus/number.rb', line 470

def match?(*constraints)
  constraints.all? do |c|
    Constraint.constraint_lambda(c).call(self)
  end
end

#minorObject

Minor version number



88
89
90
# File 'lib/versus/number.rb', line 88

def minor
  (state_index && state_index <= 1) ? nil : self[1]
end

#minor=(number) ⇒ Object

Parameters:

  • minor (Integer, nil)

    The minor version number.



142
143
144
# File 'lib/versus/number.rb', line 142

def minor=(number)
  @tuple[1] = number.to_i
end

#patchObject

Patch version number



93
94
95
# File 'lib/versus/number.rb', line 93

def patch
  (state_index && state_index <= 2) ? nil : self[2]
end

#patch=(number) ⇒ Object

Parameters:

  • patch (Integer, nil)

    The patch version number.



148
149
150
# File 'lib/versus/number.rb', line 148

def patch=(number)
  @tuple[2] = number.to_i
end

#prerelease?Boolean

Returns:

  • (Boolean)


178
179
180
# File 'lib/versus/number.rb', line 178

def prerelease?
  status == 'pre'
end

#release_candidate?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/versus/number.rb', line 183

def release_candidate?
  status == 'rc'
end

#restate(state, revision = 1) ⇒ Object

Return a new version have the same major, minor and patch levels, but with a new state and revision count.

Version::Number[1,2,3].restate(:pre,2).to_s
#=> "1.2.3.pre.2"

Version::Number[1,2,3,:pre,2].restate(:rc,4).to_s
#=> "1.2.3.rc.4"


450
451
452
453
454
455
456
457
# File 'lib/versus/number.rb', line 450

def restate(state, revision=1)
  if i = state_index
    v = @tuple[0...i] + [state.to_s] + [revision]
  else
    v = @tuple[0...3] + [state.to_s] + [revision]
  end
  self.class.new(v)
end

#sane_point(point) ⇒ Object (private)

Convert a segment into an integer or string.



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/versus/number.rb', line 484

def sane_point(point)      
  point = point.to_s if Symbol === point
  case point
  when Integer
    point
  when /[.]/
    point.split('.').map{ |p| sane_point(p) }
  when /^\d+$/
    point.to_i
  when /^(\d+)(\w+)(\d+)$/
    @crush = true
    [$1.to_i, $2, $3.to_i]
  when /^(\w+)(\d+)$/
    @crush = true
    [$1, $2.to_i]
  else
    point
  end
end

#sizeObject

Return the number of version segements.

Version::Number[1,2,3].size
#=> 3


316
317
318
# File 'lib/versus/number.rb', line 316

def size
  @tuple.size
end

#stable?Boolean Also known as: stable_release?

Returns:

  • (Boolean)


159
160
161
# File 'lib/versus/number.rb', line 159

def stable?
  build.nil?
end

#stateObject Also known as: status



113
114
115
# File 'lib/versus/number.rb', line 113

def state
  state_index ? @tuple[state_index] : nil
end

#state_indexObject (private)

Return the index of the first recognized state.

VersionNumber[1,2,3,'pre',3].state_index
#=> 3

You might ask why this is needed, since the state position should always be 3. However, there isn’t always a state entry, which means this method will return nil, and we also leave open the potential for extra-long version numbers –though we do not recommend the idea, it is possible.



520
521
522
# File 'lib/versus/number.rb', line 520

def state_index
  @tuple.index{ |s| String === s }
end

#to_aObject

Returns a duplicate of the underlying version tuple.



205
206
207
# File 'lib/versus/number.rb', line 205

def to_a
  @tuple.dup
end

#to_sObject

Converts version to a dot-separated string.

Version::Number[1,2,0].to_s
#=> "1.2.0"

TODO: crush



215
216
217
218
219
# File 'lib/versus/number.rb', line 215

def to_s         
  str = @tuple.compact.join('.')
  str = crush_point(str) if crush?
  return str
end

#to_strObject

This method is the same as #to_s. It is here becuase ‘File.join` calls it instead of #to_s.

VersionNumber[1,2,0].to_str
#=> "1.2.0"


227
228
229
# File 'lib/versus/number.rb', line 227

def to_str
  to_s
end

#to_yaml(opts = {}) ⇒ String

Converts the version to YAML.

– TODO: Should this be here? ++

Parameters:

  • opts (Hash) (defaults to: {})

    Options supporte by YAML.

Returns:

  • (String)

    The resulting YAML.



253
254
255
# File 'lib/versus/number.rb', line 253

def to_yaml(opts={})
  to_s.to_yaml(opts)
end