: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] Ruby doesn't provide a primitive version type by default. Versionaire fills this gap by providing an immutable, thread-safe, and link:https://semver.org[Semantic Version] in order to use versions within your applications. This new version type behaves and feels a lot like other primitives (i.e. `String`, `Array`, `Hash`, etc) and can even be cast/converted from other primitives. 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::Cast+` 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 pollute 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] # ---- ==== 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 ---- === 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 link:https://github.com/ruby/optparse[OptionParser] 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`. . `instance.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 |instance| instance.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=>#}` 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.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].