Skip to content

Commit fa78037

Browse files
authored
🔀 Merge pull request #300 from nevans/config/add-to_h-update-with-methods
✨ Add Config methods: `#to_h`, `#update`, and `#with`
2 parents 98a9620 + b72c221 commit fa78037

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

‎lib/net/imap/config.rb

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,51 @@ def self.[](config) # :nodoc: unfinished API
143143
# If a block is given, the new config object is yielded to it.
144144
def initialize(parent = Config.global, **attrs)
145145
super(parent)
146-
attrs.each do send(:"#{_1}=", _2) end
146+
update(**attrs)
147147
yield self if block_given?
148148
end
149149

150+
# :call-seq: update(**attrs) -> self
151+
#
152+
# Assigns all of the provided +attrs+ to this config, and returns +self+.
153+
#
154+
# An ArgumentError is raised unless every key in +attrs+ matches an
155+
# assignment method on Config.
156+
#
157+
# >>>
158+
# *NOTE:* #update is not atomic. If an exception is raised due to an
159+
# invalid attribute value, +attrs+ may be partially applied.
160+
def update(**attrs)
161+
unless (bad = attrs.keys.reject { respond_to?(:"#{_1}=") }).empty?
162+
raise ArgumentError, "invalid config options: #{bad.join(", ")}"
163+
end
164+
attrs.each do send(:"#{_1}=", _2) end
165+
self
166+
end
167+
168+
# :call-seq:
169+
# with(**attrs) -> config
170+
# with(**attrs) {|config| } -> result
171+
#
172+
# Without a block, returns a new config which inherits from self. With a
173+
# block, yields the new config and returns the block's result.
174+
#
175+
# If no keyword arguments are given, an ArgumentError will be raised.
176+
#
177+
# If +self+ is frozen, the copy will also be frozen.
178+
def with(**attrs)
179+
attrs.empty? and
180+
raise ArgumentError, "expected keyword arguments, none given"
181+
copy = new(**attrs)
182+
copy.freeze if frozen?
183+
block_given? ? yield(copy) : copy
184+
end
185+
186+
# :call-seq: to_h -> hash
187+
#
188+
# Returns all config attributes in a hash.
189+
def to_h; data.members.to_h { [_1, send(_1)] } end
190+
150191
@default = new(
151192
debug: false,
152193
open_timeout: 30,

‎test/net/imap/test_config.rb

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,73 @@ class ConfigTest < Test::Unit::TestCase
209209
assert child.inherited?(:idle_response_timeout)
210210
end
211211

212+
test "#to_h" do
213+
expected = {
214+
debug: false, open_timeout: 30, idle_response_timeout: 5, sasl_ir: true,
215+
}
216+
attributes = Config::AttrAccessors::Struct.members
217+
default_hash = Config.default.to_h
218+
assert_equal expected, default_hash.slice(*expected.keys)
219+
assert_equal attributes, default_hash.keys
220+
global_hash = Config.global.to_h
221+
assert_equal attributes, global_hash.keys
222+
assert_equal expected, global_hash.slice(*expected.keys)
223+
end
224+
225+
test "#update" do
226+
config = Config.global.update(debug: true, sasl_ir: false, open_timeout: 2)
227+
assert_same Config.global, config
228+
assert_same true, config.debug
229+
assert_same false, config.sasl_ir
230+
assert_same 2, config.open_timeout
231+
end
232+
233+
# It's simple to check first that the names are valid, so we do.
234+
test "#update with invalid key name" do
235+
config = Config.new(debug: true, sasl_ir: false, open_timeout: 2)
236+
assert_raise(ArgumentError) do
237+
config.update(debug: false, sasl_ir: true, bogus: :invalid)
238+
end
239+
assert_same true, config.debug?
240+
assert_same false, config.sasl_ir?
241+
assert_same 2, config.open_timeout
242+
end
243+
244+
# Current behavior: partial updates are applied, in order they're received.
245+
# We could make #update atomic, but the complexity probably isn't worth it.
246+
test "#update with invalid value" do
247+
config = Config.new(debug: true, sasl_ir: false, open_timeout: 2)
248+
assert_raise(TypeError) do
249+
config.update(debug: false, open_timeout: :bogus, sasl_ir: true)
250+
end
251+
assert_same false, config.debug? # updated
252+
assert_same 2, config.open_timeout # unchanged
253+
assert_same false, config.sasl_ir? # unchanged
254+
end
255+
256+
test "#with" do
257+
orig = Config.new(open_timeout: 123, sasl_ir: false)
258+
assert_raise(ArgumentError) do
259+
orig.with
260+
end
261+
copy = orig.with(open_timeout: 456, idle_response_timeout: 789)
262+
refute copy.frozen?
263+
assert_same orig, copy.parent
264+
assert_equal 123, orig.open_timeout # unchanged
265+
assert_equal 456, copy.open_timeout
266+
assert_equal 789, copy.idle_response_timeout
267+
vals = nil
268+
result = orig.with(open_timeout: 99, idle_response_timeout: 88) do |c|
269+
vals = [c.open_timeout, c.idle_response_timeout, c.frozen?]
270+
:result
271+
end
272+
assert_equal :result, result
273+
assert_equal [99, 88, false], vals
274+
orig.freeze
275+
result = orig.with(open_timeout: 11) do |c|
276+
vals = [c.open_timeout, c.idle_response_timeout, c.frozen?]
277+
end
278+
assert_equal [11, 5, true], vals
279+
end
280+
212281
end

0 commit comments

Comments
 (0)