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
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
|
+
|