Files
news.gitea.happylizard.me/bin/gems/hashery-2.1.2/lib/hashery/path_hash.rb
T
pizza2d1 4cee170d66
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s
Add bin and edit workflow
2026-09-16 13:11:16 -06:00

239 lines
6.8 KiB
Ruby

module Hashery
# A PathHash is a hash whose values can be accessed in the normal manner,
# or with keys that are slash (`/`) separated strings. To get the whole hash
# as a single flattened level, call `#flat`. All keys are converted to strings.
# All end-of-the-chain values are kept in whatever value they are.
#
# s = PathHash['a' => 'b', 'c' => {'d' => :e}]
# s['a'] #=> 'b'
# s['c'] #=> {slashed: 'd'=>:e}
# s['c']['d'] #=> :e
# s['c/d'] #=> :e
#
# PathHash is derived from the SlashedHash class in the HashMagic project
# by Daniel Parker <gems@behindlogic.com>.
#
# Copyright (c) 2006 BehindLogic (http://hash_magic.rubyforge.org)
#
# Authors: Daniel Parker
#
# TODO: This class is very much a work in progess and will be substantially rewritten
# for future versions.
#
class PathHash < Hash
#
# Initialize PathHash.
#
# hsh - Priming Hash.
#
def initialize(hsh={})
raise ArgumentError, "must be a hash or array of slashed values" unless hsh.is_a?(Hash) || hsh.is_a?(Array)
@constructor = hsh.is_a?(Hash) ? hsh.class : Hash
@flat = flatten_to_hash(hsh)
end
# Standard Hash methods, plus the overwritten ones
#include StandardHashMethodsInRuby
# Behaves like the usual Hash#[] method, but you can access nested hash
# values by composing a single key of the traversing keys joined by '/':
#
# hash['c']['d'] # is the same as:
# hash['c/d']
#
def [](key)
rg = Regexp.new("^#{key}/?")
start_obj = if @constructor == OrderedHash
@constructor.new((@flat.instance_variable_get(:@keys_in_order) || []).collect {|e| e.gsub(rg,'')})
else
@constructor.new
end
v = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ rg)}.inject(start_obj) {|h,(k,v)| h[k.gsub(rg,'')] = v; h})
v.is_a?(self.class) && v.empty? ? nil : v
end
#
# Same as above, except sets value rather than retrieving it.
#
def []=(key,value)
@flat.reject! {|k,v| k == key || k =~ Regexp.new("^#{key}/")}
if value.is_a?(Hash)
flatten_to_hash(value).each do |hk,hv|
@flat[key.to_s+'/'+hk.to_s] = hv
end
else
@flat[key.to_s] = value
end
end
def clear # :nodoc:
@flat.clear
end
#
#
#
def fetch(key,default=:ehisehoah0928309q98y30,&block) # :nodoc:
value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h})
if value.is_a?(self.class) && value.empty?
if default == :ehisehoah0928309q98y30
if block_given?
block.call(key)
else
raise IndexError
end
value
else
default
end
else
value
end
end
#
# Delete entry from Hash. Slashed keys can be used here, too.
#
# key - The key to delete.
# block - Produces the return value if key not found.
#
# Returns delete value.
#
def delete(key,&block)
value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h})
return block.call(key) if value.is_a?(self.class) && value.empty? && block_given?
@flat.keys.reject {|k| !(k == key || k =~ Regexp.new("^#{key}/"))}.each {|k| @flat.delete(k)}
return value
end
#
def empty?
@flat.empty?
end
# This gives you the slashed key of the value, no matter where the value is in the tree.
def index(value)
@flat.index(value)
end
#
def inspect
@flat.inspect.insert(1,'slashed: ')
end
# This gives you only the top-level keys, no slashes. To get the list of slashed keys, do hash.flat.keys
def keys
@flat.inject([]) {|a,(k,v)| a << [k.split('/',2)].flatten[0]; a}.uniq
end
# This is rewritten to mean something slightly different than usual: Use this to restructure the hash, for cases when you
# end up with an array holding several hashes.
def rehash # :nodoc:
@flat.rehash
end
# Gives a list of all keys in all levels in the multi-level hash, joined by slashes.
#
# {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed.flat.keys
# #=> ['a/b', 'a/c', 'b']
#
def flat
@flat
end
# Expands the whole hash to Hash objects ... not useful very often, it seems.
def expand
inject({}) {|h,(k,v)| h[k] = v.is_a?(SlashedHash) ? v.expand : v; h}
end
def to_string_array
flatten_to_array(flat,[])
end
def slashed # :nodoc:
self
end
# Same as ordered! but returns a new SlashedHash object instead of modifying the same.
def ordered(*keys_in_order)
dup.ordered!(*keys_in_order)
end
# Sets the SlashedArray as ordered. The *keys_in_order must be a flat array
# of slashed keys that specify the order for each level:
#
# s = {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed
# s.ordered!('b', 'a/c', 'a/b')
# s.expand # => {'b'=>'c', 'a'=>{'c'=>'d', 'b'=>'c'}}
# # Note that the expanded hashes will *still* be ordered!
#
def ordered!(*keys_in_order)
return self if @constructor == OrderedHash
@constructor = OrderedHash
@flat = @flat.ordered(*keys_in_order)
self
end
#
def ==(other)
case other
when SlashedHash
@slashed == other.instance_variable_get(:@slashed)
when Hash
self == SlashedHash.new(other)
else
raise TypeError, "Cannot compare #{other.class.name} with SlashedHash"
end
end
private
def flatten_to_hash(hsh)
flat = @constructor.new
if hsh.is_a?(Array)
hsh.each do |e|
flat.merge!(flatten_to_hash(e))
end
elsif hsh.is_a?(Hash)
hsh.each do |k,v|
if v.is_a?(Hash)
flatten_to_hash(v).each do |hk,hv|
flat[k.to_s+'/'+hk.to_s] = hv
end
else
flat[k.to_s] = v
end
end
else
ks = hsh.split('/',-1)
v = ks.pop
ks = ks.join('/')
if !flat[ks].nil?
if flat[ks].is_a?(Array)
flat[ks] << v
else
flat[ks] = [flat[ks], v]
end
else
flat[ks] = v
end
end
flat
end
def flatten_to_array(value,a)
if value.is_a?(Array)
value.each {|e| flatten_to_array(e,a)}
elsif value.is_a?(Hash)
value.inject([]) {|aa,(k,v)| flatten_to_array(v,[]).each {|vv| aa << k+'/'+vv.to_s}; aa}.each {|e| a << e}
else
a << value.to_s
end
a
end
end
end