Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add Config methods: #to_h, #update, and #with #300

Merged
merged 3 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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