mongrel2 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|