From 0bb18b33b2ea4d1b6717536bcbd967ff25e48b4c Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Sun, 16 Feb 2025 21:20:52 -0500 Subject: [PATCH] Livecheck::Options: Add #merge! --- Library/Homebrew/livecheck/options.rb | 27 +++++++++++++ .../Homebrew/test/livecheck/options_spec.rb | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/Library/Homebrew/livecheck/options.rb b/Library/Homebrew/livecheck/options.rb index 2f851185fd..307d393b14 100644 --- a/Library/Homebrew/livecheck/options.rb +++ b/Library/Homebrew/livecheck/options.rb @@ -56,6 +56,33 @@ module Homebrew Options.new(**new_options) end + # Merges values from `other` into `self` and returns `self`. + # + # `nil` values are removed from `other` before merging if it is an + # `Options` object, as these are unitiailized values. This ensures that + # existing values in `self` aren't unexpectedly overwritten with defaults. + sig { params(other: T.any(Options, T::Hash[Symbol, T.untyped])).returns(Options) } + def merge!(other) + return self if other.empty? + + if other.is_a?(Options) + return self if self == other + + other.instance_variables.each do |ivar| + next if (v = T.let(other.instance_variable_get(ivar), Object)).nil? + + instance_variable_set(ivar, v) + end + else + other.each do |k, v| + cmd = :"#{k}=" + send(cmd, v) if respond_to?(cmd) + end + end + + self + end + sig { params(other: Object).returns(T::Boolean) } def ==(other) return false unless other.is_a?(Options) diff --git a/Library/Homebrew/test/livecheck/options_spec.rb b/Library/Homebrew/test/livecheck/options_spec.rb index d9ced1bff1..468deb6f4e 100644 --- a/Library/Homebrew/test/livecheck/options_spec.rb +++ b/Library/Homebrew/test/livecheck/options_spec.rb @@ -27,6 +27,10 @@ RSpec.describe Homebrew::Livecheck::Options do end let(:merged_hash) { args.merge(other_args) } + let(:base_options) { options.new(**args) } + let(:other_options) { options.new(**other_args) } + let(:merged_options) { options.new(**merged_hash) } + describe "#url_options" do it "returns a Hash of the options that are provided as arguments to the `url` DSL method" do expect(options.new.url_options).to eq({ @@ -68,6 +72,40 @@ RSpec.describe Homebrew::Livecheck::Options do end end + describe "#merge!" do + it "merges values from `other` into `self` and returns `self`" do + o1 = options.new(**args) + expect(o1.merge!(other_options)).to eq(merged_options) + expect(o1).to eq(merged_options) + + o2 = options.new(**args) + expect(o2.merge!(other_args)).to eq(merged_options) + expect(o2).to eq(merged_options) + + o3 = options.new(**args) + expect(o3.merge!(base_options)).to eq(base_options) + expect(o3).to eq(base_options) + + o4 = options.new(**args) + expect(o4.merge!(args)).to eq(base_options) + expect(o4).to eq(base_options) + + o5 = options.new(**args) + expect(o5.merge!(options.new)).to eq(base_options) + expect(o5).to eq(base_options) + + o6 = options.new(**args) + expect(o6.merge!({})).to eq(base_options) + expect(o6).to eq(base_options) + end + + it "skips over hash values without a corresponding Options value" do + o1 = options.new(**args) + expect(o1.merge!({ nonexistent: true })).to eq(base_options) + expect(o1).to eq(base_options) + end + end + describe "#==" do it "returns true if all instance variables are the same" do obj_with_args1 = options.new(**args)