Class: Vers::Constraint

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

Overview

Represents a single version constraint (e.g., ">=1.2.3", "!=2.0.0")

A constraint consists of an operator and a version. This class handles parsing constraint strings and converting them to intervals.

Examples

constraint = Vers::Constraint.new(">=", "1.2.3")
constraint.operator  # => ">="
constraint.version   # => "1.2.3"
constraint.to_interval # => [1.2.3,+∞)

Constant Summary collapse

OPERATORS =

Valid constraint operators as defined in the vers spec

%w[= != < <= > >=].freeze
OPERATOR_REGEX =

Pre-compiled regex patterns for performance

/\A(!=|>=|<=|[<>=])/
@@constraint_cache =

Cache for parsed constraints

{}
@@cache_size_limit =
1000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(operator, version) ⇒ Constraint

Creates a new constraint with the given operator and version

Parameters:

  • operator (String)

    The constraint operator (=, !=, <, <=, >, >=)

  • version (String)

    The version string

Raises:

  • (ArgumentError)

    if operator is invalid



37
38
39
40
41
42
# File 'lib/vers/constraint.rb', line 37

def initialize(operator, version)
  raise ArgumentError, "Invalid operator: #{operator}" unless OPERATORS.include?(operator)
  
  @operator = operator
  @version = version
end

Instance Attribute Details

#operatorObject (readonly)

Returns the value of attribute operator.



28
29
30
# File 'lib/vers/constraint.rb', line 28

def operator
  @operator
end

#versionObject (readonly)

Returns the value of attribute version.



28
29
30
# File 'lib/vers/constraint.rb', line 28

def version
  @version
end

Class Method Details

.parse(constraint_string) ⇒ Constraint

Parses a constraint string into operator and version components

Examples

Vers::Constraint.parse(">=1.2.3")  # => #<Vers::Constraint:0x... @operator=">=", @version="1.2.3">
Vers::Constraint.parse("!=2.0.0")  # => #<Vers::Constraint:0x... @operator="!=", @version="2.0.0">

Parameters:

  • constraint_string (String)

    The constraint string to parse

Returns:

Raises:

  • (ArgumentError)

    if the constraint string is invalid



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/vers/constraint.rb', line 56

def self.parse(constraint_string)
  # Bound input length before cache lookup so oversized strings never
  # become cache keys and never trigger eviction.
  if constraint_string.length > Version::MAX_LENGTH
    raise ArgumentError, "Constraint string too long (#{constraint_string.length} > #{Version::MAX_LENGTH})"
  end

  return @@constraint_cache[constraint_string] if @@constraint_cache.key?(constraint_string)

  if @@constraint_cache.size >= @@cache_size_limit
    keys = @@constraint_cache.keys
    keys.first(keys.size / 2).each { |k| @@constraint_cache.delete(k) }
  end

  constraint = parse_uncached(constraint_string)
  @@constraint_cache[constraint_string] = constraint
  constraint
end

.parse_uncached(constraint_string) ⇒ Object

Internal uncached parsing method



78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/vers/constraint.rb', line 78

def self.parse_uncached(constraint_string)
  # Use regex for faster operator detection
  if match = constraint_string.match(OPERATOR_REGEX)
    operator = match[1]
    version = constraint_string[operator.length..-1].strip
    raise ArgumentError, "Invalid constraint format: #{constraint_string}" if version.empty?
    new(operator, version)
  else
    # No operator found, treat as exact match
    new("=", constraint_string.strip)
  end
end

Instance Method Details

#==(other) ⇒ Object



162
163
164
# File 'lib/vers/constraint.rb', line 162

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

#exclusion?Boolean

Returns true if this is an exclusion constraint (!=)

Returns:

  • (Boolean)


124
125
126
# File 'lib/vers/constraint.rb', line 124

def exclusion?
  operator == "!="
end

#hashObject



166
167
168
# File 'lib/vers/constraint.rb', line 166

def hash
  [operator, version].hash
end

#satisfies?(version_string) ⇒ Boolean

Checks if a version satisfies this constraint

Parameters:

  • version_string (String)

    The version to check

Returns:

  • (Boolean)

    true if the version satisfies the constraint



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/vers/constraint.rb', line 134

def satisfies?(version_string)
  comparison = Version.compare(version_string, version)
  
  case operator
  when "="
    comparison == 0
  when "!="
    comparison != 0
  when ">"
    comparison > 0
  when ">="
    comparison >= 0
  when "<"
    comparison < 0
  when "<="
    comparison <= 0
  end
end

#to_interval(scheme: nil) ⇒ Interval

Converts this constraint to an interval representation

Examples

Vers::Constraint.new(">=", "1.2.3").to_interval  # => [1.2.3,+∞)
Vers::Constraint.new("=", "1.0.0").to_interval   # => [1.0.0,1.0.0]

Returns:

  • (Interval)

    The interval representation of this constraint



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/vers/constraint.rb', line 101

def to_interval(scheme: nil)
  case operator
  when "="
    Interval.exact(version, scheme: scheme)
  when "!="
    # != constraints need special handling in ranges - they create exclusions
    nil
  when ">"
    Interval.greater_than(version, inclusive: false, scheme: scheme)
  when ">="
    Interval.greater_than(version, inclusive: true, scheme: scheme)
  when "<"
    Interval.less_than(version, inclusive: false, scheme: scheme)
  when "<="
    Interval.less_than(version, inclusive: true, scheme: scheme)
  end
end

#to_sString

String representation of this constraint

Returns:

  • (String)

    The constraint as a string



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

def to_s
  "#{operator}#{version}"
end