Class: Semverse::Constraint

Inherits:
Object
  • Object
show all
Defined in:
lib/semverse/constraint.rb

Constant Summary collapse

DEFAULT_OPERATOR =

The default constraint string.

Returns:

  • (String)
'='.freeze
OPERATORS =

The complete list of possible operators, paired with a proc to be used for evaluation.

Examples:

OPERATORS['='].call(constraint, version)

Returns:

  • (Hash<String, Proc>)
{ #:nodoc:
  '='  => ->(c, v) { v == c.version },
  '!=' => ->(c, v) { v != c.version },
  '>'  => ->(c, v) { v >  c.version },
  '<'  => ->(c, v) { v <  c.version },
  '>=' => ->(c, v) { v >= c.version },
  '<=' => ->(c, v) { v <= c.version },
  '~'  => method(:compare_approx),
  '~>' => method(:compare_approx),
}.freeze
REGEX =

This is a magical regular expression that matches the Semantic versioning specification found at semver.org. In addition to supporting all the possible versions, it also provides a named match_data which makes it really delightful to work with.

Returns:

  • (Regexp)
/\A
  ((?<operator>(#{OPERATORS.keys.join('|')}))[[:space:]]*)?
  (?<major>\d+)
  (\.(?<minor>\d+))?
  (\.(?<patch>\d+))?
  (\-(?<pre_release>[0-9A-Za-z\-\.]+))?
  (\+(?<build>[0-9A-Za-z\-\.]+))?
\z/x.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(constraint = '>= 0.0.0') ⇒ Constraint

Returns a new instance of Constraint.

Parameters:

  • constraint (#to_s) (defaults to: '>= 0.0.0')


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/semverse/constraint.rb', line 168

def initialize(constraint = '>= 0.0.0')
  constraint = constraint.to_s
  if constraint.nil? || constraint.empty?
    constraint = ">= 0.0.0"
  end
  @operator, @major, @minor, @patch, @pre_release, @build = self.class.split(constraint)

  unless ['~>', '~'].include?(@operator)
    @minor ||= 0
    @patch ||= 0
  end

  @version = Version.new([
    self.major,
    self.minor,
    self.patch,
    self.pre_release,
    self.build,
  ])
end

Instance Attribute Details

#buildObject (readonly)

Returns the value of attribute build.



159
160
161
# File 'lib/semverse/constraint.rb', line 159

def build
  @build
end

#majorObject (readonly)

Returns the value of attribute major.



155
156
157
# File 'lib/semverse/constraint.rb', line 155

def major
  @major
end

#minorObject (readonly)

Returns the value of attribute minor.



156
157
158
# File 'lib/semverse/constraint.rb', line 156

def minor
  @minor
end

#operatorObject (readonly)

Returns the value of attribute operator.



154
155
156
# File 'lib/semverse/constraint.rb', line 154

def operator
  @operator
end

#patchObject (readonly)

Returns the value of attribute patch.



157
158
159
# File 'lib/semverse/constraint.rb', line 157

def patch
  @patch
end

#pre_releaseObject (readonly)

Returns the value of attribute pre_release.



158
159
160
# File 'lib/semverse/constraint.rb', line 158

def pre_release
  @pre_release
end

#versionSemverse::Version (readonly)

Return the Semverse::Version representation of the major, minor, and patch attributes of this instance

Returns:



165
166
167
# File 'lib/semverse/constraint.rb', line 165

def version
  @version
end

Class Method Details

.coerce(object) ⇒ Constraint

Coerce the object into a constraint.

Parameters:

Returns:



9
10
11
12
13
14
15
# File 'lib/semverse/constraint.rb', line 9

def coerce(object)
  if object.nil?
    DEFAULT_CONSTRAINT
  else
    object.is_a?(self) ? object : new(object)
  end
end

.compare_approx(constraint, target_version) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/semverse/constraint.rb', line 97

def compare_approx(constraint, target_version)
  min = constraint.version
  max = if constraint.patch.nil?
    Version.new([min.major + 1, 0, 0, 0])
  elsif constraint.build
    identifiers = constraint.version.identifiers(:build)
    replace     = identifiers.last.to_i.to_s == identifiers.last.to_s ? "-" : nil
    Version.new([min.major, min.minor, min.patch, min.pre_release, identifiers.fill(replace, -1).join('.')])
  elsif constraint.pre_release
    identifiers = constraint.version.identifiers(:pre_release)
    replace     = identifiers.last.to_i.to_s == identifiers.last.to_s ? "-" : nil
    Version.new([min.major, min.minor, min.patch, identifiers.fill(replace, -1).join('.')])
  else
    Version.new([min.major, min.minor + 1, 0, 0])
  end
  min <= target_version && target_version < max
end

.satisfy_all(constraints, versions) ⇒ Array<Semverse::Version>

Returns all of the versions which satisfy all of the given constraints

Parameters:

Returns:



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/semverse/constraint.rb', line 23

def satisfy_all(constraints, versions)
  constraints = Array(constraints).collect do |con|
    con.is_a?(Constraint) ? con : Constraint.new(con)
  end.uniq

  versions = Array(versions).collect do |ver|
    ver.is_a?(Version) ? ver : Version.new(ver)
  end.uniq

  versions.select do |ver|
    constraints.all? { |constraint| constraint.satisfies?(ver) }
  end
end

.satisfy_best(constraints, versions) ⇒ Semverse::Version

Return the best version from the given list of versions for the given list of constraints

Parameters:

Returns:

Raises:



45
46
47
48
49
50
51
52
53
# File 'lib/semverse/constraint.rb', line 45

def satisfy_best(constraints, versions)
  solution = satisfy_all(constraints, versions)

  if solution.empty?
    raise NoSolutionError
  end

  solution.sort.last
end

.split(constraint) ⇒ Array?

Split a constraint string into an Array of two elements. The first element being the operator and second being the version string.

If the given string does not contain a constraint operator then (=) will be used.

If the given string does not contain a valid version string then nil will be returned.

Examples:

splitting a string with a constraint operator and valid version string

Constraint.split(">= 1.0.0") => [ ">=", "1.0.0" ]

splitting a string without a constraint operator

Constraint.split("0.0.0") => [ "=", "1.0.0" ]

splitting a string without a valid version string

Constraint.split("hello") => nil

Parameters:

  • constraint (#to_s)

Returns:

  • (Array, nil)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/semverse/constraint.rb', line 76

def split(constraint)
  data = REGEX.match(constraint)

  if data.nil?
    raise InvalidConstraintFormat.new(constraint)
  else
    [
      data[:operator] || DEFAULT_OPERATOR,
      data[:major].to_i,
      data[:minor] && data[:minor].to_i,
      data[:patch] && data[:patch].to_i,
      data[:pre_release],
      data[:build],
    ]
  end
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Parameters:

  • other (Object)

Returns:

  • (Boolean)


212
213
214
215
216
# File 'lib/semverse/constraint.rb', line 212

def ==(other)
  other.is_a?(self.class) &&
    self.operator == other.operator &&
    self.version == other.version
end

#inspectString

The detailed string representation of this constraint.

Returns:

  • (String)


222
223
224
# File 'lib/semverse/constraint.rb', line 222

def inspect
  "#<#{self.class.to_s} #{to_s}>"
end

#satisfies?(target) ⇒ Boolean Also known as: include?

Returns true or false if the given version would be satisfied by the version constraint.

Parameters:

Returns:

  • (Boolean)


195
196
197
198
199
200
201
202
203
# File 'lib/semverse/constraint.rb', line 195

def satisfies?(target)
  target = Version.coerce(target)

  if !version.zero? && greedy_match?(target)
    return false
  end

  OPERATORS[operator].call(self, target)
end

#to_sString

The string representation of this constraint.

Returns:

  • (String)


229
230
231
232
233
234
235
236
# File 'lib/semverse/constraint.rb', line 229

def to_s
  out =  "#{operator} #{major}"
  out << ".#{minor}" if minor
  out << ".#{patch}" if patch
  out << "-#{pre_release}" if pre_release
  out << "+#{build}" if build
  out
end