versionaire

An immutable, thread-safe, and strict semantic version type.

https://github.com/bkuhlmann/versionaire

Science Score: 44.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (9.8%) to scientific vocabulary

Keywords

version-control versioning
Last synced: 6 months ago · JSON representation ·

Repository

An immutable, thread-safe, and strict semantic version type.

Basic Info
Statistics
  • Stars: 95
  • Watchers: 4
  • Forks: 1
  • Open Issues: 0
  • Releases: 0
Topics
version-control versioning
Created almost 10 years ago · Last pushed 6 months ago
Metadata Files
Readme Funding License Citation

README.adoc

:toc: macro
:toclevels: 5
:figure-caption!:

:option_parser_link: link:https://alchemists.io/articles/ruby_option_parser[OptionParser]
:semver_link: link:https://semver.org[Semantic Versioning]
:strict_semver_link: link:https://alchemists.io/articles/strict_semantic_versioning[Strict Semantic Versioning]

= Versionaire

Ruby doesn't provide a primitive version type by default so Versionaire fills this gap by providing immutable and thread-safe {strict_semver_link} so you can leverage versions within your applications. This new `Version` type behaves and feels a lot like other primitives (i.e. `String`, `Array`, `Hash`, `Proc`, etc) and can be cast/converted from other primitives.

toc::[]

== Features

* Provides {strict_semver_link} which means `..`.
* Provides immutable, thread-safe version instances.
* Converts (casts) from a `String`, `Array`, `Hash`, `Proc`, or `Version` to a `Version`.
* Disallows `..-` usage even though {semver_link} suggests you _may_ use pre-release information.
* Disallows `..+` usage even though {semver_link} suggests you _may_ use build metadata.

== Requirements

. https://www.ruby-lang.org[Ruby].

== Setup

To install _with_ security, run:

[source,bash]
----
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install versionaire --trust-policy HighSecurity
----

To install _without_ security, run:

[source,bash]
----
gem install versionaire
----

You can also add the gem directly to your project:

[source,bash]
----
bundle add versionaire
----

Once the gem is installed, you only need to require it:

[source,ruby]
----
require "versionaire"
----

== Usage

=== Initialization

A new version can be initialized in a variety of ways:

[source,ruby]
----
Versionaire::Version.new                            # "0.0.0"
Versionaire::Version[major: 1]                      # "1.0.0"
Versionaire::Version[major: 1, minor: 2]            # "1.2.0"
Versionaire::Version[major: 1, minor: 2, patch: 3]  # "1.2.3"
----

=== Equality

==== Value (`+#==+`)

Equality is determined by the state of the object. This means that a version is equal to another version as long as all of the values (i.e. state) are equal to each other. Example:

[source,ruby]
----
version_a = Versionaire::Version[major: 1]
version_b = Versionaire::Version[major: 2]
version_c = Versionaire::Version[major: 1]

version_a == version_a  # true
version_a == version_b  # false
version_a == version_c  # true
----

Knowing this, versions can be compared against one another too:

[source,ruby]
----
version_a > version_b                    # false
version_a < version_b                    # true
version_a.between? version_c, version_b  # true
----

==== Hash (`#eql?`)

Behaves exactly as `#==`.

==== Case (`#===`)

Behaves exactly as `#==`.

==== Identity (`#equal?`)

Works like any other standard Ruby object where an object is equal only to itself.

[source,ruby]
----
version_a = Versionaire::Version[major: 1]
version_b = Versionaire::Version[major: 2]
version_c = Versionaire::Version[major: 1]

version_a.equal? version_a  # true
version_a.equal? version_b  # false
version_a.equal? version_c  # false
----

=== Conversions

==== Function

Use the `Versionaire::Version` function to explicitly cast to a version:

[source,ruby]
----
version = Versionaire::Version[major: 1]

Versionaire::Version "1.0.0"
Versionaire::Version [1, 0, 0]
Versionaire::Version major: 1, minor: 0, patch: 0
Versionaire::Version version
----

Each of these conversions will result in a version object that represents "`1.0.0`".

When attempting to convert an unsupported type, a `Versionaire::Error` exception will be thrown.

==== Refinement

Building upon the above examples, a more elegant solution is to use a link:https://alchemists.io/articles/ruby_refinements[refinement]:

[source,ruby]
----
using Versionaire::Cast

version = Versionaire::Version[major: 1]

Version "1.0.0"
Version [1, 0, 0]
Version major: 1, minor: 0, patch: 0
Version version
----

