philiprehberger-version_compare 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 45592ec6d8735752e17a9bc545f4fc3d683ee7946bdb3a6ef81f01cbf5e58c12
4
+ data.tar.gz: 7ac00c790d7da685f6d38e1682738b3df914b2afb7629e9da4950e59523bfa3b
5
+ SHA512:
6
+ metadata.gz: 968790a6f434c474581d2b2edb07be7136750cdcd94440e208395016ff19b8cf15d1806fe5db93531920812c7b9162b02a44ad2599de8c59e783d6a695bfa1c1
7
+ data.tar.gz: 1b585e386e5ce18ac64644183004de8d62be3ac362456e456bf140bd8ab9415f0a467fad92369b41aec42d0de2d863b59ae4f9a4254098323851298b369f4d19
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this gem will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-22
11
+
12
+ ### Added
13
+ - Initial release
14
+ - SemVer-style version string parsing with major, minor, patch, and pre-release
15
+ - Comparable version objects with full comparison support
16
+ - Constraint matching for >=, <=, >, <, =, !=, and ~> operators
17
+ - Sort and latest helpers for version string arrays
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philiprehberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # philiprehberger-version_compare
2
+
3
+ [![Tests](https://github.com/philiprehberger/rb-version-compare/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-version-compare/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/philiprehberger-version_compare.svg)](https://rubygems.org/gems/philiprehberger-version_compare)
5
+ [![License](https://img.shields.io/github/license/philiprehberger/rb-version-compare)](LICENSE)
6
+
7
+ Version string parsing with comparison, sorting, and constraint matching
8
+
9
+ ## Requirements
10
+
11
+ - Ruby >= 3.1
12
+
13
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'philiprehberger-version_compare'
19
+ ```
20
+
21
+ Or install directly:
22
+
23
+ ```bash
24
+ gem install philiprehberger-version_compare
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'philiprehberger/version_compare'
31
+
32
+ v = Philiprehberger::VersionCompare.parse('1.2.3')
33
+ v.major # => 1
34
+ v.minor # => 2
35
+ v.patch # => 3
36
+ v.pre_release # => nil
37
+ ```
38
+
39
+ ### Comparison
40
+
41
+ ```ruby
42
+ v1 = Philiprehberger::VersionCompare.parse('1.0.0')
43
+ v2 = Philiprehberger::VersionCompare.parse('2.0.0')
44
+
45
+ v1 < v2 # => true
46
+ v1 == v2 # => false
47
+ ```
48
+
49
+ ### Pre-release Versions
50
+
51
+ ```ruby
52
+ v = Philiprehberger::VersionCompare.parse('1.0.0-beta.1')
53
+ v.pre_release # => "beta.1"
54
+
55
+ # Release versions are greater than pre-release
56
+ Philiprehberger::VersionCompare.parse('1.0.0') > Philiprehberger::VersionCompare.parse('1.0.0-alpha')
57
+ # => true
58
+ ```
59
+
60
+ ### Constraint Matching
61
+
62
+ ```ruby
63
+ v = Philiprehberger::VersionCompare.parse('1.5.3')
64
+
65
+ v.satisfies?('>= 1.0.0') # => true
66
+ v.satisfies?('< 2.0.0') # => true
67
+ v.satisfies?('~> 1.5') # => true
68
+ v.satisfies?('!= 1.0.0') # => true
69
+ ```
70
+
71
+ ### Sorting and Latest
72
+
73
+ ```ruby
74
+ versions = ['2.0.0', '1.0.0', '1.5.0', '0.1.0']
75
+
76
+ Philiprehberger::VersionCompare.sort(versions)
77
+ # => ["0.1.0", "1.0.0", "1.5.0", "2.0.0"]
78
+
79
+ Philiprehberger::VersionCompare.latest(versions)
80
+ # => "2.0.0"
81
+ ```
82
+
83
+ ## API
84
+
85
+ ### `Philiprehberger::VersionCompare`
86
+
87
+ | Method | Description |
88
+ |--------|-------------|
89
+ | `.parse(str)` | Parse a version string into a `SemanticVersion` |
90
+ | `.sort(versions)` | Sort an array of version strings in ascending order |
91
+ | `.latest(versions)` | Return the highest version string from an array |
92
+
93
+ ### `SemanticVersion`
94
+
95
+ | Method | Description |
96
+ |--------|-------------|
97
+ | `#major` | Major version number |
98
+ | `#minor` | Minor version number |
99
+ | `#patch` | Patch version number |
100
+ | `#pre_release` | Pre-release identifier or nil |
101
+ | `#satisfies?(constraint)` | Check if version satisfies a constraint (`>=`, `<`, `~>`, `!=`) |
102
+ | `#<=>(other)` | Compare two versions (includes `Comparable`) |
103
+ | `#to_s` | String representation of the version |
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ bundle install
109
+ bundle exec rspec
110
+ bundle exec rubocop
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module VersionCompare
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'version_compare/version'
4
+
5
+ module Philiprehberger
6
+ module VersionCompare
7
+ class Error < StandardError; end
8
+
9
+ # A parsed semantic version with comparison support
10
+ class SemanticVersion
11
+ include Comparable
12
+
13
+ attr_reader :major, :minor, :patch, :pre_release
14
+
15
+ # @param major [Integer] major version number
16
+ # @param minor [Integer] minor version number
17
+ # @param patch [Integer] patch version number
18
+ # @param pre_release [String, nil] pre-release identifier
19
+ def initialize(major, minor, patch, pre_release: nil)
20
+ @major = major
21
+ @minor = minor
22
+ @patch = patch
23
+ @pre_release = pre_release
24
+ end
25
+
26
+ # Compare two versions following SemVer precedence
27
+ #
28
+ # @param other [SemanticVersion] the other version
29
+ # @return [Integer] -1, 0, or 1
30
+ def <=>(other)
31
+ return nil unless other.is_a?(SemanticVersion)
32
+
33
+ result = [major, minor, patch] <=> [other.major, other.minor, other.patch]
34
+ return result unless result.zero?
35
+
36
+ compare_pre_release(other)
37
+ end
38
+
39
+ # Check if this version satisfies a constraint string
40
+ #
41
+ # @param constraint [String] constraint like ">= 1.0.0", "~> 2.1", "!= 1.2.3", "< 3.0.0"
42
+ # @return [Boolean] true if the version satisfies the constraint
43
+ # @raise [Error] if the constraint format is invalid
44
+ def satisfies?(constraint)
45
+ operator, version_str = parse_constraint(constraint)
46
+ other = VersionCompare.parse(version_str)
47
+
48
+ case operator
49
+ when '>='
50
+ self >= other
51
+ when '<='
52
+ self <= other
53
+ when '>'
54
+ self > other
55
+ when '<'
56
+ self < other
57
+ when '='
58
+ self == other
59
+ when '!='
60
+ self != other
61
+ when '~>'
62
+ pessimistic_match?(other)
63
+ else
64
+ raise Error, "unknown operator: #{operator}"
65
+ end
66
+ end
67
+
68
+ # @return [String] version string representation
69
+ def to_s
70
+ base = "#{major}.#{minor}.#{patch}"
71
+ pre_release ? "#{base}-#{pre_release}" : base
72
+ end
73
+
74
+ def inspect
75
+ "#<Version #{self}>"
76
+ end
77
+
78
+ private
79
+
80
+ def compare_pre_release(other)
81
+ return 0 if pre_release.nil? && other.pre_release.nil?
82
+ return 1 if pre_release.nil?
83
+ return -1 if other.pre_release.nil?
84
+
85
+ pre_release <=> other.pre_release
86
+ end
87
+
88
+ def parse_constraint(constraint)
89
+ match = constraint.strip.match(/\A(>=|<=|~>|!=|>|<|=)?\s*(.+)\z/)
90
+ raise Error, "invalid constraint: #{constraint}" unless match
91
+
92
+ operator = match[1] || '='
93
+ [operator, match[2].strip]
94
+ end
95
+
96
+ def pessimistic_match?(other)
97
+ return false if self < other
98
+
99
+ if other.patch.zero? && other.minor.zero?
100
+ major == other.major
101
+ elsif other.patch.zero?
102
+ major == other.major && minor == other.minor
103
+ else
104
+ major == other.major && minor == other.minor
105
+ end
106
+ end
107
+ end
108
+
109
+ # Parse a version string into a SemanticVersion
110
+ #
111
+ # @param str [String] version string like "1.2.3" or "1.2.3-beta.1"
112
+ # @return [SemanticVersion] parsed version
113
+ # @raise [Error] if the string is not a valid version
114
+ def self.parse(str)
115
+ str = str.to_s.strip
116
+ str = str.sub(/\Av/, '')
117
+
118
+ match = str.match(/\A(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-(.+))?\z/)
119
+ raise Error, "invalid version: #{str}" unless match
120
+
121
+ SemanticVersion.new(
122
+ match[1].to_i,
123
+ (match[2] || '0').to_i,
124
+ (match[3] || '0').to_i,
125
+ pre_release: match[4]
126
+ )
127
+ end
128
+
129
+ # Sort an array of version strings
130
+ #
131
+ # @param versions [Array<String>] version strings to sort
132
+ # @return [Array<String>] sorted version strings (ascending)
133
+ def self.sort(versions)
134
+ versions.sort_by { |v| parse(v) }
135
+ end
136
+
137
+ # Return the latest version from an array of version strings
138
+ #
139
+ # @param versions [Array<String>] version strings
140
+ # @return [String] the latest version string
141
+ # @raise [Error] if the array is empty
142
+ def self.latest(versions)
143
+ raise Error, 'no versions provided' if versions.empty?
144
+
145
+ versions.max_by { |v| parse(v) }
146
+ end
147
+ end
148
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: philiprehberger-version_compare
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Philip Rehberger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Parse semantic version strings into comparable objects with support for
14
+ sorting, finding the latest version, and checking constraint satisfaction.
15
+ email:
16
+ - me@philiprehberger.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE
23
+ - README.md
24
+ - lib/philiprehberger/version_compare.rb
25
+ - lib/philiprehberger/version_compare/version.rb
26
+ homepage: https://github.com/philiprehberger/rb-version-compare
27
+ licenses:
28
+ - MIT
29
+ metadata:
30
+ homepage_uri: https://github.com/philiprehberger/rb-version-compare
31
+ source_code_uri: https://github.com/philiprehberger/rb-version-compare
32
+ changelog_uri: https://github.com/philiprehberger/rb-version-compare/blob/main/CHANGELOG.md
33
+ bug_tracker_uri: https://github.com/philiprehberger/rb-version-compare/issues
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 3.1.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.22
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Version string parsing with comparison, sorting, and constraint matching
54
+ test_files: []