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.