lib/better_ipaddr/classes.rb in better_ipaddr-0.6.0 vs lib/better_ipaddr/classes.rb in better_ipaddr-0.7.0

- old
+ new

@@ -31,10 +31,12 @@ when String from_string(address, prefix_length, family: family, classful: classful) end end + # rubocop: disable CyclomaticComplexity + # Create an IPAddr from the given object, guessing the type of address given # based on its type and content. # # Note that an Integer that corresponds to an IPv4 address will be converted # to an IPAddr::V4, even though all such Integers also correspond to valid @@ -48,24 +50,83 @@ # converted to an IPAddr, a TypeError will be raise rather than returning # nil. # @param classful [Boolean] see Base.from_string # @return [IPAddr, Nil] def self.from(address, exception: false, classful: false) + if class_converter?(address) + return class_convert(address, classful: classful, exception: exception) + end + case address when IPAddr specialize address when Regex::IPV4, 0..V4::MAX_INT V4[address, classful: classful] when Regex::IPV6, 0..V6::MAX_INT V6[address] end || ( if exception - raise TypeError, "can't convert #{address.inspect} to #{self}" + (raise TypeError, "can't convert #{address.inspect} to #{self}") end ) end + # rubocop: enable CyclomaticComplexity + + # A Hash of classes and class names which can be converted to IPAddr::Base + # subclasses, but which are not necessarily loaded at the same time as this + # file. + # + # Conversion callables for custom classes can be registered here, e.g., with + # custom class Foo: + # + # IPAddr::Base.class_converters[Foo] = proc { |foo, _| foo.to_ipaddr } + # + # The arguments passed to the callable will be the same as the parameters of + # Kernel#IPAddr. + # + # @return [Hash{String, Class => Proc, Method}] + def self.class_converters + @class_converters ||= { + 'Resolv::IPv4' => V4.method(:from_string_representable), + 'Resolv::IPv6' => V6.method(:from_string_representable) + } + end + + # Return the class_converter for the given object, if one exists. + # + # Checks by both class identity and class name so that every class with a + # converter doesn't need to be loaded for this file to load. + # + # @return [Proc, Method, Nil] + def self.class_converter(address) + class_converters[address.class] ||= class_converters[address.class.name] + class_converters[address.class] + end + + def self.class_converter?(address) + class_converters.key?(address.class) || + class_converters.key?(address.class.name) + end + + # Convert the given object using its class_converter, if one exists. + # + # @return [IPAddr, Nil] + def self.class_convert(address, mask = nil, classful: nil, exception: false) + converter = class_converter(address) + converter && converter.call( + address, + mask, + classful: classful, + exception: exception + ) || ( + if exception + (raise TypeError, "can't convert #{address.inspect} to #{self}") + end + ) + end + # Create an IPAddr host subclass from the given object, guessing the type of # address given based on its type and content. # # Uses .from internally, so the same concerns apply, though the returned # object is guaranteed to be of a Host class or nil. @@ -128,9 +189,32 @@ else ipaddr = new(address, family) return ipaddr unless ipaddr.ipv4? ipaddr.classful || ipaddr end + end + + # Create an IPAddr from an object that can be converted to a String via + # #to_s. + # + # + # @param address [#to_s] + # @param mask [Integer, String] a netmask or prefix length + # @param family [Integer, Nil] + # @param classful [Boolean] controls the conversion of IPv4 addresses + # without a prefix length in CIDR notation. When false, these are assumed + # to be host networks (/32). When true, these are assumed to be classful + # (rfc791) networks, with an implicit prefix length. Has no effect on IPv6 + # addresses. + # @return [IPAddr] + def self.from_string_representable( + address, + mask = nil, + family: self::FAMILY, + classful: false, + exception: false + ) + from_string(address.to_s, mask, family: family, classful: classful) end # Convert an object to a prefix length. # # @param mask [Integer, String]