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
data.tar.gz.sig ADDED
Binary file
data/.gemtest ADDED
File without changes
data/History.rdoc ADDED
@@ -0,0 +1,4 @@
1
+ == v0.0.1 [2011-09-12] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ Initial release.
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,66 @@
1
+ History.rdoc
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ bin/m2sh.rb
6
+ data/mongrel2/bootstrap.html
7
+ data/mongrel2/config.sql
8
+ data/mongrel2/mimetypes.sql
9
+ examples/README.txt
10
+ examples/config.rb
11
+ examples/helloworld-handler.rb
12
+ examples/request-dumper.rb
13
+ examples/request-dumper.tmpl
14
+ examples/run
15
+ lib/mongrel2.rb
16
+ lib/mongrel2/config.rb
17
+ lib/mongrel2/config/directory.rb
18
+ lib/mongrel2/config/dsl.rb
19
+ lib/mongrel2/config/handler.rb
20
+ lib/mongrel2/config/host.rb
21
+ lib/mongrel2/config/log.rb
22
+ lib/mongrel2/config/mimetype.rb
23
+ lib/mongrel2/config/proxy.rb
24
+ lib/mongrel2/config/route.rb
25
+ lib/mongrel2/config/server.rb
26
+ lib/mongrel2/config/setting.rb
27
+ lib/mongrel2/config/statistic.rb
28
+ lib/mongrel2/connection.rb
29
+ lib/mongrel2/constants.rb
30
+ lib/mongrel2/control.rb
31
+ lib/mongrel2/exceptions.rb
32
+ lib/mongrel2/handler.rb
33
+ lib/mongrel2/httprequest.rb
34
+ lib/mongrel2/httpresponse.rb
35
+ lib/mongrel2/jsonrequest.rb
36
+ lib/mongrel2/logging.rb
37
+ lib/mongrel2/mixins.rb
38
+ lib/mongrel2/request.rb
39
+ lib/mongrel2/response.rb
40
+ lib/mongrel2/table.rb
41
+ lib/mongrel2/xmlrequest.rb
42
+ spec/lib/constants.rb
43
+ spec/lib/helpers.rb
44
+ spec/lib/matchers.rb
45
+ spec/mongrel2/config/directory_spec.rb
46
+ spec/mongrel2/config/dsl_spec.rb
47
+ spec/mongrel2/config/handler_spec.rb
48
+ spec/mongrel2/config/host_spec.rb
49
+ spec/mongrel2/config/log_spec.rb
50
+ spec/mongrel2/config/proxy_spec.rb
51
+ spec/mongrel2/config/route_spec.rb
52
+ spec/mongrel2/config/server_spec.rb
53
+ spec/mongrel2/config/setting_spec.rb
54
+ spec/mongrel2/config/statistic_spec.rb
55
+ spec/mongrel2/config_spec.rb
56
+ spec/mongrel2/connection_spec.rb
57
+ spec/mongrel2/constants_spec.rb
58
+ spec/mongrel2/control_spec.rb
59
+ spec/mongrel2/handler_spec.rb
60
+ spec/mongrel2/httpresponse_spec.rb
61
+ spec/mongrel2/logging_spec.rb
62
+ spec/mongrel2/mixins_spec.rb
63
+ spec/mongrel2/request_spec.rb
64
+ spec/mongrel2/response_spec.rb
65
+ spec/mongrel2/table_spec.rb
66
+ spec/mongrel2_spec.rb
data/README.rdoc ADDED
@@ -0,0 +1,169 @@
1
+
2
+ = Ruby-Mongrel2
3
+
4
+ * http://deveiate.org/projects/Ruby-Mongrel2/
5
+
6
+ == Description
7
+
8
+ A complete Ruby connector for Mongrel2[http://mongrel2.org/].
9
+
10
+ This library includes configuration-database ORM classes, a Ruby
11
+ implementation of the 'm2sh' tool, a configuration DSL for generating config
12
+ databases in pure Ruby, a Control port interface object, handler classes for creating applications or higher-level frameworks.
13
+
14
+ It differs from the original Mongrel2 Ruby library (m2r), and the
15
+ mongrel2-rack library in several ways:
16
+
17
+ * It uses the C extension for 0MQ (zmq) instead of the FFI one. If you
18
+ strongly prefer the FFI library, both of the other Mongrel2 libraries use
19
+ it, so you'll want to stick to one of them.
20
+
21
+ * It doesn't come with a Rack handler, or Rails examples, or anything fancy. I
22
+ intend to build my own webby framework bits around Mongrel2, and I thought
23
+ maybe someone else might want to as well. If you don't, well again, there
24
+ are two other libraries for you.
25
+
26
+ * It includes configuration stuff. I want to make tools that use the Mongrel2
27
+ config database, so I wrote config classes. Sequel::Model made it
28
+ stupid-easy. There's also a DSL for generating a config database, too,
29
+ mostly because I found it an interesting exercise, and I like the way it
30
+ looks.
31
+
32
+
33
+ == Installation
34
+
35
+ gem install mongrel2
36
+
37
+
38
+ This library uses Jeremy Hinegardner's 'amalgalite' library for the config ORM classes, but it will also fall back to using the sqlite3 library instead:
39
+
40
+ # Loading the sqlite3 library explicitly
41
+ $ rspec -rsqlite3 -cfp spec
42
+ >>> Using SQLite3 1.3.4 for DB access.
43
+ .....[...]
44
+
45
+ Finished in 5.53 seconds
46
+ 102 examples, 0 failures
47
+
48
+ # No -rsqlite3 means amalgalite loads first.
49
+ $ rspec -cfp spec
50
+ >>> Using Amalgalite 1.1.2 for DB access.
51
+ .....[...]
52
+
53
+ Finished in 3.67 seconds
54
+ 102 examples, 0 failures
55
+
56
+
57
+ == Usage
58
+
59
+ The library consists of three major parts: the Config ORM classes, the
60
+ Handler classes, and the Control class.
61
+
62
+ === Config ORM Classes
63
+
64
+ There's one class per table like with most ORMs, a Mongrel2::Config::DSL mixin
65
+ for adding the Ruby configuration DSL to your namespace, and the top-level
66
+ Mongrel2::Config class, which manages the database connection, installs the
67
+ schema, etc.
68
+
69
+ * Mongrel2::Config
70
+ * Mongrel2::Config::DSL
71
+ * Mongrel2::Config::Server
72
+ * Mongrel2::Config::Host
73
+ * Mongrel2::Config::Route
74
+ * Mongrel2::Config::Directory
75
+ * Mongrel2::Config::Proxy
76
+ * Mongrel2::Config::Handler
77
+ * Mongrel2::Config::Setting
78
+ * Mongrel2::Config::Mimetype
79
+ * Mongrel2::Config::Statistic
80
+ * Mongrel2::Config::Log
81
+
82
+
83
+ === Handler Classes
84
+
85
+ The main handler class is, unsurprisingly, Mongrel2::Handler. It uses a
86
+ Mongrel2::Connection object to talk to the server, wrapping the request data
87
+ up in a Mongrel2::Request object, and expecting a Mongrel2::Response in
88
+ response.
89
+
90
+ There are specialized Request classes for each of the kinds of requests
91
+ Mongrel2 sends:
92
+
93
+ * Mongrel2::HTTPRequest
94
+ * Mongrel2::JSONRequest
95
+ * Mongrel2::XMLRequest
96
+
97
+ These are all {overridable}[rdoc-ref:Mongrel2::Request.register_request_type]
98
+ if you should want a more-specialized class for one of them.
99
+
100
+ The Mongrel2::Handler class itself has documentation on how to write your own
101
+ handlers.
102
+
103
+
104
+ === The Control Class
105
+
106
+ The Mongrel2::Control class is an object interface to {the Mongrel2 control
107
+ port}[http://mongrel2.org/static/mongrel2-manual.html#x1-390003.8]. It can be
108
+ used to stop and restart the server, check its status, etc.
109
+
110
+
111
+ === Other Classes
112
+
113
+ There are a few other classes and modules, too:
114
+
115
+ [Mongrel2::Constants]
116
+ A collection of constants used throughout the rest of the library.
117
+ [Mongrel2::Loggable]
118
+ A mixin for adding logging methods to a class.
119
+ [Mongrel2::Table]
120
+ A case-insensitive hash-like object class that can store multiple values per
121
+ key, and serialize itself into RFC822-style headers. Used for
122
+ request[rdoc-ref:Mongrel2::Request#headers] and
123
+ response[rdoc-ref:Mongrel2::Response#headers] headers.
124
+
125
+
126
+ == Contributing
127
+
128
+ You can check out the current development source with Mercurial via its
129
+ {Bitbucket project}[https://bitbucket.org/ged/ruby-mongrel2]. Or if you
130
+ prefer Git, via {its Github mirror}[https://github.com/ged/ruby-mongrel2].
131
+
132
+ After checking out the source, run:
133
+
134
+ $ rake newb
135
+
136
+ This task will install any missing dependencies, run the tests/specs,
137
+ and generate the API documentation.
138
+
139
+
140
+ == License
141
+
142
+ Copyright (c) 2011, Michael Granger
143
+ All rights reserved.
144
+
145
+ Redistribution and use in source and binary forms, with or without
146
+ modification, are permitted provided that the following conditions are met:
147
+
148
+ * Redistributions of source code must retain the above copyright notice,
149
+ this list of conditions and the following disclaimer.
150
+
151
+ * Redistributions in binary form must reproduce the above copyright notice,
152
+ this list of conditions and the following disclaimer in the documentation
153
+ and/or other materials provided with the distribution.
154
+
155
+ * Neither the name of the author/s, nor the names of the project's
156
+ contributors may be used to endorse or promote products derived from this
157
+ software without specific prior written permission.
158
+
159
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
160
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
161
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
162
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
163
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
164
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
165
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
166
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
167
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
168
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
169
+
data/Rakefile ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env rake
2
+
3
+ begin
4
+ require 'hoe'
5
+ rescue LoadError
6
+ abort "This Rakefile requires 'hoe' (gem install hoe)"
7
+ end
8
+
9
+ # Work around borked RSpec support in this version
10
+ if Hoe::VERSION == '2.12.0'
11
+ warn "Ignore warnings about not having rspec; it's a bug in Hoe 2.12.0"
12
+ require 'rspec'
13
+ end
14
+
15
+ Hoe.plugin :mercurial
16
+ Hoe.plugin :signing
17
+
18
+ Hoe.plugins.delete :rubyforge
19
+
20
+ hoespec = Hoe.spec 'mongrel2' do
21
+ self.readme_file = 'README.rdoc'
22
+ self.history_file = 'History.rdoc'
23
+ self.extra_rdoc_files << 'README.rdoc' << 'History.rdoc'
24
+ self.spec_extras[:rdoc_options] = ['-t', 'Ruby-Mongrel2']
25
+
26
+ self.developer 'Michael Granger', 'ged@FaerieMUD.org'
27
+
28
+ self.dependency 'nokogiri', '~> 1.5'
29
+ self.dependency 'sequel', '~> 3.26'
30
+ self.dependency 'amalgalite', '~> 1.1'
31
+ self.dependency 'tnetstring', '~> 0.3'
32
+ self.dependency 'yajl-ruby', '~> 0.8'
33
+ self.dependency 'zmq', '~> 2.1.3.1'
34
+
35
+ self.dependency 'configurability', '~> 1.0', :developer
36
+ self.dependency 'rspec', '~> 2.4', :developer
37
+
38
+ self.spec_extras[:licenses] = ["BSD"]
39
+ self.require_ruby_version( '>= 1.9.2' )
40
+
41
+ self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
42
+ end
43
+
44
+ ENV['VERSION'] ||= hoespec.spec.version.to_s
45
+
46
+ # Ensure the specs pass before checking in
47
+ task 'hg:precheckin' => :spec
48
+
49
+ ### Make the ChangeLog update if the repo has changed since it was last built
50
+ file '.hg/branch'
51
+ file 'ChangeLog' => '.hg/branch' do |task|
52
+ $stderr.puts "Updating the changelog..."
53
+ abort "Can't create the ChangeLog without hoe-mercurial (gem install hoe-mercurial)" unless
54
+ defined?( MercurialHelpers )
55
+
56
+ content = MercurialHelpers.make_changelog()
57
+ File.open( task.name, 'w', 0644 ) do |fh|
58
+ fh.print( content )
59
+ end
60
+ end
61
+
62
+ # Rebuild the ChangeLog immediately before release
63
+ task :prerelease => 'ChangeLog'
64
+
65
+ if Rake::Task.task_defined?( '.gemtest' )
66
+ Rake::Task['.gemtest'].clear
67
+ task '.gemtest' do
68
+ $stderr.puts "Not including a .gemtest until I'm confident the test suite is idempotent."
69
+ end
70
+ end
71
+
72
+ desc "Build a coverage report"
73
+ task :coverage do
74
+ ENV["COVERAGE"] = 'yes'
75
+ Rake::Task[:spec].invoke
76
+ end
77
+
data/bin/m2sh.rb ADDED
@@ -0,0 +1,600 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pp'
4
+ require 'shellwords'
5
+ require 'tnetstring'
6
+
7
+ require 'trollop'
8
+ require 'highline'
9
+
10
+ # Have to do it this way to avoid the vendored 'sysexits' under OSX.
11
+ gem 'sysexits'
12
+ require 'sysexits'
13
+
14
+ require 'mongrel2'
15
+ require 'mongrel2/config'
16
+
17
+
18
+ # A tool for interacting with a Mongrel2 config database and server. This isn't
19
+ # quite a replacement for the real m2sh yet; here's what I have working so far:
20
+ #
21
+ # [√] load Load a config.
22
+ # [√] config Alias for load.
23
+ # [√] shell Starts an interactive shell.
24
+ # [√] access Prints the access log.
25
+ # [√] servers Lists the servers in a config database.
26
+ # [√] hosts Lists the hosts in a server.
27
+ # [√] routes Lists the routes in a host.
28
+ # [√] commit Adds a message to the log.
29
+ # [√] log Prints the commit log.
30
+ # [√] start Starts a server.
31
+ # [ ] stop Stops a server.
32
+ # [ ] reload Reloads a server.
33
+ # [ ] running Tells you what's running.
34
+ # [ ] control Connects to the control port.
35
+ # [√] version Prints the Mongrel2 and m2sh version.
36
+ # [√] help Get help, lists commands.
37
+ # [-] uuid Prints out a randomly generated UUID.
38
+ #
39
+ # I just use 'uuidgen' to generate uuids (which is all m2sh does, as
40
+ # well), so I don't plan to implement that. Everything else should
41
+ # be pretty easy.
42
+ #
43
+ class Mongrel2::M2SHCommand
44
+ extend ::Sysexits
45
+ include Sysexits,
46
+ Mongrel2::Loggable,
47
+ Mongrel2::Constants
48
+
49
+ # Make a HighLine color scheme
50
+ COLOR_SCHEME = HighLine::ColorScheme.new do |scheme|
51
+ scheme[:header] = [ :bold, :yellow ]
52
+ scheme[:subheader] = [ :bold, :white ]
53
+ scheme[:key] = [ :white ]
54
+ scheme[:value] = [ :bold, :white ]
55
+ scheme[:error] = [ :red ]
56
+ scheme[:warning] = [ :yellow ]
57
+ scheme[:message] = [ :reset ]
58
+ end
59
+
60
+
61
+ # Path to the default history file for 'shell' mode
62
+ HISTORY_FILE = Pathname( "~/.m2shrb.history" )
63
+
64
+ # Number of items to store in history by default
65
+ DEFAULT_HISTORY_SIZE = 100
66
+
67
+ # The prompt the 'shell' mode should show
68
+ PROMPT = 'mongrel2> '
69
+
70
+
71
+ # Class instance variables
72
+ @command_help = Hash.new {|h,k| h[k] = { :desc => nil, :usage => ''} }
73
+ @prompt = @option_parser = nil
74
+
75
+
76
+ ### Add a help string for the given +command+.
77
+ def self::help( command, helpstring=nil )
78
+ if helpstring
79
+ @command_help[ command.to_sym ][:desc] = helpstring
80
+ end
81
+
82
+ return @command_help[ command.to_sym ][:desc]
83
+ end
84
+
85
+
86
+ ### Add/fetch the +usagestring+ for +command+.
87
+ def self::usage( command, usagestring=nil )
88
+ if usagestring
89
+ prefix = usagestring[ /\A(\s+)/, 1 ]
90
+ usagestring.gsub!( /^#{prefix}/m, '' ) if prefix
91
+
92
+ @command_help[ command.to_sym ][:usage] = usagestring
93
+ end
94
+
95
+ return @command_help[ command.to_sym ][:usage]
96
+ end
97
+
98
+
99
+ ### Return the global Highline prompt object, creating it if necessary.
100
+ def self::prompt
101
+ unless @prompt
102
+ @prompt = HighLine.new
103
+ @prompt.wrap_at = @prompt.output_cols - 10
104
+ end
105
+
106
+ return @prompt
107
+ end
108
+
109
+
110
+ ### Run the utility with the given +args+.
111
+ def self::run( args )
112
+ HighLine.color_scheme = COLOR_SCHEME
113
+
114
+ oparser = self.make_option_parser
115
+ opts = Trollop.with_standard_exception_handling( oparser ) do
116
+ oparser.parse( args )
117
+ end
118
+
119
+ command = oparser.leftovers.shift
120
+ self.new( opts ).run( command, *oparser.leftovers )
121
+ exit :ok
122
+
123
+ rescue => err
124
+ Mongrel2.logger.fatal "Oops: %s: %s" % [ err.class.name, err.message ]
125
+ Mongrel2.logger.debug { ' ' + err.backtrace.join("\n ") }
126
+
127
+ exit :software_error
128
+ end
129
+
130
+
131
+ ### Return a String that describes the available commands, e.g., for the 'help'
132
+ ### command.
133
+ def self::make_command_table
134
+ commands = self.available_commands
135
+
136
+ # Build the command table
137
+ col1len = commands.map( &:length ).max
138
+ return commands.collect do |cmd|
139
+ helptext = self.help( cmd.to_sym ) or next # no help == invisible command
140
+ "%s %s" % [
141
+ self.prompt.color(cmd.rjust(col1len), :key),
142
+ self.prompt.color(helptext, :value)
143
+ ]
144
+ end.compact
145
+ end
146
+
147
+
148
+ ### Return an Array of the available commands.
149
+ def self::available_commands
150
+ return self.public_instance_methods( false ).
151
+ map( &:to_s ).
152
+ grep( /_command$/ ).
153
+ map {|methodname| methodname.sub(/_command$/, '') }.
154
+ sort
155
+ end
156
+
157
+
158
+ ### Create and configure a command-line option parser for the command.
159
+ ### Returns a Trollop::Parser.
160
+ def self::make_option_parser
161
+ unless @option_parser
162
+ progname = File.basename( $0 )
163
+ default_configdb = Mongrel2::DEFAULT_CONFIG_URI
164
+
165
+ # Make a list of the log level names and the available commands
166
+ loglevels = Mongrel2::Logging::LOG_LEVELS.
167
+ sort_by {|name,lvl| lvl }.
168
+ collect {|name,lvl| name.to_s }.
169
+ join( ', ' )
170
+ command_table = self.make_command_table
171
+
172
+ @option_parser = Trollop::Parser.new do
173
+ banner "Mongrel2 (Ruby) Shell has these commands available:"
174
+
175
+ text ''
176
+ command_table.each {|line| text(line) }
177
+ text ''
178
+
179
+ text 'Global Options'
180
+ opt :config, "Specify the configfile to use.",
181
+ :default => DEFAULT_CONFIG_URI
182
+ text ''
183
+
184
+ text 'Other Options:'
185
+ opt :debug, "Turn debugging on. Also sets the --loglevel to 'debug'."
186
+ opt :loglevel, "Set the logging level. Must be one of: #{loglevels}",
187
+ :default => Mongrel2::Logging::LOG_LEVEL_NAMES[ Mongrel2.logger.level ]
188
+ end
189
+ end
190
+
191
+ return @option_parser
192
+ end
193
+
194
+
195
+ #################################################################
196
+ ### I N S T A N C E M E T H O D S
197
+ #################################################################
198
+
199
+ ### Create a new instance of the command and set it up with the given
200
+ ### +options+.
201
+ def initialize( options )
202
+ Mongrel2.logger.formatter = Mongrel2::Logging::ColorFormatter.new( Mongrel2.logger )
203
+ @options = options
204
+ @shellmode = false
205
+
206
+ if @options.debug
207
+ $DEBUG = true
208
+ $VERBOSE = true
209
+ Mongrel2.logger.level = Logger::DEBUG
210
+ elsif @options.loglevel
211
+ Mongrel2.logger.level = Mongrel2::Logging::LOG_LEVELS[ @options.loglevel ]
212
+ end
213
+
214
+ Mongrel2::Config.configure( :configdb => @options.config )
215
+ end
216
+
217
+
218
+ ######
219
+ public
220
+ ######
221
+
222
+ # The Trollop options hash the command will read its configuration from
223
+ attr_reader :options
224
+
225
+ # True if running in shell mode
226
+ attr_reader :shellmode
227
+
228
+
229
+ # Delegate the instance #prompt method to the class method instead
230
+ define_method( :prompt, &self.method(:prompt) )
231
+
232
+
233
+ ### Run the command with the specified +command+ and +args+.
234
+ def run( command, *args )
235
+ command ||= 'shell'
236
+ cmd_method = nil
237
+
238
+ begin
239
+ cmd_method = self.method( "#{command}_command" )
240
+ rescue NoMethodError => err
241
+ error "No such command"
242
+ exit :usage
243
+ end
244
+
245
+ cmd_method.call( *args )
246
+ end
247
+
248
+
249
+ #
250
+ # Commands
251
+ #
252
+
253
+ ### The 'help' command
254
+ def help_command( *args )
255
+
256
+ # Subcommand help
257
+ if !args.empty?
258
+ command = args.shift
259
+
260
+ if self.class.available_commands.include?( command )
261
+ header( self.class.help(command) )
262
+ desc = "\n" + 'Usage: ' + command + ' ' + self.class.usage(command) + "\n"
263
+ message( desc )
264
+ else
265
+ error "No such command %p" % [ command ]
266
+ end
267
+
268
+ # Help by itself show the table of available commands
269
+ else
270
+ command_table = self.class.make_command_table
271
+ header "Available Commands"
272
+ message( *command_table )
273
+ end
274
+
275
+ end
276
+ help :help, "Show help for a single COMMAND if given, or list available commands if not"
277
+ usage :help, "[COMMAND]"
278
+
279
+
280
+ ### The 'load' command
281
+ def load_command( *args )
282
+ configfile = args.shift or
283
+ raise "No configfile specified."
284
+
285
+ runspace = Module.new do
286
+ extend Mongrel2::Config::DSL
287
+ end
288
+
289
+ header "Loading config from #{configfile}"
290
+ source = File.read( configfile )
291
+ Mongrel2::Config.init_database!
292
+ runspace.module_eval( source, configfile, 0 )
293
+ end
294
+ help :load, "Overwrite the config database with the values from the speciifed CONFIGFILE."
295
+ usage :load, <<-END_USAGE
296
+ CONFIGFILE
297
+ Note: the CONFIGFILE should contain a configuration described using the
298
+ Ruby config DSL, not a Python-ish normal one. m2sh already works perfectly
299
+ fine for loading those.
300
+ END_USAGE
301
+
302
+
303
+ ### The 'config' command
304
+ alias_method :config_command, :load_command
305
+ help :config, "Alias for 'load'."
306
+
307
+
308
+ ### The 'init' command
309
+ def init_command( * )
310
+ if Mongrel2::Config.database_initialized?
311
+ abort "Okay, aborting." unless
312
+ self.prompt.agree( "Are you sure you want to destroy the current config? " )
313
+ end
314
+
315
+ header "Initializing #{self.options.config}"
316
+ Mongrel2::Config.init_database!
317
+ end
318
+ help :init, "Initialize a new empty config database."
319
+
320
+
321
+ ### The 'shell' command.
322
+ def shell_command( * )
323
+ require 'readline'
324
+ require 'termios'
325
+ require 'shellwords'
326
+
327
+ term = Termios.getattr( $stdin )
328
+ @shellmode = true
329
+
330
+ # Set up the completion callback
331
+ # self.setup_completion
332
+
333
+ # Load saved command-line history
334
+ self.read_history
335
+
336
+ # Run until something sets the quit flag
337
+ quitting = false
338
+ until quitting
339
+ $stderr.puts
340
+ input = Readline.readline( PROMPT, true )
341
+ self.log.debug "Input is: %p" % [ input ]
342
+
343
+ # EOL makes the shell quit
344
+ if input.nil?
345
+ self.log.debug "EOL: setting quit flag"
346
+ quitting = true
347
+
348
+ # Blank input -- just reprompt
349
+ elsif input == ''
350
+ self.log.debug "No command. Re-displaying the prompt."
351
+
352
+ # Parse everything else into command + args
353
+ else
354
+ self.log.debug "Dispatching input: %p" % [ input ]
355
+ command, *args = Shellwords.split( input )
356
+
357
+ # Don't allow recursive shells
358
+ if command == 'shell'
359
+ error "Already in a shell."
360
+ next
361
+ end
362
+
363
+ begin
364
+ self.run( command, *args )
365
+ rescue => err
366
+ error "%p: %s" % [ err.class, err.message ]
367
+ err.backtrace.each do |frame|
368
+ self.log.debug " " + frame
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ message "\nSaving history...\n"
375
+ self.save_history
376
+
377
+ message "done."
378
+
379
+ ensure
380
+ @shellmode = false
381
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
382
+ end
383
+ help :shell, "Start the program in interactive mode."
384
+
385
+
386
+ ### The 'access' command
387
+ def access_command( logfile='logs/access.log', * )
388
+ # 1$ 2$ 3$ 4$ 5$ 6$ 7$ 8$ 9$
389
+ # ["localhost", "127.0.0.1", 53420, 1315533812, "GET", "/favicon.ico", "HTTP/1.1", 404, 0]
390
+ # -> [1315533812] 127.0.0.1:53420 localhost "GET /favicon.ico HTTP/1.1" 404 0
391
+ IO.foreach( logfile ) do |line|
392
+ row, _ = TNetstring.parse( line )
393
+ message %{[%4$d] %2$s:%3$d %1$s "%5$s %6$s %7$s" %8$03d %9$d} % row
394
+ end
395
+ end
396
+ help :access, "Dump the access log."
397
+ usage :access, "[logfile]\nThe logfile defaults to 'logs/access.log'."
398
+
399
+
400
+ ### The 'servers' command
401
+ def servers_command( * )
402
+ header 'SERVERS:'
403
+ Mongrel2::Config.servers.each do |server|
404
+ message "%s [%s]: %s" % [
405
+ self.prompt.color( server.name, :key ),
406
+ server.default_host,
407
+ server.uuid,
408
+ ]
409
+ end
410
+ end
411
+ help :servers, "Lists the servers in a config database."
412
+
413
+
414
+ ### The 'hosts' command
415
+ def hosts_command( *args )
416
+ servername = args.shift
417
+
418
+ # Start with all servers, then narrow it down if they specified a server name.
419
+ servers = Mongrel2::Config::Server.dataset
420
+ servers = servers.filter( :name => servername ) if servername
421
+
422
+ # Output a section for each server
423
+ servers.each do |server|
424
+ header "HOSTS for server #{server.name}:"
425
+ server.hosts.each do |host|
426
+ line = "%d: %s" % [ host.id, host.name ]
427
+ line << " /%s/" % [ host.matching ] if host.matching != host.name
428
+
429
+ message( line )
430
+ end
431
+
432
+ $stdout.puts
433
+ end
434
+ end
435
+ help :hosts, "Lists the hosts in a server, or in all servers if none is specified."
436
+ usage :hosts, "[server]"
437
+
438
+
439
+ ### The 'routes' command
440
+ def routes_command( *args )
441
+ servername = args.shift
442
+ hostname = args.shift
443
+
444
+ # Start with all hosts, then narrow it down if a server and/or host was given.
445
+ hosts = Mongrel2::Config::Host.dataset
446
+ if servername
447
+ server = Mongrel2::Config::Server[ servername ] or
448
+ raise "No such server '#{servername}'"
449
+ hosts = server.hosts_dataset
450
+ end
451
+ hosts = hosts.filter( :name => hostname ) if hostname
452
+
453
+ # Output a section for each host
454
+ hosts.each do |host|
455
+ header "ROUTES for host #{host.server.name}/#{host.name}:"
456
+
457
+ host.routes.each do |route|
458
+ message( route.path )
459
+ end
460
+ end
461
+
462
+ end
463
+ help :routes, "Show the routes under a host."
464
+ usage :routes, "[server [host]]"
465
+
466
+
467
+ ### The 'commit' command
468
+ def commit_command( *args )
469
+ what, where, why, how = *args
470
+ what ||= ''
471
+
472
+ log = Mongrel2::Config::Log.log_action( what, where, why, how )
473
+
474
+ header "Okay, logged."
475
+ message( log.to_s )
476
+ end
477
+ help :commit, "Add a message to the commit log."
478
+ usage :commit, "[WHAT [WHERE [WHY [HOW]]]]"
479
+
480
+
481
+ ### The 'log' command
482
+ def log_command( *args )
483
+ header "Log Messages"
484
+ Mongrel2::Config::Log.order_by( :happened_at ).each do |log|
485
+ message( log.to_s )
486
+ end
487
+ end
488
+ help :log, "Prints the commit log."
489
+
490
+
491
+ ### The 'start' command
492
+ def start_command( *args )
493
+ serverspec = args.shift
494
+ servers = Mongrel2::Config.servers
495
+
496
+ raise "No servers are configured." if servers.empty?
497
+ server = nil
498
+
499
+ # If there's only one configured server, just make sure if a serverspec was given
500
+ # that it would have matched.
501
+ if servers.length == 1
502
+ server = servers.first if !serverspec ||
503
+ servers.first.values_at( :uuid, :default_host, :name ).include?( serverspec )
504
+
505
+ # Otherwise, require an argument and search for the desired server if there is one
506
+ else
507
+ raise "You must specify a server uuid/hostname/name when more " +
508
+ "than one server is configured." if servers.length > 1 && !serverspec
509
+
510
+ server = servers.find {|s| s.uuid == serverspec } ||
511
+ servers.find {|s| s.default_host == serverspec } ||
512
+ servers.find {|s| s.name == serverspec }
513
+ end
514
+
515
+ raise "No servers match '#{serverspec}'" unless server
516
+
517
+ # Run the command, waiting for it to finish if invoked from shell mode, or
518
+ # execing it if not.
519
+ cmd = [ 'mongrel2', Mongrel2::Config.pathname.to_s, server.uuid ]
520
+ if @shellmode
521
+ system( *cmd )
522
+ else
523
+ exec( *cmd )
524
+ end
525
+ end
526
+ help :start, "Starts a server."
527
+ usage :start, <<-END_USAGE
528
+ [SERVER]
529
+ If not specified, SERVER is assumed to be the only server entry in the
530
+ current config. If there are more than one, you must specify a SERVER.
531
+
532
+ The SERVER can be a uuid, hostname, or server name, and are searched for
533
+ in that order.
534
+ END_USAGE
535
+
536
+
537
+ ### The 'version' command
538
+ def version_command( *args )
539
+ message( "<%= color 'Version:', :header %> " + Mongrel2.version_string(true) )
540
+ end
541
+ help :version, "Prints the Ruby-Mongrel2 version."
542
+
543
+
544
+ #
545
+ # Utility methods
546
+ #
547
+
548
+ ### Output normal output
549
+ def message( *parts )
550
+ self.prompt.say( parts.map(&:to_s).join($/) )
551
+ end
552
+
553
+
554
+ ### Output the given +text+ highlighted as a header.
555
+ def header( text )
556
+ message( self.prompt.color(text, :header) )
557
+ end
558
+
559
+
560
+ ### Output the given +text+ highlighted as an error.
561
+ def error( text )
562
+ message( self.prompt.color(text, :error) )
563
+ end
564
+
565
+
566
+ ### Read command line history from HISTORY_FILE
567
+ def read_history
568
+ histfile = HISTORY_FILE.expand_path
569
+
570
+ if histfile.exist?
571
+ lines = histfile.readlines.collect {|line| line.chomp }
572
+ self.log.debug "Read %d saved history commands from %s." % [ lines.length, histfile ]
573
+ Readline::HISTORY.push( *lines )
574
+ else
575
+ self.log.debug "History file '%s' was empty or non-existant." % [ histfile ]
576
+ end
577
+ end
578
+
579
+
580
+ ### Save command line history to HISTORY_FILE
581
+ def save_history
582
+ histfile = HISTORY_FILE.expand_path
583
+
584
+ lines = Readline::HISTORY.to_a.reverse.uniq.reverse
585
+ lines = lines[ -DEFAULT_HISTORY_SIZE, DEFAULT_HISTORY_SIZE ] if
586
+ lines.length > DEFAULT_HISTORY_SIZE
587
+
588
+ self.log.debug "Saving %d history lines to %s." % [ lines.length, histfile ]
589
+
590
+ histfile.open( File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
591
+ ofh.puts( *lines )
592
+ end
593
+ end
594
+
595
+
596
+ end # class Mongrel2::M2SHCommand
597
+
598
+
599
+ Mongrel2::M2SHCommand.run( ARGV.dup )
600
+