Class: Vers::Version
- Inherits:
-
Object
- Object
- Vers::Version
- Defined in:
- lib/vers/version.rb
Overview
Handles version comparison and normalization across different package ecosystems.
This class provides version comparison functionality that can handle different versioning schemes used by various package managers (npm, gem, pypi, etc.).
Examples
Vers::Version.compare("1.2.3", "1.2.4") # => -1
Vers::Version.compare("2.0.0", "1.9.9") # => 1
Vers::Version.compare("1.0.0", "1.0.0") # => 0
Constant Summary collapse
- SEMANTIC_VERSION_REGEX =
Regex for parsing semantic version components including build metadata
/\A(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([^+]+))?(?:\+(.+))?\z/- MAX_LENGTH =
Maximum accepted length for a version string. Real-world version strings rarely exceed 100 characters; 256 leaves headroom for unusual prerelease tags while bounding regex/split work and cache key size.
256- @@version_cache =
Cache for parsed versions to avoid repeated parsing
{}
- @@cache_size_limit =
2000
Instance Attribute Summary collapse
-
#build ⇒ Object
readonly
Returns the value of attribute build.
-
#major ⇒ Object
readonly
Returns the value of attribute major.
-
#minor ⇒ Object
readonly
Returns the value of attribute minor.
-
#patch ⇒ Object
readonly
Returns the value of attribute patch.
-
#prerelease ⇒ Object
readonly
Returns the value of attribute prerelease.
Class Method Summary collapse
-
.cached_new(version_string) ⇒ Version
Creates a new Version object with caching.
- .clean(version_string, scheme = nil) ⇒ Object
-
.compare(a, b) ⇒ Integer
Compares two version strings.
- .compare_for_range(a, b, scheme) ⇒ Object
-
.compare_with_scheme(a, b, scheme) ⇒ Integer
Compares two version strings using scheme-specific rules.
-
.normalize(version_string, scheme = nil) ⇒ String
Normalizes a version string to a consistent format.
- .prerelease?(version_string, scheme = nil) ⇒ Boolean
- .stable?(version_string, scheme = nil) ⇒ Boolean
-
.valid?(version_string, scheme = nil) ⇒ Boolean
Checks if a version string is valid.
Instance Method Summary collapse
- #<(other) ⇒ Object
- #<=(other) ⇒ Object
-
#<=>(other) ⇒ Integer
Version comparison operator.
- #==(other) ⇒ Object
- #>(other) ⇒ Object
- #>=(other) ⇒ Object
-
#base ⇒ Version
Creates a new Version with the same major.minor but patch set to 0.
- #hash ⇒ Object
-
#increment(component) ⇒ Version
Increments the specified component of the version.
-
#increment_major ⇒ Version
Increments the major version component.
-
#increment_minor ⇒ Version
Increments the minor version component.
-
#increment_patch ⇒ Version
Increments the patch version component.
-
#initialize(version_string) ⇒ Version
constructor
Creates a new Version object.
-
#prerelease? ⇒ Boolean
Checks if this is a prerelease version.
-
#satisfies?(constraint) ⇒ Boolean
Checks if this version satisfies a constraint using pessimistic operator logic.
-
#stable? ⇒ Boolean
Checks if this is a stable release (no prerelease components).
-
#to_h ⇒ Hash
Gets the semantic version components as a hash.
-
#to_s ⇒ String
String representation of the version.
Constructor Details
#initialize(version_string) ⇒ Version
Creates a new Version object
51 52 53 54 55 56 57 |
# File 'lib/vers/version.rb', line 51 def initialize(version_string) @original = version_string.to_s if @original.length > MAX_LENGTH raise ArgumentError, "Version string too long (#{@original.length} > #{MAX_LENGTH})" end parse_version end |
Instance Attribute Details
#build ⇒ Object (readonly)
Returns the value of attribute build.
43 44 45 |
# File 'lib/vers/version.rb', line 43 def build @build end |
#major ⇒ Object (readonly)
Returns the value of attribute major.
43 44 45 |
# File 'lib/vers/version.rb', line 43 def major @major end |
#minor ⇒ Object (readonly)
Returns the value of attribute minor.
43 44 45 |
# File 'lib/vers/version.rb', line 43 def minor @minor end |
#patch ⇒ Object (readonly)
Returns the value of attribute patch.
43 44 45 |
# File 'lib/vers/version.rb', line 43 def patch @patch end |
#prerelease ⇒ Object (readonly)
Returns the value of attribute prerelease.
43 44 45 |
# File 'lib/vers/version.rb', line 43 def prerelease @prerelease end |
Class Method Details
.cached_new(version_string) ⇒ Version
Creates a new Version object with caching
65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/vers/version.rb', line 65 def self.cached_new(version_string) # Skip caching for oversized keys to bound cache memory by entry # count, not by attacker-controlled key length. return new(version_string) if version_string.to_s.length > MAX_LENGTH if @@version_cache.size >= @@cache_size_limit # Keep the most recent half instead of clearing everything keys = @@version_cache.keys keys.first(keys.size / 2).each { |k| @@version_cache.delete(k) } end @@version_cache[version_string] ||= new(version_string) end |
.clean(version_string, scheme = nil) ⇒ Object
177 178 179 180 181 182 |
# File 'lib/vers/version.rb', line 177 def self.clean(version_string, scheme = nil) return nil unless valid?(version_string, scheme) return normalize(version_string, scheme) if Scheme.handler(scheme) version_string.to_s.sub(/\Av/, '') end |
.compare(a, b) ⇒ Integer
Compares two version strings
86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/vers/version.rb', line 86 def self.compare(a, b) return 0 if a == b return -1 if a.nil? return 1 if b.nil? # Use cached versions for better performance version_a = cached_new(a) version_b = cached_new(b) version_a <=> version_b end |
.compare_for_range(a, b, scheme) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/vers/version.rb', line 115 def self.compare_for_range(a, b, scheme) return 0 if a == b return -1 if a.nil? return 1 if b.nil? case Scheme.canonical(scheme) when "cargo" SemverVersion.compare(a, b) when "pypi" PyPIVersion.compare_public(a, b) else compare_with_scheme(a, b, scheme) end end |
.compare_with_scheme(a, b, scheme) ⇒ Integer
Compares two version strings using scheme-specific rules
106 107 108 109 110 111 112 113 |
# File 'lib/vers/version.rb', line 106 def self.compare_with_scheme(a, b, scheme) return 0 if a == b return -1 if a.nil? return 1 if b.nil? handler = Scheme.handler(scheme) handler ? handler.compare(a, b) : compare(a, b) end |
.normalize(version_string, scheme = nil) ⇒ String
Normalizes a version string to a consistent format
136 137 138 139 140 141 142 143 |
# File 'lib/vers/version.rb', line 136 def self.normalize(version_string, scheme = nil) handler = Scheme.handler(scheme) return cached_new(version_string).to_s unless handler raise ArgumentError, "Invalid #{Scheme.canonical(scheme)} version: #{version_string}" unless handler.valid?(version_string) handler.respond_to?(:normalize) ? handler.normalize(version_string) : version_string.to_s.strip end |
.prerelease?(version_string, scheme = nil) ⇒ Boolean
168 169 170 171 172 173 174 175 |
# File 'lib/vers/version.rb', line 168 def self.prerelease?(version_string, scheme = nil) handler = Scheme.handler(scheme) return handler.valid?(version_string) && handler.prerelease?(version_string) if handler cached_new(version_string).prerelease? rescue ArgumentError false end |
.stable?(version_string, scheme = nil) ⇒ Boolean
159 160 161 162 163 164 165 166 |
# File 'lib/vers/version.rb', line 159 def self.stable?(version_string, scheme = nil) handler = Scheme.handler(scheme) return handler.valid?(version_string) && !handler.prerelease?(version_string) if handler cached_new(version_string).stable? rescue ArgumentError false end |
.valid?(version_string, scheme = nil) ⇒ Boolean
Checks if a version string is valid
152 153 154 155 156 157 |
# File 'lib/vers/version.rb', line 152 def self.valid?(version_string, scheme = nil) handler = Scheme.handler(scheme) return handler.valid?(version_string) if handler version_string.to_s.match?(/\Av?\d+\.\d+\.\d+/) end |
Instance Method Details
#<(other) ⇒ Object
230 231 232 |
# File 'lib/vers/version.rb', line 230 def <(other) (self <=> other) < 0 end |
#<=(other) ⇒ Object
234 235 236 |
# File 'lib/vers/version.rb', line 234 def <=(other) (self <=> other) <= 0 end |
#<=>(other) ⇒ Integer
Version comparison operator
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/vers/version.rb', line 190 def <=>(other) return 0 if @original == other.to_s # Compare major.minor.patch numerically major_cmp = (major || 0) <=> (other.major || 0) return major_cmp unless major_cmp == 0 minor_cmp = (minor || 0) <=> (other.minor || 0) return minor_cmp unless minor_cmp == 0 patch_cmp = (patch || 0) <=> (other.patch || 0) return patch_cmp unless patch_cmp == 0 # Handle prerelease comparison return 1 if prerelease.nil? && !other.prerelease.nil? return -1 if !prerelease.nil? && other.prerelease.nil? return 0 if prerelease.nil? && other.prerelease.nil? compare_prerelease(prerelease, other.prerelease) end |
#==(other) ⇒ Object
226 227 228 |
# File 'lib/vers/version.rb', line 226 def ==(other) other.is_a?(Version) && (self <=> other) == 0 end |
#>(other) ⇒ Object
238 239 240 |
# File 'lib/vers/version.rb', line 238 def >(other) (self <=> other) > 0 end |
#>=(other) ⇒ Object
242 243 244 |
# File 'lib/vers/version.rb', line 242 def >=(other) (self <=> other) >= 0 end |
#base ⇒ Version
Creates a new Version with the same major.minor but patch set to 0
383 384 385 |
# File 'lib/vers/version.rb', line 383 def base self.class.new("#{major}.#{minor || 0}.0") end |
#hash ⇒ Object
246 247 248 |
# File 'lib/vers/version.rb', line 246 def hash [@original].hash end |
#increment(component) ⇒ Version
263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/vers/version.rb', line 263 def increment(component) case component when :major self.class.new("#{major + 1}.0.0") when :minor self.class.new("#{major}.#{(minor || 0) + 1}.0") when :patch self.class.new("#{major}.#{minor || 0}.#{(patch || 0) + 1}") else raise ArgumentError, "Invalid component: #{component}. Must be :major, :minor, or :patch" end end |
#increment_major ⇒ Version
Increments the major version component
281 282 283 |
# File 'lib/vers/version.rb', line 281 def increment_major increment(:major) end |
#increment_minor ⇒ Version
Increments the minor version component
290 291 292 |
# File 'lib/vers/version.rb', line 290 def increment_minor increment(:minor) end |
#increment_patch ⇒ Version
Increments the patch version component
299 300 301 |
# File 'lib/vers/version.rb', line 299 def increment_patch increment(:patch) end |
#prerelease? ⇒ Boolean
Checks if this is a prerelease version
359 360 361 |
# File 'lib/vers/version.rb', line 359 def prerelease? !prerelease.nil? end |
#satisfies?(constraint) ⇒ Boolean
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/vers/version.rb', line 316 def satisfies?(constraint) if constraint.start_with?("~>") # Pessimistic constraint base_version = constraint.sub(/^~>\s*/, "").strip base = self.class.new(base_version) # Must be >= base version return false if self < base # Must be < next significant version if base.patch && base.patch > 0 # ~> 1.2.3 means >= 1.2.3, < 1.3.0 upper_bound = self.class.new("#{base.major}.#{(base.minor || 0) + 1}.0") elsif base.minor # ~> 1.2 means >= 1.2.0, < 1.3.0 upper_bound = self.class.new("#{base.major}.#{(base.minor || 0) + 1}.0") else # ~> 1 means >= 1.0.0, < 2.0.0 upper_bound = self.class.new("#{base.major + 1}.0.0") end self < upper_bound else # For other constraints, delegate to constraint parsing # This would require the Constraint class, so for now return true true end end |
#stable? ⇒ Boolean
Checks if this is a stable release (no prerelease components)
350 351 352 |
# File 'lib/vers/version.rb', line 350 def stable? prerelease.nil? end |
#to_h ⇒ Hash
Gets the semantic version components as a hash
368 369 370 371 372 373 374 375 376 |
# File 'lib/vers/version.rb', line 368 def to_h { major: major, minor: minor, patch: patch, prerelease: prerelease, build: build } end |
#to_s ⇒ String
String representation of the version
216 217 218 219 220 221 222 223 224 |
# File 'lib/vers/version.rb', line 216 def to_s @to_s ||= begin version = "#{major || 0}" version += ".#{minor || 0}" version += ".#{patch || 0}" version += "-#{prerelease}" if prerelease version.freeze end end |