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
data.tar.gz.sig
ADDED
Binary file
|
data/.gemtest
ADDED
File without changes
|
data/History.rdoc
ADDED
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
|
+
|