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) ⇒ Object
-
.compare(a, b) ⇒ Integer
Compares two version strings.
-
.compare_with_scheme(a, b, scheme) ⇒ Integer
Compares two version strings using scheme-specific rules.
-
.normalize(version_string) ⇒ String
Normalizes a version string to a consistent format.
-
.valid?(version_string) ⇒ 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
38 39 40 41 42 43 44 |
# File 'lib/vers/version.rb', line 38 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.
30 31 32 |
# File 'lib/vers/version.rb', line 30 def build @build end |
#major ⇒ Object (readonly)
Returns the value of attribute major.
30 31 32 |
# File 'lib/vers/version.rb', line 30 def major @major end |
#minor ⇒ Object (readonly)
Returns the value of attribute minor.
30 31 32 |
# File 'lib/vers/version.rb', line 30 def minor @minor end |
#patch ⇒ Object (readonly)
Returns the value of attribute patch.
30 31 32 |
# File 'lib/vers/version.rb', line 30 def patch @patch end |
#prerelease ⇒ Object (readonly)
Returns the value of attribute prerelease.
30 31 32 |
# File 'lib/vers/version.rb', line 30 def prerelease @prerelease end |
Class Method Details
.cached_new(version_string) ⇒ Version
Creates a new Version object with caching
52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/vers/version.rb', line 52 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) ⇒ Object
124 125 126 127 |
# File 'lib/vers/version.rb', line 124 def self.clean(version_string) return nil unless valid?(version_string) version_string.to_s.sub(/\Av/, '') end |
.compare(a, b) ⇒ Integer
Compares two version strings
73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/vers/version.rb', line 73 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_with_scheme(a, b, scheme) ⇒ Integer
Compares two version strings using scheme-specific rules
93 94 95 96 97 98 99 100 101 102 |
# File 'lib/vers/version.rb', line 93 def self.compare_with_scheme(a, b, scheme) case scheme when "maven" MavenVersion.compare(a, b) when "nuget" NuGetVersion.compare(a, b) else compare(a, b) end end |
.normalize(version_string) ⇒ String
Normalizes a version string to a consistent format
110 111 112 |
# File 'lib/vers/version.rb', line 110 def self.normalize(version_string) cached_new(version_string).to_s end |
.valid?(version_string) ⇒ Boolean
Checks if a version string is valid
120 121 122 |
# File 'lib/vers/version.rb', line 120 def self.valid?(version_string) version_string.to_s.match?(/\Av?\d+\.\d+\.\d+/) end |
Instance Method Details
#<(other) ⇒ Object
175 176 177 |
# File 'lib/vers/version.rb', line 175 def <(other) (self <=> other) < 0 end |
#<=(other) ⇒ Object
179 180 181 |
# File 'lib/vers/version.rb', line 179 def <=(other) (self <=> other) <= 0 end |
#<=>(other) ⇒ Integer
Version comparison operator
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/vers/version.rb', line 135 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
171 172 173 |
# File 'lib/vers/version.rb', line 171 def ==(other) other.is_a?(Version) && (self <=> other) == 0 end |
#>(other) ⇒ Object
183 184 185 |
# File 'lib/vers/version.rb', line 183 def >(other) (self <=> other) > 0 end |
#>=(other) ⇒ Object
187 188 189 |
# File 'lib/vers/version.rb', line 187 def >=(other) (self <=> other) >= 0 end |
#base ⇒ Version
Creates a new Version with the same major.minor but patch set to 0
328 329 330 |
# File 'lib/vers/version.rb', line 328 def base self.class.new("#{major}.#{minor || 0}.0") end |
#hash ⇒ Object
191 192 193 |
# File 'lib/vers/version.rb', line 191 def hash [@original].hash end |
#increment(component) ⇒ Version
208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/vers/version.rb', line 208 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
226 227 228 |
# File 'lib/vers/version.rb', line 226 def increment_major increment(:major) end |
#increment_minor ⇒ Version
Increments the minor version component
235 236 237 |
# File 'lib/vers/version.rb', line 235 def increment_minor increment(:minor) end |
#increment_patch ⇒ Version
Increments the patch version component
244 245 246 |
# File 'lib/vers/version.rb', line 244 def increment_patch increment(:patch) end |
#prerelease? ⇒ Boolean
Checks if this is a prerelease version
304 305 306 |
# File 'lib/vers/version.rb', line 304 def prerelease? !prerelease.nil? end |
#satisfies?(constraint) ⇒ Boolean
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/vers/version.rb', line 261 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)
295 296 297 |
# File 'lib/vers/version.rb', line 295 def stable? prerelease.nil? end |
#to_h ⇒ Hash
Gets the semantic version components as a hash
313 314 315 316 317 318 319 320 321 |
# File 'lib/vers/version.rb', line 313 def to_h { major: major, minor: minor, patch: patch, prerelease: prerelease, build: build } end |
#to_s ⇒ String
String representation of the version
161 162 163 164 165 166 167 168 169 |
# File 'lib/vers/version.rb', line 161 def to_s @to_s ||= begin version = "#{major || 0}" version += ".#{minor || 0}" version += ".#{patch || 0}" version += "-#{prerelease}" if prerelease version.freeze end end |