On 16.04.2011 16:51, Iñáki Baz Castillo wrote:
> 2011/4/15 Kevin Mahler<kevin.mahler / yahoo.com>:
>> He has a key. It contains some
>> data. It's not necessarily true that he should duplicate that data in
>> the mapped-to values.
>
> To clarify, my exact case is the following:
Now it gets interesting. :-)
> I've coded a parser for SIP (similar to HTTP). The parser generates a
> Request object which inherits from Hash,
Usually it's better to use composition instead of inheritance to achieve his. Now your SipRequest inherits *all* methods from Hash including
some that you might not want users to be able to invoke.
> and each SIP request header
> (i.e. "From: sip:alice / example.org") becomes an entry of the hash
> (Request object) as follows:
>
> - The key is "FROM" (capitalized).
> - The value is an Array of strings (a s header can have multiple values).
>
> I need to store the key capitalized for fastest lookup, but I also
> want to store the original header name (which could be "from", "From",
> "frOM" and so).
So, to sum it up: you want to have a class for SIP request which allows
(efficient) header field access through [] using header name in any case pelling.
> So my parser adds an instance variable @real_name within the header
> name string ("FROM").
>
> When I do the lookup of a header in the Request object, I would like
> also to retrieve the key's @real_name, but I've already understood
> that this is only possible if taint the key string before inserting it
> in the hash and use Hash#assoc. This solution is not good for
> performance.
>
> The solution suggested by Robert is adding such information (the
> header original name) as a field in the hash entry value, so instead
> of having:
>
> request["FROM"]
> => [ "sip:alice / xample.org ]
>
> I would end with something like:
>
> request["FROM"]
> => Struct ( "From", [ "sip:alice / xample.org ] )
>
> The problem this last suggestion introduces is that it breaks the
> existing API and makes more complext for a developer to handle the
> Request class (which should be as easy as handling a Hash).
Here's how I'd do it. First, I would start with the interface, maybe
something like this
module SIP
class Request
def self.parse(io)
# ...
end
# get a header field by symbol
def [](header_name_sym)
end
# return the real name used
def header_name(header_name_sym)
end
end
end
Then I'd think how I could make that API work properly. For example two ariants, error and default value:
module SIP
class Request
HdrInfo = Struct.new name, values
DUMMY = HdrInfo[nil, [].freeze].freeze
LT = "\r\n".freeze
def self.parse(io)
hdr = {}
io.each_line LT do |l|
case l
when /^([^:]+:\s*(.*)$/
# too simplistic parsing!
hdr[$1] = $2.split(/,/).each(&:strip!)
when /^$/
break
else
raise "Not a header line: %p" % l
end
end
new(hdr)
end
def initialize(headers)
@hdr = {}
# assume hdr is String and values is parsed
headers.each do |hdr, values|
@hdr[normalize(hdr)] = HdrInfo[hdr, values]
end
end
# get a header field by symbol
def [](header_name_sym)
@hdr.fetch(normalize(header_name_sym)) do |k|
DUMMY
end.values
end
# return the real name used
def header_name(header_name_sym)
@hdr.fetch(normalize(header_name_sym)).do |k|
raise ArgumentError,
"Header not found %p" % header_name_sym
end.name
end
private
def normalize(h)
/[A-Z]/ =~ h ? h.downcase : h).to_sym
end
end
end
Of course we could build the internal hash straight away during parsing. he main focus of the example was how to use the header once parsed.
> Thanks to both for your comments.
You're welcome.
Kind regards
robert
--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/