diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 33f9af6..4d0f301 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,23 +30,6 @@ jobs: with: token: ${{ secrets.QLTY_COVERAGE_TOKEN }} files: coverage/.resultset.json - # CodeClimate has become qlth.sh, this integration is currently broken. - # coverage: - # needs: test - # runs-on: ubuntu-latest - # env: - # BUNDLE_WITHOUT: optional - # steps: - # - uses: actions/checkout@v6.0.1 - # - uses: ruby/setup-ruby@v1 - # with: - # bundler-cache: true - # - name: Publish code coverage - # uses: paambaati/codeclimate-action@v9 - # env: - # CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} - # with: - # coverageCommand: bundle exec rake yard: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 8b80bb7..26c65b8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ /spec/reports/ /tmp/ /bin/ +/vendor/bundle/ # rspec failure tracking .rspec_status diff --git a/.qlty/qlty.toml b/.qlty/qlty.toml index 2f50e17..e130b32 100644 --- a/.qlty/qlty.toml +++ b/.qlty/qlty.toml @@ -77,3 +77,13 @@ threshold = 5 [[source]] name = "default" default = true + +[[plugin]] +name = "rubocop" +package_file = "Gemfile.qlty" +package_filters = ["rubocop"] +config_files = [".rubocop.yml", ".rubocop_todo.yml"] + +[[plugin]] +name = "reek" +mode = "comment" diff --git a/Gemfile b/Gemfile index 04e63f5..d09c9b0 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,6 @@ group :optional do gem "guard-rspec" gem "pry" gem "redcarpet", platform: :mri # redcarpet doesn't support jruby - gem "rubocop" - gem "rubocop-rake" - gem "rubocop-rspec" gem "ruby-maven", platform: :jruby gem "ruby-prof", platform: :mri gem "simplecov-html" @@ -20,6 +17,7 @@ group :optional do gem "terminal-notifier" gem "terminal-notifier-guard" gem "webrick" + eval_gemfile("Gemfile.qlty") end gem "bigdecimal" diff --git a/Gemfile.lock b/Gemfile.lock index a8656b5..3220a71 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - ruby-units (4.1.0) + ruby-units (5.0.0) GEM remote: https://rubygems.org/ @@ -20,12 +20,10 @@ GEM reline (>= 0.3.8) diff-lcs (1.6.2) docile (1.4.1) - e2mmap (0.1.0) erb (6.0.1) erb (6.0.1-java) ffi (1.17.2-arm64-darwin) ffi (1.17.2-java) - ffi (1.17.2-x86_64-darwin) ffi (1.17.2-x86_64-linux-gnu) formatador (1.2.3) reline @@ -74,13 +72,12 @@ GEM racc (~> 1.4) nokogiri (1.18.10-java) racc (~> 1.4) - nokogiri (1.18.10-x86_64-darwin) - racc (~> 1.4) nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) + observer (0.1.2) ostruct (0.6.3) parallel (1.27.0) parser (3.3.10.0) @@ -110,7 +107,9 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rdoc (7.0.1) + rbs (3.10.0) + logger + rdoc (7.0.2) erb psych (>= 4.0.0) tsort @@ -118,7 +117,7 @@ GEM regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) - reverse_markdown (2.1.1) + reverse_markdown (3.0.1) nokogiri rexml (3.4.4) rspec (3.13.2) @@ -134,7 +133,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) - rubocop (1.82.0) + rubocop (1.82.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -167,21 +166,27 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) - solargraph (0.48.0) + solargraph (0.57.0) backport (~> 1.2) - benchmark - bundler (>= 1.17.2) + benchmark (~> 0.4) + bundler (~> 2.0) diff-lcs (~> 1.4) - e2mmap - jaro_winkler (~> 1.5) + jaro_winkler (~> 1.6, >= 1.6.1) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) parser (~> 3.0) - reverse_markdown (>= 1.0.5, < 3) - rubocop (>= 0.52) + prism (~> 1.4) + rbs (>= 3.6.1, <= 4.0.0.dev.4) + reverse_markdown (~> 3.0) + rubocop (~> 1.76) thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) + yard-activesupport-concern (~> 0.0) + yard-solargraph (~> 0.1) spoon (0.0.6) ffi stringio (3.2.0) @@ -195,17 +200,16 @@ GEM unicode-emoji (4.2.0) webrick (1.9.2) yard (0.9.38) + yard-activesupport-concern (0.0.1) + yard (>= 0.8) + yard-solargraph (0.1.0) + yard (~> 0.9) PLATFORMS - arm64-darwin-21 - arm64-darwin-22 - arm64-darwin-23 - arm64-darwin-24 arm64-darwin-25 java universal-java-11 universal-java-18 - x86_64-darwin-19 x86_64-linux DEPENDENCIES @@ -232,4 +236,4 @@ DEPENDENCIES yard BUNDLED WITH - 4.0.2 + 2.7.2 diff --git a/Gemfile.qlty b/Gemfile.qlty new file mode 100644 index 0000000..35bc830 --- /dev/null +++ b/Gemfile.qlty @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# These are gems used by qlty to perform code quality checks. +# They are installed by the qlty cli tool and are included for local development, but are not installed in CI. + +gem "rubocop" +gem "rubocop-rake" +gem "rubocop-rspec" diff --git a/README.md b/README.md index 00d9ad7..64b74e7 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ manipulations to ensure an accurate result. ## Installation +This package requires Ruby 3.2 or later. + This package may be installed using: ```bash @@ -52,6 +54,7 @@ unit = "1 mm".to_unit # convert string object unit = object.to_unit # convert any object using object.to_s unit = Unit.new('1/4 cup') # Rational number unit = Unit.new('1+1i mm') # Complex Number +unit = Unit.new(scalar: 1.5, numerator: [""], denominator: [""]) # keyword arguments ``` ### Rules diff --git a/lib/ruby_units/configuration.rb b/lib/ruby_units/configuration.rb index 0f52b77..3d4206f 100644 --- a/lib/ruby_units/configuration.rb +++ b/lib/ruby_units/configuration.rb @@ -42,9 +42,15 @@ class Configuration # @return [Symbol] the format to use when generating output (:rational or :exponential) (default: :rational) attr_reader :format - def initialize - self.format = :rational - self.separator = true + # Initialize configuration with keyword arguments + # + # @param separator [Boolean] whether to include a space between the scalar and the unit (default: true) + # @param format [Symbol] the format to use when generating output (default: :rational) + # @param _options [Hash] additional keyword arguments (ignored, for forward compatibility) + # @return [Configuration] a new configuration instance + def initialize(separator: true, format: :rational, **_options) + self.separator = separator + self.format = format end # Use a space for the separator to use when generating output. diff --git a/lib/ruby_units/definition.rb b/lib/ruby_units/definition.rb index 793b4c5..8d9ad01 100644 --- a/lib/ruby_units/definition.rb +++ b/lib/ruby_units/definition.rb @@ -79,15 +79,11 @@ def definition=(unit) # is this definition for a prefix? # @return [Boolean] - def prefix? - kind == :prefix - end + def prefix? = kind == :prefix # Is this definition the unity definition? # @return [Boolean] - def unity? - prefix? && scalar == 1 - end + def unity? = prefix? && scalar == 1 # is this a base unit? # units are base units if the scalar is one, and the unit is defined in terms of itself. diff --git a/lib/ruby_units/unit.rb b/lib/ruby_units/unit.rb index 9a7d30e..b0e12db 100644 --- a/lib/ruby_units/unit.rb +++ b/lib/ruby_units/unit.rb @@ -325,7 +325,10 @@ def self.eliminate_terms(q, n, d) end num = UNITY_ARRAY if num.empty? den = UNITY_ARRAY if den.empty? - { scalar: q, numerator: num.flatten, denominator: den.flatten } + scalar = q + numerator = num.flatten + denominator = den.flatten + { scalar:, numerator:, denominator: } end # Creates a new unit from the current one with all common terms eliminated. @@ -519,32 +522,32 @@ def initialize(*options) end case options[0] - when Unit - copy(options[0]) + in Unit => unit + copy(unit) return - when Hash - @scalar = options[0][:scalar] || 1 - @numerator = options[0][:numerator] || UNITY_ARRAY - @denominator = options[0][:denominator] || UNITY_ARRAY - @signature = options[0][:signature] - when Array - initialize(*options[0]) + in Hash => hash + @scalar = hash[:scalar] || 1 + @numerator = hash[:numerator] || UNITY_ARRAY + @denominator = hash[:denominator] || UNITY_ARRAY + @signature = hash[:signature] + in Array => array + initialize(*array) return - when Numeric - @scalar = options[0] + in Numeric => num + @scalar = num @numerator = @denominator = UNITY_ARRAY - when Time - @scalar = options[0].to_f + in Time => time + @scalar = time.to_f @numerator = [""] @denominator = UNITY_ARRAY - when DateTime, Date - @scalar = options[0].ajd + in DateTime | Date => date + @scalar = date.ajd @numerator = [""] @denominator = UNITY_ARRAY - when /^\s*$/ + in /^\s*$/ => _empty raise ArgumentError, "No Unit Specified" - when String - parse(options[0]) + in String => str + parse(str) else raise ArgumentError, "Invalid Unit Format" end @@ -1334,22 +1337,25 @@ def -@ def abs return @scalar.abs if unitless? - self.class.new(@scalar.abs, @numerator, @denominator) + self.class.new(scalar: @scalar.abs, numerator: @numerator, denominator: @denominator) end # ceil of a unit + # Forwards all arguments to the scalar's ceil method # @return [Numeric,Unit] - def ceil(*args) - return @scalar.ceil(*args) if unitless? + def ceil(...) + return @scalar.ceil(...) if unitless? - self.class.new(@scalar.ceil(*args), @numerator, @denominator) + self.class.new(scalar: @scalar.ceil(...), numerator: @numerator, denominator: @denominator) end + # Floor of a unit + # Forwards all arguments to the scalar's floor method # @return [Numeric,Unit] - def floor(*args) - return @scalar.floor(*args) if unitless? + def floor(...) + return @scalar.floor(...) if unitless? - self.class.new(@scalar.floor(*args), @numerator, @denominator) + self.class.new(scalar: @scalar.floor(...), numerator: @numerator, denominator: @denominator) end # Round the unit according to the rules of the scalar's class. Call this @@ -1357,22 +1363,25 @@ def floor(*args) # Rational, etc..). Because unit conversions can often result in Rational # scalars (to preserve precision), it may be advisable to use +to_s+ to # format output instead of using +round+. + # Forwards all arguments to the scalar's round method # @example # RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').round(1) #=> 2187/100 m/min # RubyUnits::Unit.new('21870 mm/min').convert_to('m/min').to_s('%0.1f') #=> 21.9 m/min # # @return [Numeric,Unit] - def round(*args, **kwargs) - return @scalar.round(*args, **kwargs) if unitless? + def round(...) + return @scalar.round(...) if unitless? - self.class.new(@scalar.round(*args, **kwargs), @numerator, @denominator) + self.class.new(scalar: @scalar.round(...), numerator: @numerator, denominator: @denominator) end + # Truncate the unit according to the scalar's truncate method + # Forwards all arguments to the scalar's truncate method # @return [Numeric, Unit] - def truncate(*args) - return @scalar.truncate(*args) if unitless? + def truncate(...) + return @scalar.truncate(...) if unitless? - self.class.new(@scalar.truncate(*args), @numerator, @denominator) + self.class.new(scalar: @scalar.truncate(...), numerator: @numerator, denominator: @denominator) end # Returns next unit in a range. Increments the scalar by 1. @@ -1385,7 +1394,7 @@ def truncate(*args) def succ raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i - self.class.new(@scalar.to_i.succ, @numerator, @denominator) + self.class.new(scalar: @scalar.to_i.succ, numerator: @numerator, denominator: @denominator) end alias next succ @@ -1400,7 +1409,7 @@ def succ def pred raise ArgumentError, "Non Integer Scalar" unless @scalar == @scalar.to_i - self.class.new(@scalar.to_i.pred, @numerator, @denominator) + self.class.new(scalar: @scalar.to_i.pred, numerator: @numerator, denominator: @denominator) end # Tries to make a Time object from current unit. Assumes the current unit hold the duration in seconds from the epoch. diff --git a/lib/ruby_units/version.rb b/lib/ruby_units/version.rb index fd6350a..7e26759 100644 --- a/lib/ruby_units/version.rb +++ b/lib/ruby_units/version.rb @@ -2,6 +2,6 @@ module RubyUnits class Unit < Numeric - VERSION = "4.1.0" + VERSION = "5.0.0" end end diff --git a/ruby-units.gemspec b/ruby-units.gemspec index 3983ad8..c3d561c 100644 --- a/ruby-units.gemspec +++ b/ruby-units.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |spec| spec.email = ["kevin.olbrich@gmail.com"] spec.required_rubygems_version = ">= 2.0" - spec.required_ruby_version = ">= 2.7" + spec.required_ruby_version = ">= 3.2" spec.summary = "Provides classes and methods to perform unit math and conversions" spec.description = "Provides classes and methods to perform unit math and conversions" spec.homepage = "https://github.com/olbrich/ruby-units" diff --git a/spec/ruby_units/configuration_spec.rb b/spec/ruby_units/configuration_spec.rb index 0b35c76..a90970f 100644 --- a/spec/ruby_units/configuration_spec.rb +++ b/spec/ruby_units/configuration_spec.rb @@ -3,6 +3,32 @@ require "spec_helper" describe RubyUnits::Configuration do + describe "#initialize" do + it "accepts keyword arguments for separator and format" do + config = described_class.new(separator: false, format: :exponential) + expect(config.separator).to be_nil + expect(config.format).to eq :exponential + end + + it "uses default values when no arguments are provided" do + config = described_class.new + expect(config.separator).to eq " " + expect(config.format).to eq :rational + end + + it "accepts additional keyword arguments for forward compatibility" do + expect { described_class.new(separator: true, unknown_option: "value") }.not_to raise_error + end + + it "validates separator value" do + expect { described_class.new(separator: "invalid") }.to raise_error(ArgumentError) + end + + it "validates format value" do + expect { described_class.new(format: :invalid) }.to raise_error(ArgumentError) + end + end + describe ".separator" do context "when set to true" do it "has a space between the scalar and the unit" do diff --git a/spec/ruby_units/unit_spec.rb b/spec/ruby_units/unit_spec.rb index d919231..5970f3d 100644 --- a/spec/ruby_units/unit_spec.rb +++ b/spec/ruby_units/unit_spec.rb @@ -273,6 +273,43 @@ end end + # keyword arguments + describe RubyUnits::Unit.new(scalar: 1.5, numerator: [""], denominator: [""]) do + it { is_expected.to be_a Numeric } + it { is_expected.to be_an_instance_of Unit } + + describe "#scalar" do + subject { super().scalar } + + it { is_expected.to eq(1.5) } + it { is_expected.to be_a Float } + end + + describe "#units" do + subject { super().units } + + it { is_expected.to eq("m/s") } + end + + describe "#kind" do + subject { super().kind } + + it { is_expected.to eq(:speed) } + end + + it { is_expected.not_to be_temperature } + it { is_expected.not_to be_degree } + it { is_expected.to be_base } + it { is_expected.not_to be_unitless } + it { is_expected.not_to be_zero } + + describe "#base" do + subject { super().base } + + it { is_expected.to eq(subject) } + end + end + # scalar and unit describe RubyUnits::Unit.new("1 mm") do it { is_expected.to be_a Numeric }