Purpose
Versionian is a Ruby library for declaring, parsing, comparing, and rendering version schemes. It provides model-driven primitives for defining how versions work, supporting semantic versioning, calendar versioning, and unlimited custom schemes through declarative YAML configuration.
Why Versionian?
Ruby’s built-in Gem::Version handles semantic versioning well, but real-world projects use diverse versioning schemes that Gem::Version cannot parse or compare correctly:
-
Calendar versioning (
2024.01.17,2024.03) -Gem::Versionparses these but cannot enforce date validation -
Single-number versioning (
5,5+hotfix,5-beta) -Gem::Versiontreats postfixes incorrectly -
Human-readable versions (
Alpha.1.5,Beta.2.0) -Gem::Versioncannot compare these -
Hash-based versions (
2024.01.abc123) -Gem::Versionhas no concept of commit hashes -
Custom schemes - Every project has unique versioning needs
How Versionian is different
| Feature | Gem::Version | Versionian | Supported schemes | Semantic versioning only | 4 built-in schemes, unlimited custom | Extensibility | Not extensible | Define custom schemes via declarative YAML or Ruby | Component types |
|---|---|---|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
Versionian fills the gap between `Gem::Version’s semantic versioning focus and the diverse versioning needs of real-world projects.
Features
-
Built-in schemes - SemVer, CalVer, SoloVer, WendtVer
-
Declarative schemes - Custom via YAML
-
Component types - Extensible type system
-
Range matching - Version range comparisons
-
Custom Ruby schemes - Ruby API for custom schemes
-
API reference - Complete API documentation
Quick start
Get started with Versionian in three steps:
require 'versionian'# Use built-in semantic versioning
scheme = Versionian.get_scheme(:semantic)
version = scheme.parse("1.12.0")
puts version.to_s # => "1.12.0"
# Compare versions
result = scheme.compare("1.12.0", "2.0.0")
puts result # => -1 (less than)# schemes/my_scheme.yaml
name: my_scheme
type: declarative
description: Custom version scheme
components:
- name: major
type: integer
separator: "."
- name: minor
type: integer
separator: "."
- name: patch
type: integer
optional: true# Load and use custom scheme
scheme = Versionian::SchemeLoader.from_yaml_file('schemes/my_scheme.yaml')
Versionian.register_scheme(:my_scheme, scheme)
version = scheme.parse("1.2.3")
puts version.major # => 1Discovering available schemes
Versionian provides several ways to discover available schemes:
# List all registered scheme names
scheme_names = Versionian.scheme_registry.registered
puts scheme_names.inspect # => [:semantic, :calver, :solover, :wendtver]
# Check if a scheme exists
Versionian.scheme_registry.registered.include?(:semantic) # => true# Auto-detect which scheme matches a version string
detected = Versionian.detect_scheme("1.2.3")
puts detected.name # => :semantic
detected = Versionian.detect_scheme("2024.01.17")
puts detected.name # => :calver
detected = Versionian.detect_scheme("5+hotfix")
puts detected.name # => :solover
# Returns nil for unrecognised version strings
detected = Versionian.detect_scheme("not-a-version")
puts detected # => nil# Get a scheme by name
scheme = Versionian.get_scheme(:semantic)
# Check scheme capabilities
puts scheme.supports?("1.2.3") # => true
puts scheme.supports?("invalid") # => false
# Parse and compare
version = scheme.parse("1.12.0")
puts version.to_s # => "1.12.0"
# Raises error for unknown schemes
begin
Versionian.get_scheme(:unknown)
rescue Versionian::Errors::InvalidSchemeError => e
puts e.message # => "Unknown scheme: unknown"
endArchitecture
Versionian follows a model-driven architecture where version schemes define their own parsing, comparison, and rendering behavior.
Lexicographic array comparison
Versionian uses lexicographic array comparison instead of weighted integer sums. Each version stores a comparable_array where each element represents a component in comparison order:
# Version stores array for comparison
version.comparable_array # => [2024, 1, 17, :rc, 1]
# Comparison is element-by-element
[2024, 1, 17] <=> [2024, 1, 18] # => -1
[2024, 1, 17] <=> [2024, 2, 1] # => -1
[2024, 1, 17] <=> [2024, 1, 17] # => 0This approach avoids integer overflow, provides better performance, and allows each component type to define its own comparison semantics.
Parse versus build
Versionian provides two ways to create version identifiers:
-
Parse: Extract components from a version string
-
Build: Create a version from component values
# Parse: from string
version = scheme.parse("1.12.0")
# Build: from component values
version = scheme.build(major: 1, minor: 12, patch: 0)Both methods return the same VersionIdentifier object with a comparable_array for comparison.
Object model
Versionian::VersionScheme (abstract)
├── parse(version_string) -> VersionIdentifier
├── build(component_values) -> VersionIdentifier
├── compare_arrays(a, b) -> Integer
├── render(version) -> String
└── matches_range?(version_string, range) -> Boolean
Implemented Schemes:
├── Semantic (SemVer)
├── CalVer (Calendar versioning)
├── SoloVer (Single number with postfix)
├── WendtVer (Auto-incrementing)
└── Declarative (Custom segment-based)
VersionIdentifier objects:
├── raw_string (original input)
├── scheme (reference to scheme)
├── components (array of VersionComponent)
└── comparable_array (for lexicographic comparison)
Component type registry:
├── Integer (numeric sequences)
├── Float (IEEE754 floats)
├── String (arbitrary text)
├── Enum (ordered stages)
├── DatePart (calendar components)
├── Prerelease (SemVer prereleases)
├── Postfix (SoloVer suffixes)
├── Hash (git commit hashes)
└── Custom types (user-defined)Installation
Add this line to your application’s Gemfile:
gem 'versionian'And then execute:
bundle installOr install it yourself as:
gem install versionian