From 2caa59dd496fa56f0038e16853556cea407049e9 Mon Sep 17 00:00:00 2001 From: nick evans Date: Fri, 7 Jun 2024 23:06:18 -0400 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A7=20Add=20Config#to=5Fh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap/config.rb | 5 +++++ test/net/imap/test_config.rb | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index fdf9f0d4..5924918f 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -147,6 +147,11 @@ def initialize(parent = Config.global, **attrs) yield self if block_given? end + # :call-seq: to_h -> hash + # + # Returns all config attributes in a hash. + def to_h; data.members.to_h { [_1, send(_1)] } end + @default = new( debug: false, open_timeout: 30, diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb index b3bbe9d6..622f114a 100644 --- a/test/net/imap/test_config.rb +++ b/test/net/imap/test_config.rb @@ -209,4 +209,17 @@ class ConfigTest < Test::Unit::TestCase assert child.inherited?(:idle_response_timeout) end + test "#to_h" do + expected = { + debug: false, open_timeout: 30, idle_response_timeout: 5, sasl_ir: true, + } + attributes = Config::AttrAccessors::Struct.members + default_hash = Config.default.to_h + assert_equal expected, default_hash.slice(*expected.keys) + assert_equal attributes, default_hash.keys + global_hash = Config.global.to_h + assert_equal attributes, global_hash.keys + assert_equal expected, global_hash.slice(*expected.keys) + end + end From 381c885c2bb671edb701c8cd879de5dcec65eecf Mon Sep 17 00:00:00 2001 From: nick evans Date: Tue, 18 Jun 2024 22:53:50 -0400 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7=20Add=20Config#update=20for=20?= =?UTF-8?q?multiple=20assignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although `#update` is not atomic, an ArgumentError will be raised unless all of the kwargs names are valid. In that case, none of the attrs are updated. --- lib/net/imap/config.rb | 20 +++++++++++++++++++- test/net/imap/test_config.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index 5924918f..9f1540bc 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -143,10 +143,28 @@ def self.[](config) # :nodoc: unfinished API # If a block is given, the new config object is yielded to it. def initialize(parent = Config.global, **attrs) super(parent) - attrs.each do send(:"#{_1}=", _2) end + update(**attrs) yield self if block_given? end + # :call-seq: update(**attrs) -> self + # + # Assigns all of the provided +attrs+ to this config, and returns +self+. + # + # An ArgumentError is raised unless every key in +attrs+ matches an + # assignment method on Config. + # + # >>> + # *NOTE:* #update is not atomic. If an exception is raised due to an + # invalid attribute value, +attrs+ may be partially applied. + def update(**attrs) + unless (bad = attrs.keys.reject { respond_to?(:"#{_1}=") }).empty? + raise ArgumentError, "invalid config options: #{bad.join(", ")}" + end + attrs.each do send(:"#{_1}=", _2) end + self + end + # :call-seq: to_h -> hash # # Returns all config attributes in a hash. diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb index 622f114a..8adb79bf 100644 --- a/test/net/imap/test_config.rb +++ b/test/net/imap/test_config.rb @@ -222,4 +222,35 @@ class ConfigTest < Test::Unit::TestCase assert_equal expected, global_hash.slice(*expected.keys) end + test "#update" do + config = Config.global.update(debug: true, sasl_ir: false, open_timeout: 2) + assert_same Config.global, config + assert_same true, config.debug + assert_same false, config.sasl_ir + assert_same 2, config.open_timeout + end + + # It's simple to check first that the names are valid, so we do. + test "#update with invalid key name" do + config = Config.new(debug: true, sasl_ir: false, open_timeout: 2) + assert_raise(ArgumentError) do + config.update(debug: false, sasl_ir: true, bogus: :invalid) + end + assert_same true, config.debug? + assert_same false, config.sasl_ir? + assert_same 2, config.open_timeout + end + + # Current behavior: partial updates are applied, in order they're received. + # We could make #update atomic, but the complexity probably isn't worth it. + test "#update with invalid value" do + config = Config.new(debug: true, sasl_ir: false, open_timeout: 2) + assert_raise(TypeError) do + config.update(debug: false, open_timeout: :bogus, sasl_ir: true) + end + assert_same false, config.debug? # updated + assert_same 2, config.open_timeout # unchanged + assert_same false, config.sasl_ir? # unchanged + end + end From b72c221e39e840f582c9f91a7fd728aa7a856128 Mon Sep 17 00:00:00 2001 From: nick evans Date: Thu, 20 Jun 2024 07:29:40 -0400 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A7=20Add=20Config#with(**attrs)?= =?UTF-8?q?=20to=20make=20child=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap/config.rb | 18 ++++++++++++++++++ test/net/imap/test_config.rb | 25 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index 9f1540bc..10740ffb 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -165,6 +165,24 @@ def update(**attrs) self end + # :call-seq: + # with(**attrs) -> config + # with(**attrs) {|config| } -> result + # + # Without a block, returns a new config which inherits from self. With a + # block, yields the new config and returns the block's result. + # + # If no keyword arguments are given, an ArgumentError will be raised. + # + # If +self+ is frozen, the copy will also be frozen. + def with(**attrs) + attrs.empty? and + raise ArgumentError, "expected keyword arguments, none given" + copy = new(**attrs) + copy.freeze if frozen? + block_given? ? yield(copy) : copy + end + # :call-seq: to_h -> hash # # Returns all config attributes in a hash. diff --git a/test/net/imap/test_config.rb b/test/net/imap/test_config.rb index 8adb79bf..9391eadb 100644 --- a/test/net/imap/test_config.rb +++ b/test/net/imap/test_config.rb @@ -253,4 +253,29 @@ class ConfigTest < Test::Unit::TestCase assert_same false, config.sasl_ir? # unchanged end + test "#with" do + orig = Config.new(open_timeout: 123, sasl_ir: false) + assert_raise(ArgumentError) do + orig.with + end + copy = orig.with(open_timeout: 456, idle_response_timeout: 789) + refute copy.frozen? + assert_same orig, copy.parent + assert_equal 123, orig.open_timeout # unchanged + assert_equal 456, copy.open_timeout + assert_equal 789, copy.idle_response_timeout + vals = nil + result = orig.with(open_timeout: 99, idle_response_timeout: 88) do |c| + vals = [c.open_timeout, c.idle_response_timeout, c.frozen?] + :result + end + assert_equal :result, result + assert_equal [99, 88, false], vals + orig.freeze + result = orig.with(open_timeout: 11) do |c| + vals = [c.open_timeout, c.idle_response_timeout, c.frozen?] + end + assert_equal [11, 5, true], vals + end + end