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

= Versionaire

[link=http://badge.fury.io/rb/versionaire]
image::https://badge.fury.io/rb/versionaire.svg[Gem Version]
[link=https://www.alchemists.io/projects/code_quality]
image::https://img.shields.io/badge/code_style-alchemists-brightgreen.svg[Alchemists Style Guide]
[link=https://circleci.com/gh/bkuhlmann/versionaire]
image::https://circleci.com/gh/bkuhlmann/versionaire.svg?style=svg[Circle CI Status]

Provides an immutable, thread-safe, and semantic version type when managing versions within your
applications.

toc::[]

== Features

* Provides https://semver.org[Semantic Versioning].
* Provides immutable, thread-safe version instances.
* Converts (casts) from a `String`, `Array`, `Hash`, or `Version` to a `Version`.

== Screencasts

[link=https://www.alchemists.io/screencasts/versionaire]
image::https://www.alchemists.io/images/screencasts/versionaire/cover.svg[Screencast,600,240,role=focal_point]

== Requirements

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

== Setup

To install, run:

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

Add the following to your Gemfile:

[source,ruby]
----
gem "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 deterimined 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
----

Knowning 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::Errors::Conversion+` exception will be thrown.

==== Refinement

Building upon the examples shown above, there is an even more elegant solution where you can use
this gem's built-in link:https://www.alchemists.io/articles/ruby_refinements[refinement] support:

[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 it reduces the amount of
typing, doesn't polute your entire object space like a monkey patch would, and provides a 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]  # <MatchData "1.0.0">
----

==== Explicit

Explicit conversion to a `String`, `Array`, or `Hash` 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}
----

=== 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)
----

=== 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::Errors::NegativeNumber
----

==== 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::Errors::NegativeNumber
----

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/versionaire.git
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]
----
bundle exec rake
----

== Versioning

Read link:https://semver.org[Semantic Versioning] for details. Briefly, it means:

* Major (X.y.z) - Incremented for any backwards incompatible public API changes.
* Minor (x.Y.z) - Incremented for new, backwards compatible, public API enhancements/fixes.
* Patch (x.y.Z) - Incremented for small, backwards compatible, bug fixes.

== Code of Conduct

Please note that this project is released with a link:CODE_OF_CONDUCT.adoc[CODE OF CONDUCT]. By
participating in this project you agree to abide by its terms.

== Contributions

Read link:CONTRIBUTING.adoc[CONTRIBUTING] for details.

== License

Read link:LICENSE.adoc[LICENSE] for details.

== History

Read link:CHANGES.adoc[CHANGES] for details.

== Credits

Engineered by link:https://www.alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].