require 'kvmultiplex/provider' require 'logger'; require 'diff-lcs' module KVMultiplex class Multiplexer attr_reader :logger def initialize(delimiter: ".", logger: Logger.new) @logger = logger @delimiter = delimiter.dup @providers = {} end def register(prefix, provider, prepend: false) raise "provider must be a KVMultiplex::Provider." unless provider.is_a?(KVMultiplex::Provider) prefix = if prefix.is_a?(String) prefix.split(@delimiter).freeze elsif prefix.is_a?(Array) && prefix.all? { |i| i.is_a?(String) } prefix.map { |i| i.to_s.dup }.freeze else raise "prefix must be a String or array of Strings." end @providers[prefix] ||= [] if prepend @providers[prefix].unshift provider else @providers[prefix].push provider end end def get!(key) ret = get(key) raise "Value not found for key: #{key}" if ret.nil? ret end def get(key) providers, subkey, full_key = find_providers_for_key(key) if providers.nil? || providers.empty? logger.warn "No providers for '#{full_key.inspect}'." nil else ret = providers.find { |provider| provider.get(subkey, full_key) } logger.warn "No values found for '#{full_key.inspect}'." if ret.nil? ret end end def set(key, value) providers, subkey, full_key = find_providers_for_key(key) if providers.nil? || providers.empty? logger.warn "No providers for '#{full_key.inspect}'." nil else logger.warn "Multiple providers found for '#{full_key.inspect}'; this might cause surprising behavior!" \ unless providers.size == 1 ret = providers.find do |provider| logger.debug "Attempting to set into #{provider}." p_ret = provider.set(subkey, full_key, value) logger.debug "Failed to set into #{provider}." if p_ret.nil? !p_ret.nil? end raise "No provider able to set '#{full_key.inspect}'." unless ret ret end value end def find_providers_for_key(key) logger.debug "Key lookup for '#{key.inspect}'." key = if key.is_a?(String) key.split(@delimiter).freeze elsif key.is_a?(Array) && key.all? { |i| i.is_a?(String) } key.map(&:to_s).freeze else raise "key must be a String or array of Strings." end key_length = key.size longest = -1 longest_prefix = nil matched_providers = nil @providers.each_pair do |prefix, providers| lcs_length = Diff::LCS.LCS(key, prefix).length next unless [longest, lcs_length].max == prefix.length longest = lcs_length longest_prefix = prefix matched_providers = providers break if key_length == lcs_length # we've matched exactly end if matched_providers.nil? [nil, nil, key] else subkey = key - longest_prefix [matched_providers, subkey, key] end end end end