Rack::Utils contains a grab-bag of useful methods for writing web applications adopted from all kinds of Ruby libraries.
Every standard HTTP code mapped to the appropriate message. Generated with:
curl -s http://www.iana.org/assignments/http-status-codes | \
ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and
puts " #{m[1]} => \x27#{m[2].strip}x27,"'Responses with HTTP status codes that should not have an entity body
# File lib/rack/utils.rb, line 112
112: def build_nested_query(value, prefix = nil)
113: case value
114: when Array
115: value.map { |v|
116: build_nested_query(v, "#{prefix}[]")
117: }.join("&")
118: when Hash
119: value.map { |k, v|
120: build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
121: }.join("&")
122: when String
123: raise ArgumentError, "value must be a Hash" if prefix.nil?
124: "#{prefix}=#{escape(value)}"
125: else
126: prefix
127: end
128: end
# File lib/rack/utils.rb, line 101
101: def build_query(params)
102: params.map { |k, v|
103: if v.class == Array
104: build_query(v.map { |x| [k, x] })
105: else
106: "#{escape(k)}=#{escape(v)}"
107: end
108: }.join("&")
109: end
# File lib/rack/utils.rb, line 238
238: def bytesize(string)
239: string.bytesize
240: end
# File lib/rack/utils.rb, line 242
242: def bytesize(string)
243: string.size
244: end
Performs URI escaping so that you can construct proper query strings faster. Use this rather than the cgi.rb version since it’s faster. (Stolen from Camping).
# File lib/rack/utils.rb, line 15
15: def escape(s)
16: s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) {
17: '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
18: }.tr(' ', '+')
19: end
Escape ampersands, brackets and quotes to their HTML/XML entities.
# File lib/rack/utils.rb, line 141
141: def escape_html(string)
142: string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
143: end
# File lib/rack/utils.rb, line 69
69: def normalize_params(params, name, v = nil)
70: name =~ %(\A[\[\]]*([^\[\]]+)\]*)
71: k = $1 || ''
72: after = $' || ''
73:
74: return if k.empty?
75:
76: if after == ""
77: params[k] = v
78: elsif after == "[]"
79: params[k] ||= []
80: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
81: params[k] << v
82: elsif after =~ %(^\[\]\[([^\[\]]+)\]$) || after =~ %(^\[\](.+)$)
83: child_key = $1
84: params[k] ||= []
85: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
86: if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
87: normalize_params(params[k].last, child_key, v)
88: else
89: params[k] << normalize_params({}, child_key, v)
90: end
91: else
92: params[k] ||= {}
93: raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
94: params[k] = normalize_params(params[k], after, v)
95: end
96:
97: return params
98: end
# File lib/rack/utils.rb, line 57
57: def parse_nested_query(qs, d = nil)
58: params = {}
59:
60: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
61: k, v = unescape(p).split('=', 2)
62: normalize_params(params, k, v)
63: end
64:
65: return params
66: end
Stolen from Mongrel, with some small modifications: Parses a query string by breaking it up at the ’&’ and ’;’ characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to ’&;’).
# File lib/rack/utils.rb, line 37
37: def parse_query(qs, d = nil)
38: params = {}
39:
40: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
41: k, v = p.split('=', 2).map { |x| unescape(x) }
42: if cur = params[k]
43: if cur.class == Array
44: params[k] << v
45: else
46: params[k] = [cur, v]
47: end
48: else
49: params[k] = v
50: end
51: end
52:
53: return params
54: end
Modified version of stdlib time.rb Time#rfc2822 to use ’%d-%b-%Y’ instead of ’% %b %Y’. It assumes that the time is in GMT to comply to the RFC 2109.
NOTE: I’m not sure the RFC says it requires GMT, but is ambigous enough that I’m certain someone implemented only that option. Do not use %a and %b from Time.strptime, it would use localized names for weekday and month.
# File lib/rack/utils.rb, line 257
257: def rfc2822(time)
258: wday = Time::RFC2822_DAY_NAME[time.wday]
259: mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
260: time.strftime("#{wday}, %d-#{mon}-%Y %T GMT")
261: end
# File lib/rack/utils.rb, line 146
146: def select_best_encoding(available_encodings, accept_encoding)
147: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
148:
149: expanded_accept_encoding =
150: accept_encoding.map { |m, q|
151: if m == "*"
152: (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
153: else
154: [[m, q]]
155: end
156: }.inject([]) { |mem, list|
157: mem + list
158: }
159:
160: encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
161:
162: unless encoding_candidates.include?("identity")
163: encoding_candidates.push("identity")
164: end
165:
166: expanded_accept_encoding.find_all { |m, q|
167: q == 0.0
168: }.each { |m, _|
169: encoding_candidates.delete(m)
170: }
171:
172: return (encoding_candidates & available_encodings)[0]
173: end
# File lib/rack/utils.rb, line 112
112: def build_nested_query(value, prefix = nil)
113: case value
114: when Array
115: value.map { |v|
116: build_nested_query(v, "#{prefix}[]")
117: }.join("&")
118: when Hash
119: value.map { |k, v|
120: build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
121: }.join("&")
122: when String
123: raise ArgumentError, "value must be a Hash" if prefix.nil?
124: "#{prefix}=#{escape(value)}"
125: else
126: prefix
127: end
128: end
# File lib/rack/utils.rb, line 101
101: def build_query(params)
102: params.map { |k, v|
103: if v.class == Array
104: build_query(v.map { |x| [k, x] })
105: else
106: "#{escape(k)}=#{escape(v)}"
107: end
108: }.join("&")
109: end
# File lib/rack/utils.rb, line 238
238: def bytesize(string)
239: string.bytesize
240: end
# File lib/rack/utils.rb, line 242
242: def bytesize(string)
243: string.size
244: end
Performs URI escaping so that you can construct proper query strings faster. Use this rather than the cgi.rb version since it’s faster. (Stolen from Camping).
# File lib/rack/utils.rb, line 15
15: def escape(s)
16: s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) {
17: '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
18: }.tr(' ', '+')
19: end
Escape ampersands, brackets and quotes to their HTML/XML entities.
# File lib/rack/utils.rb, line 141
141: def escape_html(string)
142: string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
143: end
# File lib/rack/utils.rb, line 69
69: def normalize_params(params, name, v = nil)
70: name =~ %(\A[\[\]]*([^\[\]]+)\]*)
71: k = $1 || ''
72: after = $' || ''
73:
74: return if k.empty?
75:
76: if after == ""
77: params[k] = v
78: elsif after == "[]"
79: params[k] ||= []
80: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
81: params[k] << v
82: elsif after =~ %(^\[\]\[([^\[\]]+)\]$) || after =~ %(^\[\](.+)$)
83: child_key = $1
84: params[k] ||= []
85: raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
86: if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
87: normalize_params(params[k].last, child_key, v)
88: else
89: params[k] << normalize_params({}, child_key, v)
90: end
91: else
92: params[k] ||= {}
93: raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
94: params[k] = normalize_params(params[k], after, v)
95: end
96:
97: return params
98: end
# File lib/rack/utils.rb, line 57
57: def parse_nested_query(qs, d = nil)
58: params = {}
59:
60: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
61: k, v = unescape(p).split('=', 2)
62: normalize_params(params, k, v)
63: end
64:
65: return params
66: end
Stolen from Mongrel, with some small modifications: Parses a query string by breaking it up at the ’&’ and ’;’ characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to ’&;’).
# File lib/rack/utils.rb, line 37
37: def parse_query(qs, d = nil)
38: params = {}
39:
40: (qs || '').split(d ? /[#{d}] */ : DEFAULT_SEP).each do |p|
41: k, v = p.split('=', 2).map { |x| unescape(x) }
42: if cur = params[k]
43: if cur.class == Array
44: params[k] << v
45: else
46: params[k] = [cur, v]
47: end
48: else
49: params[k] = v
50: end
51: end
52:
53: return params
54: end
Modified version of stdlib time.rb Time#rfc2822 to use ’%d-%b-%Y’ instead of ’% %b %Y’. It assumes that the time is in GMT to comply to the RFC 2109.
NOTE: I’m not sure the RFC says it requires GMT, but is ambigous enough that I’m certain someone implemented only that option. Do not use %a and %b from Time.strptime, it would use localized names for weekday and month.
# File lib/rack/utils.rb, line 257
257: def rfc2822(time)
258: wday = Time::RFC2822_DAY_NAME[time.wday]
259: mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
260: time.strftime("#{wday}, %d-#{mon}-%Y %T GMT")
261: end
# File lib/rack/utils.rb, line 146
146: def select_best_encoding(available_encodings, accept_encoding)
147: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
148:
149: expanded_accept_encoding =
150: accept_encoding.map { |m, q|
151: if m == "*"
152: (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
153: else
154: [[m, q]]
155: end
156: }.inject([]) { |mem, list|
157: mem + list
158: }
159:
160: encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
161:
162: unless encoding_candidates.include?("identity")
163: encoding_candidates.push("identity")
164: end
165:
166: expanded_accept_encoding.find_all { |m, q|
167: q == 0.0
168: }.each { |m, _|
169: encoding_candidates.delete(m)
170: }
171:
172: return (encoding_candidates & available_encodings)[0]
173: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.