Skip to content

Commit

Permalink
🔀 Merge pull request #300 from nevans/config/add-to_h-update-with-met…
Browse files Browse the repository at this point in the history
…hods

✨ Add Config methods: `#to_h`, `#update`, and `#with`
  • Loading branch information
nevans authored Jun 22, 2024
2 parents 98a9620 + b72c221 commit fa78037
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 1 deletion.
43 changes: 42 additions & 1 deletion lib/net/imap/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,51 @@ 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:
# 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.
def to_h; data.members.to_h { [_1, send(_1)] } end

@default = new(
debug: false,
open_timeout: 30,
Expand Down
69 changes: 69 additions & 0 deletions test/net/imap/test_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,73 @@ 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

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

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

0 comments on commit fa78037

Please sign in to comment.