mongrel2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+