By adding `using Versionaire::Cast` to your implementation, this allows Versionaire to refine
`Kernel` so you have a top-level `Version` conversion function much like Kernel's native support for
`Integer`, `String`, `Array`, `Hash`, etc. The benefit to this approach is to reduce the amount of
typing so you don't pollute your entire object space, like a monkey patch, while providing an idiomatic approach to casting like any other primitive.

==== Implicit

Implicit conversion to a `String` is supported:

[source,ruby]
----
"1.0.0".match Versionaire::Version[major: 1]  # 
----

==== Explicit

Explicit conversion to a `String`, `Array`, `Hash`, or `Proc` is supported:

[source,ruby]
----
version = Versionaire::Version.new

version.to_s     # "0.0.0"
version.to_a     # [0, 0, 0]
version.to_h     # {major: 0, minor: 0, patch: 0}
version.to_proc  # #
----

To elaborate on procs, this means the following is possible where you might want to collect all minor verions values or make use of version information in other useful ways:

[source,ruby]
----
using Versionaire::Cast

version = Version "1.2.3"

version.to_proc.call :major               # 1
[version, version, version].map(&:minor)  # [2, 2, 2]
----

=== Inspections

You can inspect a version which is the equivalent of an escaped string representation. Example:

[source,ruby]
----
using Versionaire::Cast

Version("1.2.3").inspect  # "\"1.2.3\""
----

=== Comparisons

All versions are comparable which means any of the operators from the `+Comparable+` module will
work. Example:

[source,ruby]
----
version_1 = Versionaire::Version "1.0.0"
version_2 = Versionaire::Version "2.0.0"

version_1 < version_2                    # true
version_1 <= version_2                   # true
version_1 == version_2                   # false (see Equality section above for details)
version_1 > version_2                    # false
version_1 >= version_2                   # false
version_1.between? version_1, version_2  # true
version_1.clamp version_1, version_2     # version_1 (added in Ruby 2.4.0)
----

=== Bumping

Versions can be bumped to next logical version with respect current version. Example:

[source,ruby]
----
version = Versionaire::Version.new  # "0.0.0"
version.bump :patch                 # "0.0.1"
version.bump :minor                 # "0.1.0"
version.bump :major                 # "1.0.0"

Versionaire::Version[major: 1, minor: 2, patch: 3].bump :major  # "2.0.0"
Versionaire::Version[major: 1, minor: 2, patch: 3].bump :minor  # "1.3.0"
Versionaire::Version[major: 1, minor: 2, patch: 3].bump :patch  # "1.2.4"
----

You'll notice, when bumping the major or minor versions, lower precision gets zeroed out in order to provide the next logical version.

=== Math

Versions can be added, subtracted, sequentially increased, or sequentially decreased from each
other.

==== Addition

Versions can be added together to produce a resulting version sum.

[source,ruby]
----
version_1 = Versionaire::Version[major: 1, minor: 2, patch: 3]
version_2 = Versionaire::Version[major: 2, minor: 5, patch: 7]
version_1 + version_2  # "3.7.10"
----

==== Subtraction

Versions can be substracted from each other as long as there isn't a negative result.

[source,ruby]
----
version_1 = Versionaire::Version[major: 1, minor: 2, patch: 3]
version_2 = Versionaire::Version[major: 1, minor: 1, patch: 1]
version_1 - version_2  # "0.1.2"

version_1 = Versionaire::Version[major: 1]
version_2 = Versionaire::Version[major: 5]
version_1 - version_2  # Versionaire::Error
----

==== Up

Versions can be sequentially increased or given a specific version to jump to.

[source,ruby]
----
version = Versionaire::Version[major: 1, minor: 1, patch: 1]
version.up :major     # => "2.1.1"
version.up :major, 3  # => "4.1.1"
version.up :minor     # => "1.2.1"
version.up :minor, 3  # => "1.4.1"
version.up :patch     # => "1.1.2"
version.up :patch, 3  # => "1.1.4"
----

==== Down

Versions can be sequentially decreased or given a specific version to jump to as long as the result
is not negative.

[source,ruby]
----
version = Versionaire::Version[major: 5, minor: 5, patch: 5]
version.down :major     # => "4.5.5"
version.down :major, 3  # => "2.5.5"
version.down :minor     # => "5.4.5"
version.down :minor, 3  # => "5.2.5"
version.down :patch     # => "5.5.4"
version.down :patch, 3  # => "5.5.2"
version.down :major, 6  # => Versionaire::Error
----

=== Extensions

This project supports libraries which might desire native `Version` types. Each extension _must be
explicitly required_ in order to be used since they are _optional_ by default. See below for
details.

==== OptionParser

