mongrel2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +66 -0
- data/README.rdoc +169 -0
- data/Rakefile +77 -0
- data/bin/m2sh.rb +600 -0
- data/data/mongrel2/bootstrap.html +25 -0
- data/data/mongrel2/config.sql +84 -0
- data/data/mongrel2/mimetypes.sql +855 -0
- data/examples/README.txt +6 -0
- data/examples/config.rb +54 -0
- data/examples/helloworld-handler.rb +31 -0
- data/examples/request-dumper.rb +45 -0
- data/examples/request-dumper.tmpl +71 -0
- data/examples/run +17 -0
- data/lib/mongrel2.rb +62 -0
- data/lib/mongrel2/config.rb +212 -0
- data/lib/mongrel2/config/directory.rb +78 -0
- data/lib/mongrel2/config/dsl.rb +206 -0
- data/lib/mongrel2/config/handler.rb +124 -0
- data/lib/mongrel2/config/host.rb +88 -0
- data/lib/mongrel2/config/log.rb +48 -0
- data/lib/mongrel2/config/mimetype.rb +15 -0
- data/lib/mongrel2/config/proxy.rb +15 -0
- data/lib/mongrel2/config/route.rb +51 -0
- data/lib/mongrel2/config/server.rb +58 -0
- data/lib/mongrel2/config/setting.rb +15 -0
- data/lib/mongrel2/config/statistic.rb +23 -0
- data/lib/mongrel2/connection.rb +212 -0
- data/lib/mongrel2/constants.rb +159 -0
- data/lib/mongrel2/control.rb +165 -0
- data/lib/mongrel2/exceptions.rb +59 -0
- data/lib/mongrel2/handler.rb +309 -0
- data/lib/mongrel2/httprequest.rb +51 -0
- data/lib/mongrel2/httpresponse.rb +187 -0
- data/lib/mongrel2/jsonrequest.rb +43 -0
- data/lib/mongrel2/logging.rb +241 -0
- data/lib/mongrel2/mixins.rb +143 -0
- data/lib/mongrel2/request.rb +148 -0
- data/lib/mongrel2/response.rb +74 -0
- data/lib/mongrel2/table.rb +216 -0
- data/lib/mongrel2/xmlrequest.rb +36 -0
- data/spec/lib/constants.rb +237 -0
- data/spec/lib/helpers.rb +246 -0
- data/spec/lib/matchers.rb +50 -0
- data/spec/mongrel2/config/directory_spec.rb +91 -0
- data/spec/mongrel2/config/dsl_spec.rb +218 -0
- data/spec/mongrel2/config/handler_spec.rb +118 -0
- data/spec/mongrel2/config/host_spec.rb +30 -0
- data/spec/mongrel2/config/log_spec.rb +95 -0
- data/spec/mongrel2/config/proxy_spec.rb +30 -0
- data/spec/mongrel2/config/route_spec.rb +83 -0
- data/spec/mongrel2/config/server_spec.rb +84 -0
- data/spec/mongrel2/config/setting_spec.rb +30 -0
- data/spec/mongrel2/config/statistic_spec.rb +30 -0
- data/spec/mongrel2/config_spec.rb +111 -0
- data/spec/mongrel2/connection_spec.rb +172 -0
- data/spec/mongrel2/constants_spec.rb +32 -0
- data/spec/mongrel2/control_spec.rb +192 -0
- data/spec/mongrel2/handler_spec.rb +261 -0
- data/spec/mongrel2/httpresponse_spec.rb +232 -0
- data/spec/mongrel2/logging_spec.rb +76 -0
- data/spec/mongrel2/mixins_spec.rb +62 -0
- data/spec/mongrel2/request_spec.rb +157 -0
- data/spec/mongrel2/response_spec.rb +81 -0
- data/spec/mongrel2/table_spec.rb +176 -0
- data/spec/mongrel2_spec.rb +34 -0
- metadata +294 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
6
|
+
require 'mongrel2/constants'
|
7
|
+
|
8
|
+
|
9
|
+
module Mongrel2
|
10
|
+
|
11
|
+
# Add logging to a Mongrel2 class. Including classes get #log and #log_debug methods.
|
12
|
+
module Loggable
|
13
|
+
|
14
|
+
# A logging proxy class that wraps calls to the logger into calls that include
|
15
|
+
# the name of the calling class.
|
16
|
+
class ClassNameProxy
|
17
|
+
|
18
|
+
### Create a new proxy for the given +klass+.
|
19
|
+
def initialize( klass, force_debug=false )
|
20
|
+
@classname = klass.name
|
21
|
+
@force_debug = force_debug
|
22
|
+
end
|
23
|
+
|
24
|
+
### Delegate debug messages to the global logger with the appropriate class name.
|
25
|
+
def debug( msg=nil, &block )
|
26
|
+
Mongrel2.logger.add( Logger::DEBUG, msg, @classname, &block )
|
27
|
+
end
|
28
|
+
|
29
|
+
### Delegate info messages to the global logger with the appropriate class name.
|
30
|
+
def info( msg=nil, &block )
|
31
|
+
return self.debug( msg, &block ) if @force_debug
|
32
|
+
Mongrel2.logger.add( Logger::INFO, msg, @classname, &block )
|
33
|
+
end
|
34
|
+
|
35
|
+
### Delegate warn messages to the global logger with the appropriate class name.
|
36
|
+
def warn( msg=nil, &block )
|
37
|
+
return self.debug( msg, &block ) if @force_debug
|
38
|
+
Mongrel2.logger.add( Logger::WARN, msg, @classname, &block )
|
39
|
+
end
|
40
|
+
|
41
|
+
### Delegate error messages to the global logger with the appropriate class name.
|
42
|
+
def error( msg=nil, &block )
|
43
|
+
return self.debug( msg, &block ) if @force_debug
|
44
|
+
Mongrel2.logger.add( Logger::ERROR, msg, @classname, &block )
|
45
|
+
end
|
46
|
+
|
47
|
+
### Delegate fatal messages to the global logger with the appropriate class name.
|
48
|
+
def fatal( msg=nil, &block )
|
49
|
+
Mongrel2.logger.add( Logger::FATAL, msg, @classname, &block )
|
50
|
+
end
|
51
|
+
|
52
|
+
end # ClassNameProxy
|
53
|
+
|
54
|
+
#########
|
55
|
+
protected
|
56
|
+
#########
|
57
|
+
|
58
|
+
### Copy constructor -- clear the original's log proxy.
|
59
|
+
def initialize_copy( original )
|
60
|
+
@log_proxy = @log_debug_proxy = nil
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
### Return the proxied logger.
|
65
|
+
def log
|
66
|
+
@log_proxy ||= ClassNameProxy.new( self.class )
|
67
|
+
end
|
68
|
+
|
69
|
+
### Return a proxied "debug" logger that ignores other level specification.
|
70
|
+
def log_debug
|
71
|
+
@log_debug_proxy ||= ClassNameProxy.new( self.class, true )
|
72
|
+
end
|
73
|
+
|
74
|
+
end # module Loggable
|
75
|
+
|
76
|
+
# A collection of ANSI color utility functions
|
77
|
+
module ANSIColorUtilities
|
78
|
+
|
79
|
+
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
|
80
|
+
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
|
81
|
+
ANSI_ATTRIBUTES = {
|
82
|
+
'clear' => 0,
|
83
|
+
'reset' => 0,
|
84
|
+
'bold' => 1,
|
85
|
+
'dark' => 2,
|
86
|
+
'underline' => 4,
|
87
|
+
'underscore' => 4,
|
88
|
+
'blink' => 5,
|
89
|
+
'reverse' => 7,
|
90
|
+
'concealed' => 8,
|
91
|
+
|
92
|
+
'black' => 30, 'on_black' => 40,
|
93
|
+
'red' => 31, 'on_red' => 41,
|
94
|
+
'green' => 32, 'on_green' => 42,
|
95
|
+
'yellow' => 33, 'on_yellow' => 43,
|
96
|
+
'blue' => 34, 'on_blue' => 44,
|
97
|
+
'magenta' => 35, 'on_magenta' => 45,
|
98
|
+
'cyan' => 36, 'on_cyan' => 46,
|
99
|
+
'white' => 37, 'on_white' => 47
|
100
|
+
}
|
101
|
+
|
102
|
+
###############
|
103
|
+
module_function
|
104
|
+
###############
|
105
|
+
|
106
|
+
### Create a string that contains the ANSI codes specified and return it
|
107
|
+
def ansi_code( *attributes )
|
108
|
+
attributes.flatten!
|
109
|
+
attributes.collect! {|at| at.to_s }
|
110
|
+
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
|
111
|
+
attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
|
112
|
+
|
113
|
+
if attributes.empty?
|
114
|
+
return ''
|
115
|
+
else
|
116
|
+
return "\e[%sm" % attributes
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
### Colorize the given +string+ with the specified +attributes+ and return it, handling
|
122
|
+
### line-endings, color reset, etc.
|
123
|
+
def colorize( *args )
|
124
|
+
string = ''
|
125
|
+
|
126
|
+
if block_given?
|
127
|
+
string = yield
|
128
|
+
else
|
129
|
+
string = args.shift
|
130
|
+
end
|
131
|
+
|
132
|
+
ending = string[/(\s)$/] || ''
|
133
|
+
string = string.rstrip
|
134
|
+
|
135
|
+
return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
|
136
|
+
end
|
137
|
+
|
138
|
+
end # module ANSIColorUtilities
|
139
|
+
|
140
|
+
end # module Mongrel2
|
141
|
+
|
142
|
+
# vim: set nosta noet ts=4 sw=4:
|
143
|
+
|
@@ -0,0 +1,148 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'tnetstring'
|
4
|
+
require 'yajl'
|
5
|
+
|
6
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
7
|
+
require 'mongrel2/mixins'
|
8
|
+
require 'mongrel2/table'
|
9
|
+
|
10
|
+
|
11
|
+
# The Mongrel2 Request base class. Derivatives of this class represent a request from
|
12
|
+
# a Mongrel2 server.
|
13
|
+
class Mongrel2::Request
|
14
|
+
include Mongrel2::Loggable
|
15
|
+
|
16
|
+
|
17
|
+
# METHOD header -> request class mapping
|
18
|
+
@request_types = Hash.new {|h,k| h[k] = Mongrel2::Request }
|
19
|
+
class << self; attr_reader :request_types; end
|
20
|
+
|
21
|
+
|
22
|
+
### Register the specified +subclass+ as the class to instantiate when the +METHOD+
|
23
|
+
### header is one of the specified +req_methods+. This method exists for frameworks
|
24
|
+
### which wish to provide their own Request types.
|
25
|
+
###
|
26
|
+
### For example, if your framework has a JSONRequest class that inherits from
|
27
|
+
### Mongrel2::JSONRequest, and you want it to be returned from Mongrel2::Request.parse
|
28
|
+
### for METHOD=JSON requests:
|
29
|
+
###
|
30
|
+
### class MyFramework::JSONRequest < Mongrel2::JSONRequest
|
31
|
+
### register_request_type self, 'JSON'
|
32
|
+
###
|
33
|
+
### # Override #initialize to do any stuff specific to your
|
34
|
+
### # request type, but you'll likely want to super() to
|
35
|
+
### # Mongrel2::JSONRequest.
|
36
|
+
### def initialize( * )
|
37
|
+
### super
|
38
|
+
### # Do some other stuff
|
39
|
+
### end
|
40
|
+
###
|
41
|
+
### end # class MyFramework::JSONRequest
|
42
|
+
###
|
43
|
+
### If you wish one of your subclasses to be used instead of Mongrel2::Request
|
44
|
+
### for the default request class, register it with a METHOD of :__default.
|
45
|
+
def self::register_request_type( subclass, *req_methods )
|
46
|
+
req_methods.each do |methname|
|
47
|
+
if methname == :__default
|
48
|
+
# Clear cached lookups
|
49
|
+
Mongrel2.log.info "Registering %p as the default request type." % [ subclass ]
|
50
|
+
Mongrel2::Request.request_types.delete_if {|_, klass| klass == Mongrel2::Request }
|
51
|
+
Mongrel2::Request.request_types.default_proc = lambda {|h,k| h[k] = subclass }
|
52
|
+
else
|
53
|
+
Mongrel2.log.info "Registering %p for the %p method." % [ subclass, methname ]
|
54
|
+
Mongrel2::Request.request_types[ methname.to_sym ] = subclass
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
### Return the Mongrel2::Request class registered for the request method +methname+.
|
61
|
+
def self::subclass_for_method( methname )
|
62
|
+
return Mongrel2::Request.request_types[ methname.to_sym ]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
### Parse the given +raw_request+ from a Mongrel2 server and return an appropriate
|
67
|
+
### request object.
|
68
|
+
def self::parse( raw_request )
|
69
|
+
sender, conn_id, path, rest = raw_request.split( ' ', 4 )
|
70
|
+
Mongrel2.log.debug "Parsing request for %p from %s:%s (rest: %p)" %
|
71
|
+
[ path, sender, conn_id, rest ]
|
72
|
+
|
73
|
+
# Extract the headers and the body, ignore the rest
|
74
|
+
headers, rest = TNetstring.parse( rest )
|
75
|
+
body, _ = TNetstring.parse( rest )
|
76
|
+
|
77
|
+
# Headers will be a JSON String when not using the TNetString protocol
|
78
|
+
if headers.is_a?( String )
|
79
|
+
Mongrel2.log.debug " parsing old-style headers"
|
80
|
+
headers = Yajl::Parser.parse( headers )
|
81
|
+
end
|
82
|
+
|
83
|
+
# This isn't supposed to happen, but guard against it anyway
|
84
|
+
headers['METHOD'] =~ /^(\w+)$/ or
|
85
|
+
raise Mongrel2::UnhandledMethodError, headers['METHOD']
|
86
|
+
req_method = $1.untaint.to_sym
|
87
|
+
Mongrel2.log.debug "Request method is: %p" % [ req_method ]
|
88
|
+
concrete_class = self.subclass_for_method( req_method )
|
89
|
+
|
90
|
+
return concrete_class.new( sender, conn_id, path, headers, body, raw_request )
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
### Create a new Request object with the given +sender_id+, +conn_id+, +path+, +headers+,
|
96
|
+
### and +body+. The optional +nil+ is for the raw request content, which can be useful
|
97
|
+
### later for debugging.
|
98
|
+
def initialize( sender_id, conn_id, path, headers, body, raw=nil )
|
99
|
+
@sender_id = sender_id
|
100
|
+
@conn_id = Integer( conn_id )
|
101
|
+
@path = path
|
102
|
+
@headers = Mongrel2::Table.new( headers )
|
103
|
+
@body = body
|
104
|
+
|
105
|
+
@raw = raw
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
######
|
110
|
+
public
|
111
|
+
######
|
112
|
+
|
113
|
+
# The UUID of the requesting mongrel server
|
114
|
+
attr_reader :sender_id
|
115
|
+
|
116
|
+
# The listener ID on the server
|
117
|
+
attr_reader :conn_id
|
118
|
+
|
119
|
+
# The path component of the requested URL in HTTP, or the equivalent
|
120
|
+
# for other request types
|
121
|
+
attr_reader :path
|
122
|
+
|
123
|
+
# The Mongrel2::Table object that contains the request headers
|
124
|
+
attr_reader :headers
|
125
|
+
|
126
|
+
# The request body data, if there is any, as a String
|
127
|
+
attr_reader :body
|
128
|
+
|
129
|
+
# The raw request content, if the request was parsed from mongrel2
|
130
|
+
attr_reader :raw
|
131
|
+
|
132
|
+
|
133
|
+
### Create a Mongrel2::Response that will respond to the same server/connection as
|
134
|
+
### the receiver. If you wish your specialized Request class to have a corresponding
|
135
|
+
### response type, you can override this method to achieve that.
|
136
|
+
def response
|
137
|
+
return Mongrel2::Response.from_request( self )
|
138
|
+
end
|
139
|
+
|
140
|
+
### Return +true+ if the request is a special 'disconnect' notification from Mongrel2.
|
141
|
+
def is_disconnect?
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
end # class Mongrel2::Request
|
146
|
+
|
147
|
+
# vim: set nosta noet ts=4 sw=4:
|
148
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'tnetstring'
|
4
|
+
require 'yajl'
|
5
|
+
|
6
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
7
|
+
require 'mongrel2/mixins'
|
8
|
+
|
9
|
+
|
10
|
+
# The Mongrel2 Response base class.
|
11
|
+
class Mongrel2::Response
|
12
|
+
include Mongrel2::Loggable
|
13
|
+
|
14
|
+
# The default number of bytes of the response body to send to the mongrel2
|
15
|
+
# server at a time.
|
16
|
+
DEFAULT_CHUNKSIZE = 1024 * 512
|
17
|
+
|
18
|
+
|
19
|
+
### Create a response to the specified +request+ and return it.
|
20
|
+
def self::from_request( request )
|
21
|
+
Mongrel2.log.debug "Creating a %p to request %p" % [ self, request ]
|
22
|
+
return new( request.sender_id, request.conn_id )
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
### Create a new Response object for the specified +sender_id+, +conn_id+, and +body+.
|
27
|
+
def initialize( sender_id, conn_id, body='' )
|
28
|
+
@sender_id = sender_id
|
29
|
+
@conn_id = conn_id
|
30
|
+
@body = body
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
######
|
35
|
+
public
|
36
|
+
######
|
37
|
+
|
38
|
+
# The response's UUID; this corresponds to the mongrel2 server the response will
|
39
|
+
# be routed to by the Connection.
|
40
|
+
attr_accessor :sender_id
|
41
|
+
|
42
|
+
# The response's connection ID; this corresponds to the identifier of the connection
|
43
|
+
# the response will be routed to by the mongrel2 server
|
44
|
+
attr_accessor :conn_id
|
45
|
+
|
46
|
+
# The body of the response
|
47
|
+
attr_accessor :body
|
48
|
+
|
49
|
+
|
50
|
+
### Append the given +object+ to the response body. Returns the response for
|
51
|
+
### chaining.
|
52
|
+
def <<( object )
|
53
|
+
self.body << object
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
### Write the given +objects+ to the response body, calling #to_s on each one.
|
59
|
+
def puts( *objects )
|
60
|
+
objects.each do |obj|
|
61
|
+
self << obj.to_s.chomp << $/
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
### Stringify the response, which just returns its body.
|
67
|
+
def to_s
|
68
|
+
return self.body
|
69
|
+
end
|
70
|
+
|
71
|
+
end # class Mongrel2::Response
|
72
|
+
|
73
|
+
# vim: set nosta noet ts=4 sw=4:
|
74
|
+
|
@@ -0,0 +1,216 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'mongrel2' unless defined?( Mongrel2 )
|
6
|
+
require 'mongrel2/mixins'
|
7
|
+
|
8
|
+
|
9
|
+
# The Mongrel2 Table class. Instances of this class provide a case-insensitive hash-like
|
10
|
+
# object that can store multiple values per key.
|
11
|
+
#
|
12
|
+
# headers = Mongrel2::Table.new
|
13
|
+
# headers['User-Agent'] = 'PornBrowser 1.1.5'
|
14
|
+
# headers['user-agent'] # => 'PornBrowser 1.1.5'
|
15
|
+
# headers[:user_agent] # => 'PornBrowser 1.1.5'
|
16
|
+
# headers.user_agent # => 'PornBrowser 1.1.5'
|
17
|
+
#
|
18
|
+
# == Author/s
|
19
|
+
#
|
20
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
21
|
+
# * Mahlon E. Smith <mahlon@martini.nu>
|
22
|
+
#
|
23
|
+
class Mongrel2::Table
|
24
|
+
extend Forwardable
|
25
|
+
include Mongrel2::Loggable
|
26
|
+
|
27
|
+
# Methods that understand case-insensitive keys
|
28
|
+
KEYED_METHODS = [ :"[]", :"[]=", :delete, :fetch, :has_key?, :include?, :member?, :store ]
|
29
|
+
|
30
|
+
|
31
|
+
### Auto-generate methods which call the given +delegate+ after normalizing
|
32
|
+
### their first argument via +normalize_key+
|
33
|
+
def self::def_normalized_delegators( delegate, *syms )
|
34
|
+
syms.each do |methodname|
|
35
|
+
define_method( methodname ) do |key, *args|
|
36
|
+
nkey = normalize_key( key )
|
37
|
+
instance_variable_get( delegate ).
|
38
|
+
__send__( methodname, nkey, *args )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
### Create a new Mongrel2::Table using the given +hash+ for initial
|
45
|
+
### values.
|
46
|
+
def initialize( initial_values={} )
|
47
|
+
@hash = {}
|
48
|
+
initial_values.each {|k,v| self.append(k => v) }
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
### Make sure the inner Hash is unique on duplications.
|
53
|
+
def initialize_copy( orig_table ) # :nodoc:
|
54
|
+
@hash = orig_table.to_hash
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
######
|
59
|
+
public
|
60
|
+
######
|
61
|
+
|
62
|
+
# Delegate a handful of methods to the underlying Hash after normalizing
|
63
|
+
# the key.
|
64
|
+
def_normalized_delegators :@hash, *KEYED_METHODS
|
65
|
+
|
66
|
+
|
67
|
+
# Delegate some methods to the underlying Hash
|
68
|
+
begin
|
69
|
+
unoverridden_methods = Hash.instance_methods(false).collect {|mname| mname.to_sym }
|
70
|
+
def_delegators :@hash, *( unoverridden_methods - KEYED_METHODS )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
### Append the keys and values in the given +hash+ to the table, transforming
|
75
|
+
### each value into an array if there was an existing value for the same key.
|
76
|
+
def append( hash )
|
77
|
+
self.merge!( hash ) do |key,origval,newval|
|
78
|
+
[ origval, newval ].flatten
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
### Return the Table as RFC822 headers in a String
|
84
|
+
def to_s
|
85
|
+
@hash.collect do |header,value|
|
86
|
+
Array( value ).collect {|val|
|
87
|
+
"%s: %s" % [
|
88
|
+
normalize_header( header ),
|
89
|
+
val
|
90
|
+
]
|
91
|
+
}
|
92
|
+
end.flatten.sort.join( "\r\n" ) + "\r\n"
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
### Enumerator for iterating over the table contents, yielding each as an RFC822 header.
|
97
|
+
def each_header( &block )
|
98
|
+
enum = Enumerator.new do |yielder|
|
99
|
+
@hash.each do |header, value|
|
100
|
+
Array( value ).each do |val|
|
101
|
+
yielder.yield( normalize_header(header), val.to_s )
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if block
|
107
|
+
return enum.each( &block )
|
108
|
+
else
|
109
|
+
return enum
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
### Return the Table as a hash.
|
115
|
+
def to_h
|
116
|
+
@hash.dup
|
117
|
+
end
|
118
|
+
alias_method :to_hash, :to_h
|
119
|
+
|
120
|
+
|
121
|
+
### Merge +other_table+ into the receiver.
|
122
|
+
def merge!( other_table, &merge_callback )
|
123
|
+
nhash = normalize_hash( other_table.to_hash )
|
124
|
+
@hash.merge!( nhash, &merge_callback )
|
125
|
+
end
|
126
|
+
alias_method :update!, :merge!
|
127
|
+
|
128
|
+
|
129
|
+
### Return a new table which is the result of merging the receiver
|
130
|
+
### with +other_table+ in the same fashion as Hash#merge. If the optional
|
131
|
+
### +merge_callback+ block is provided, it is called whenever there is a
|
132
|
+
### key collision between the two.
|
133
|
+
def merge( other_table, &merge_callback ) # :yields: key, original_value, new_value
|
134
|
+
other = self.dup
|
135
|
+
other.merge!( other_table, &merge_callback )
|
136
|
+
return other
|
137
|
+
end
|
138
|
+
alias_method :update, :merge
|
139
|
+
|
140
|
+
|
141
|
+
### Return an array containing the values associated with the given
|
142
|
+
### keys.
|
143
|
+
def values_at( *keys )
|
144
|
+
@hash.values_at( *(keys.collect {|k| normalize_key(k)}) )
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
#########
|
149
|
+
protected
|
150
|
+
#########
|
151
|
+
|
152
|
+
### Proxy method: handle getting/setting headers via methods instead of the
|
153
|
+
### index operator.
|
154
|
+
def method_missing( sym, *args )
|
155
|
+
# work magic
|
156
|
+
return super unless sym.to_s =~ /^([a-z]\w+)(=)?$/
|
157
|
+
|
158
|
+
# If it's an assignment, the (=)? will have matched
|
159
|
+
key, assignment = $1, $2
|
160
|
+
|
161
|
+
method_body = nil
|
162
|
+
if assignment
|
163
|
+
method_body = self.make_setter( key )
|
164
|
+
else
|
165
|
+
method_body = self.make_getter( key )
|
166
|
+
end
|
167
|
+
|
168
|
+
self.class.send( :define_method, sym, &method_body )
|
169
|
+
return self.method( sym ).call( *args )
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
### Create a Proc that will act as a setter for the given key
|
174
|
+
def make_setter( key )
|
175
|
+
return Proc.new {|new_value| self[ key ] = new_value }
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
### Create a Proc that will act as a getter for the given key
|
180
|
+
def make_getter( key )
|
181
|
+
return Proc.new { self[key] }
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
#######
|
186
|
+
private
|
187
|
+
#######
|
188
|
+
|
189
|
+
### Return a copy of +hash+ with all of its keys normalized by #normalize_key.
|
190
|
+
def normalize_hash( hash )
|
191
|
+
hash = hash.dup
|
192
|
+
hash.keys.each do |key|
|
193
|
+
nkey = normalize_key( key )
|
194
|
+
hash[ nkey ] = hash.delete( key ) if key != nkey
|
195
|
+
end
|
196
|
+
|
197
|
+
return hash
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
### Normalize the given key to equivalence
|
202
|
+
def normalize_key( key )
|
203
|
+
key.to_s.downcase.gsub('-', '_').to_sym
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
### Return the given key as an RFC822-style header label
|
208
|
+
def normalize_header( key )
|
209
|
+
key.to_s.split( '_' ).collect {|part| part.capitalize }.join( '-' )
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
end # class Mongrel2::Table
|
214
|
+
|
215
|
+
# vim: set nosta noet ts=4 sw=4:
|
216
|
+
|