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.
Files changed (70) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/History.rdoc +4 -0
  4. data/Manifest.txt +66 -0
  5. data/README.rdoc +169 -0
  6. data/Rakefile +77 -0
  7. data/bin/m2sh.rb +600 -0
  8. data/data/mongrel2/bootstrap.html +25 -0
  9. data/data/mongrel2/config.sql +84 -0
  10. data/data/mongrel2/mimetypes.sql +855 -0
  11. data/examples/README.txt +6 -0
  12. data/examples/config.rb +54 -0
  13. data/examples/helloworld-handler.rb +31 -0
  14. data/examples/request-dumper.rb +45 -0
  15. data/examples/request-dumper.tmpl +71 -0
  16. data/examples/run +17 -0
  17. data/lib/mongrel2.rb +62 -0
  18. data/lib/mongrel2/config.rb +212 -0
  19. data/lib/mongrel2/config/directory.rb +78 -0
  20. data/lib/mongrel2/config/dsl.rb +206 -0
  21. data/lib/mongrel2/config/handler.rb +124 -0
  22. data/lib/mongrel2/config/host.rb +88 -0
  23. data/lib/mongrel2/config/log.rb +48 -0
  24. data/lib/mongrel2/config/mimetype.rb +15 -0
  25. data/lib/mongrel2/config/proxy.rb +15 -0
  26. data/lib/mongrel2/config/route.rb +51 -0
  27. data/lib/mongrel2/config/server.rb +58 -0
  28. data/lib/mongrel2/config/setting.rb +15 -0
  29. data/lib/mongrel2/config/statistic.rb +23 -0
  30. data/lib/mongrel2/connection.rb +212 -0
  31. data/lib/mongrel2/constants.rb +159 -0
  32. data/lib/mongrel2/control.rb +165 -0
  33. data/lib/mongrel2/exceptions.rb +59 -0
  34. data/lib/mongrel2/handler.rb +309 -0
  35. data/lib/mongrel2/httprequest.rb +51 -0
  36. data/lib/mongrel2/httpresponse.rb +187 -0
  37. data/lib/mongrel2/jsonrequest.rb +43 -0
  38. data/lib/mongrel2/logging.rb +241 -0
  39. data/lib/mongrel2/mixins.rb +143 -0
  40. data/lib/mongrel2/request.rb +148 -0
  41. data/lib/mongrel2/response.rb +74 -0
  42. data/lib/mongrel2/table.rb +216 -0
  43. data/lib/mongrel2/xmlrequest.rb +36 -0
  44. data/spec/lib/constants.rb +237 -0
  45. data/spec/lib/helpers.rb +246 -0
  46. data/spec/lib/matchers.rb +50 -0
  47. data/spec/mongrel2/config/directory_spec.rb +91 -0
  48. data/spec/mongrel2/config/dsl_spec.rb +218 -0
  49. data/spec/mongrel2/config/handler_spec.rb +118 -0
  50. data/spec/mongrel2/config/host_spec.rb +30 -0
  51. data/spec/mongrel2/config/log_spec.rb +95 -0
  52. data/spec/mongrel2/config/proxy_spec.rb +30 -0
  53. data/spec/mongrel2/config/route_spec.rb +83 -0
  54. data/spec/mongrel2/config/server_spec.rb +84 -0
  55. data/spec/mongrel2/config/setting_spec.rb +30 -0
  56. data/spec/mongrel2/config/statistic_spec.rb +30 -0
  57. data/spec/mongrel2/config_spec.rb +111 -0
  58. data/spec/mongrel2/connection_spec.rb +172 -0
  59. data/spec/mongrel2/constants_spec.rb +32 -0
  60. data/spec/mongrel2/control_spec.rb +192 -0
  61. data/spec/mongrel2/handler_spec.rb +261 -0
  62. data/spec/mongrel2/httpresponse_spec.rb +232 -0
  63. data/spec/mongrel2/logging_spec.rb +76 -0
  64. data/spec/mongrel2/mixins_spec.rb +62 -0
  65. data/spec/mongrel2/request_spec.rb +157 -0
  66. data/spec/mongrel2/response_spec.rb +81 -0
  67. data/spec/mongrel2/table_spec.rb +176 -0
  68. data/spec/mongrel2_spec.rb +34 -0
  69. metadata +294 -0
  70. 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
+