RegHash
During the early days developing Rote we had need for a special type of Hash that allowed us to use Regexp keys and search by String keys. The idea being that the first matching key (of either kind) is used and it’s value returned to the caller. This would allow us to use a basic Hash-like syntax for working with a set of file-extension mappings, and to have the mappings specified by regular expressions which them mapped to strings (the output extension).
We also thought it would be nice to allow backreferences from the key match to be used in the value (with $1..$n notation), so that you could have a mapping from /t(.*)/ => to ’$1’ (for a contrived example) and have ‘thtml’ mapped to ‘html’.
Obviously for this to work, entries have to be kept in insertion order, which is contrary to the operation of a regular hash. This has the effect of making searches/insertion slow, but since we would mainly be iterating until we found a match, and Rote doesn’t run in a performance-critical context anyway, we weren’t concerned about this.
As with most of Rote’s best stuff, this came about with a lot of input and guidance from Jonathan Paisley, and between us this is what we came up with.
This is a single-file bit, which you can Download here.
class RegHash
class << self
alias :[] :new
end
# Create a new RegHash, copying the supplied
# map (maybe a hash, but that will end up
# unordering this one of course...)
def initialize(map = nil)
@data = []
map.each { |k,v| self[k] = v } if map
end
# Insert the given regex key unless it already exists.
# You may use string representations for the keys, but
# they are converted as-is (i.e. unescaped) to regexps
# so be extra careful there.
#
# Returns the value that was inserted, or nil.
def []=(key,value)
@data << [key,value] unless member?(key)
end
# Fetch the first matching data.
def [](key)
md = nil
if v = @data.detect { |it| md = /^#{it[0]}$/.match(key.to_s) }
v[1][0].gsub!(/\$(\d)/) { md[$1.to_i] }
v[1]
end
end
# Fetch a single entry based on key equality.
def fetch_entry(key)
@data.detect { |it| it[0] == key }
end
# Determine membership based on key equality.
def member?(key)
true if fetch_entry(key)
end
end
It’s nothing overly complex, and it’s certainly not pushing any boundaries anywhere, but it works, and it ticks all the boxes we needed. Although it’s not a real Hash (in the sense of is_a?) we didn’t need that (in Ruby you mostly don’t), and if you want to map regexps to string keys it works quite nicely.
I found useful reminder of Ruby’s power and flexibility came from trying to duplicate RegHash in another language ;)

