#!/usr/bin/env ruby # frozen_string_literal: true # encoding=utf-8 class LayeredHash def initialize(table = {}, layers: %i[main]) @layers = layers.map { |layer| [layer, {}] }.to_h @current_layer = layers.first @layers[@current_layer] = table end private def method_missing(method, *args, &block) method_name = method.to_s if @layers.respond_to?(method_name) @layers.send(method_name, *args, &block) elsif method_name[-1] == '=' @layers[method_name.chop.to_sym] = args[0] elsif @layers.respond_to?(method_name) @layers.send(method_name, *args) else @layers[method_name.to_sym] end rescue StandardError => err warn("ERROR ** LayeredHash.method_missing(method: #{method_name}," \ " *args: #{args.inspect}, &block)") warn err.inspect warn(caller[0..4]) raise err end public # Retrieves the value of a key from the current layer using hash notation def [](key) raise "Current layer not set" unless @current_layer get_from_layer(@current_layer, key) end # Sets a key-value pair in the current layer using hash notation def []=(key, value) raise "Current layer not set" unless @current_layer set(@current_layer, key, value) end def fetch(key, *args) key_sym = key.to_sym if respond_to?("get_#{key}") send("get_#{key}") # elsif @table.key?(key_sym) elsif @layers[@current_layer].key?(key_sym) # @table[key_sym] @layers[@current_layer][key_sym] elsif block_given? yield key_sym elsif args.count.positive? args.first else binding.irb raise KeyError, "key not found: #{key}" end.tap { |ret| pp([__LINE__, "Poly.fetch #{key} #{args}", '->', ret]) if $pd } end # Retrieves the value of a key from the highest priority layer that has a value def get(key) @layers.reverse_each do |_, hash| return hash[key] if hash.key?(key) end nil end # Retrieves the value of a key from the specified layer def get_from_layer(layer, key) if @layers.key?(layer) @layers[layer][key] else raise ArgumentError, "Layer #{layer} does not exist" end end def merge(*args) @layers.merge(*args).tap { |ret| pp([__LINE__, "LayeredHash.merge", '->', ret]) if $pd } end def respond_to_missing?(method_name, include_private = false) @layers.key?(method_name.to_sym) || super end # Sets a key-value pair in the specified layer def set(layer, key, value) if @layers.key?(layer) @layers[layer][key] = value else raise ArgumentError, "Layer #{layer} does not exist" end end # Sets the current layer for use with hash notation ([]) def set_current_layer(layer) if @layers.key?(layer) @current_layer = layer else raise ArgumentError, "Layer #{layer} does not exist" end end def to_h @layers.to_h end end return if $PROGRAM_NAME != __FILE__ layered_hash = LayeredHash.new(layers: %i[low high]) # Set current layer layered_hash.set_current_layer(:low) # Set values in the current layer using hash notation layered_hash[:key1] = 'low_value' layered_hash[:key2] = 'low_only_value' # Switch current layer layered_hash.set_current_layer(:high) # Set values in the new current layer using hash notation layered_hash[:key1] = 'high_value' # Get value from the specific current layer using hash notation puts layered_hash[:key1] # Output: 'high_value' # Get value from the highest priority layer puts layered_hash.get(:key1) # Output: 'high_value' puts layered_hash.get(:key2) # Output: 'low_only_value'