{option_parser_link} is one of Ruby's link:https://stdgems.org[default gems] which can accept additional types not native to Ruby by default. To extend `OptionParser` with the `Version` type, all you need to do is add these two lines to your implementation:

. `require "versionaire/extensions/option_parser"`: This will load dependencies and register the `Version` type with `OptionParser`.
. `act.on "--tag VERSION", Versionaire::Version`: Specifying `Versionaire::Version` as the second argument will ensure `OptionParser` properly casts command line input as a `Version` type.

Here's an example implementation that demonstrates full usage:

[source,ruby]
----
require "versionaire/extensions/option_parser"

options = {}

parser = OptionParser.new do |act|
  act.on "--tag VERSION", Versionaire::Version, "Casts to version." do |value|
    options[:version] = value
  end
end

parser.parse %w[--tag 1.2.3]
puts options
----

The above will ensure `--tag 1.2.3` is parsed as `{version: "1.2.3"}` within your `options` variable. Should `OptionParser` parse an invalid version, you'll get a `OptionParser::InvalidArgument` instead.

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/versionaire
cd versionaire
bin/setup
----

You can also use the IRB console for direct access to all objects:

[source,bash]
----
bin/console
----

== Tests

To test, run:

[source,bash]
----
bin/rake
----

== link:https://alchemists.io/policies/license[License]

== link:https://alchemists.io/policies/security[Security]

== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://alchemists.io/policies/contributions[Contributions]

== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]

== link:https://alchemists.io/projects/versionaire/versions[Versions]

== link:https://alchemists.io/community[Community]

== Credits

* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].

Owner

  • Name: Brooke Kuhlmann
  • Login: bkuhlmann
  • Kind: user
  • Location: Boulder, CO USA
  • Company: Alchemists

Quality over quantity.

Citation (CITATION.cff)

cff-version: 1.2.0
message: Please use the following metadata when citing this project in your work.
title: Versionaire
abstract: An immutable, thread-safe, and strict semantic version type.
version: 14.3.0
license: Hippocratic-2.1
date-released: 2025-07-15
authors:
  - family-names: Kuhlmann
    given-names: Brooke
    affiliation: Alchemists
    orcid: https://orcid.org/0000-0002-5810-6268
keywords:
 - ruby
 - semantic versioning
repository-code: https://github.com/bkuhlmann/versionaire
repository-artifact: https://rubygems.org/gems/versionaire
url: https://alchemists.io/projects/versionaire

GitHub Events

Total
  • Watch event: 8
  • Delete event: 151
  • Push event: 34
  • Create event: 9
Last Year
  • Watch event: 8
  • Delete event: 151
  • Push event: 34
  • Create event: 9

Committers

Last synced: 9 months ago

All Time
  • Total Commits: 595
  • Total Committers: 1
  • Avg Commits per committer: 595.0
  • Development Distribution Score (DDS): 0.0
Past Year
  • Commits: 53
  • Committers: 1
  • Avg Commits per committer: 53.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Brooke Kuhlmann b****e@a****o 595
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 6 months ago

All Time
  • Total issues: 0
  • Total pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Total issue authors: 0
  • Total pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
Pull Request Authors
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 2
  • Total downloads:
    • rubygems 224,361 total
  • Total dependent packages: 4
    (may contain duplicates)
  • Total dependent repositories: 12
    (may contain duplicates)
  • Total versions: 93
  • Total maintainers: 1
proxy.golang.org: github.com/bkuhlmann/versionaire
  • Versions: 15
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent packages count: 6.5%
Average: 6.7%
Dependent repos count: 6.9%
Last synced: 6 months ago
rubygems.org: versionaire

An immutable, thread-safe, and strict semantic version type.

  • Versions: 78
  • Dependent Packages: 4
  • Dependent Repositories: 12
  • Downloads: 224,361 Total
Rankings
Dependent packages count: 3.3%
Stargazers count: 6.3%
Dependent repos count: 6.7%
Downloads: 6.8%
Average: 9.0%
Forks count: 22.1%
Maintainers (1)
Funding
  • https://github.com/sponsors/bkuhlmann
Last synced: 6 months ago

Dependencies

Gemfile rubygems
  • amazing_print ~> 1.4 development
  • caliber ~> 0.11 development
  • debug ~> 1.6 development
  • git-lint ~> 4.0 development
  • guard-rspec ~> 4.7 development
  • rake ~> 13.0 development
  • reek ~> 6.1 development
  • rspec ~> 3.11 development
  • simplecov ~> 0.21 development
versionaire.gemspec rubygems
  • refinements ~> 9.6