loading
Generated 2025-10-09T00:04:12+00:00

All Files ( 100.0% covered at 16.53 hits/line )

6 files in total.
99 relevant lines, 99 lines covered and 0 lines missed. ( 100.0% )
19 total branches, 19 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/versionaire.rb 100.00 % 21 7 7 0 1.00 100.00 % 0 0 0
lib/versionaire/cast.rb 100.00 % 10 4 4 0 1.00 100.00 % 0 0 0
lib/versionaire/error.rb 100.00 % 7 2 2 0 1.00 100.00 % 0 0 0
lib/versionaire/extensions/option_parser.rb 100.00 % 10 5 5 0 1.20 100.00 % 0 0 0
lib/versionaire/function.rb 100.00 % 76 40 40 0 20.65 100.00 % 11 11 0
lib/versionaire/version.rb 100.00 % 75 41 41 0 19.29 100.00 % 8 8 0

lib/versionaire.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "versionaire/cast"
  3. 1 require "versionaire/error"
  4. 1 require "versionaire/function"
  5. 1 require "versionaire/version"
  6. 1 module Versionaire
  7. 1 DELIMITER = "."
  8. 1 PATTERN = /
  9. \A( # Start of string and OR.
  10. \d* # Major only.
  11. | # OR pipe.
  12. \d+ # Major.
  13. #{DELIMITER}? # Delimiter.
  14. \d* # Minor.
  15. (?:#{DELIMITER}\d+) # Passive delimiter and patch.
  16. )\z # End of OR and string.
  17. /x
  18. end

lib/versionaire/cast.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Versionaire
  3. # Refines Kernel in order to provide a top-level Version conversion function.
  4. 1 module Cast
  5. 1 refine Kernel do
  6. 1 def Version(object) = Versionaire::Version object
  7. end
  8. end
  9. end

lib/versionaire/error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Versionaire
  3. # The base error class for all gem related errors.
  4. 1 class Error < StandardError
  5. end
  6. end

lib/versionaire/extensions/option_parser.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "optparse"
  3. 1 require "versionaire"
  4. 1 OptionParser.accept Versionaire::Version do |value|
  5. 2 Versionaire::Version value
  6. rescue Versionaire::Error
  7. 1 raise OptionParser::InvalidArgument, value
  8. end

lib/versionaire/function.rb

100.0% lines covered

100.0% branches covered

40 relevant lines. 40 lines covered and 0 lines missed.
11 total branches, 11 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "refinements/array"
  3. # The gem namespace.
  4. 1 module Versionaire
  5. 1 module_function
  6. # Conversion function (strict) for casting an object into a version.
  7. 1 def Version object
  8. 82 Converter.new(object).then do |converter|
  9. 82 when: 56 case object
  10. 56 when: 10 when String then converter.from_string
  11. 10 when: 10 when Array then converter.from_array
  12. 10 when: 2 when Hash then converter.from_hash
  13. 2 else: 4 when Version then object
  14. 4 else converter.from_object
  15. end
  16. end
  17. end
  18. # Aids with converting objects into valid versions.
  19. 1 class Converter
  20. 1 using Refinements::Array
  21. 1 def initialize object, model: Version
  22. 82 @object = object
  23. 82 @model = model
  24. end
  25. 1 def from_string
  26. 56 body = "Use: <major>.<minor>.<patch>, <major>.<minor>, <major>, or empty string."
  27. 56 else: 53 then: 3 fail Error, error_message(object, body) unless PATTERN.match? object
  28. 53 string_to_version
  29. end
  30. 1 def from_array
  31. 10 body = "Use: [<major>, <minor>, <patch>], [<major>, <minor>], [<major>], or []."
  32. 10 else: 8 then: 2 fail Error, error_message(object, body) unless (0..3).cover? object.size
  33. 8 model.new(**attributes_for(object.pad(0, 3)))
  34. end
  35. 1 def from_hash
  36. 10 body = "Use: {major: <major>, minor: <minor>, patch: <patch>}, " \
  37. "{major: <major>, minor: <minor>}, {major: <major>}, or {}."
  38. 10 else: 8 then: 2 fail Error, error_message(object, body) unless required_keys?
  39. 8 Version[**object]
  40. end
  41. 1 def from_object
  42. 4 fail Error, error_message(object, "Use: String, Array, Hash, or Version.")
  43. end
  44. 1 private
  45. 1 attr_reader :object, :model
  46. 1 def string_to_version
  47. 53 object.split(DELIMITER)
  48. .map(&:to_i)
  49. 53 .then { |numbers| numbers.pad 0, 3 }
  50. 53 .then { |values| model.new(**attributes_for(values)) }
  51. end
  52. 1 def attributes_for(values) = model.members.zip(values).to_h
  53. 15 def required_keys? = object.keys.all? { |key| model.members.include? key }
  54. 1 def error_message(object, body) = "Invalid version conversion: #{object}. #{body}"
  55. end
  56. 1 private_constant :Converter
  57. end

lib/versionaire/version.rb

100.0% lines covered

100.0% branches covered

41 relevant lines. 41 lines covered and 0 lines missed.
8 total branches, 8 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "refinements/array"
  3. 1 module Versionaire
  4. # An immutable, semantic version value object.
  5. 1 Version = Data.define :major, :minor, :patch do
  6. 1 include Comparable
  7. 1 using Refinements::Array
  8. 1 def initialize major: 0, minor: 0, patch: 0
  9. 176 super
  10. 176 validate
  11. end
  12. 1 def +(other) = add other
  13. 1 def -(other) = substract other
  14. 1 def ==(other) = hash == other.hash
  15. 1 alias_method :eql?, :==
  16. 1 def <=>(other) = to_s <=> other.to_s
  17. 1 def down(key, value = 1) = substract({key => value})
  18. 1 def up(key, value = 1) = add({key => value})
  19. 1 def bump key
  20. 4 when: 1 case key
  21. 1 when: 1 when :major then bump_major
  22. 1 when: 1 when :minor then bump_minor
  23. 1 else: 1 when :patch then bump_patch
  24. 1 else fail Error, %(Invalid key: #{key.inspect}. Use: #{members.to_sentence "or"}.)
  25. end
  26. end
  27. 1 def inspect = to_s.inspect
  28. 1 def to_proc = method(:public_send).to_proc
  29. 1 def to_s = to_a.join DELIMITER
  30. 1 alias_method :to_str, :to_s
  31. 1 alias_method :to_a, :deconstruct
  32. 1 private
  33. 1 def validate
  34. 176 else: 175 then: 1 fail Error, "Major, minor, and patch must be a number." unless to_a.all? Integer
  35. 175 then: 3 else: 172 fail Error, "Major, minor, and patch must be a positive number." if to_a.any?(&:negative?)
  36. end
  37. 1 def add other
  38. 5 attributes = other.to_h
  39. 12 attributes.each { |key, value| attributes[key] = public_send(key) + value }
  40. 5 with(**attributes)
  41. end
  42. 1 def substract other
  43. 7 attributes = other.to_h
  44. 18 attributes.each { |key, value| attributes[key] = public_send(key) - value }
  45. 7 with(**attributes)
  46. end
  47. 1 def bump_major = with major: major + 1, minor: 0, patch: 0
  48. 1 def bump_minor = with major:, minor: minor + 1, patch: 0
  49. 1 def bump_patch = with major:, minor:, patch: patch + 1
  50. end
  51. end