strelka 0.0.1pre4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gemtest +0 -0
  2. data/History.rdoc +4 -0
  3. data/IDEAS.textile +174 -0
  4. data/Manifest.txt +38 -0
  5. data/README.rdoc +66 -0
  6. data/Rakefile +64 -0
  7. data/bin/leash +403 -0
  8. data/data/strelka/apps/strelka-admin +65 -0
  9. data/data/strelka/apps/strelka-setup +26 -0
  10. data/data/strelka/bootstrap-config.rb +34 -0
  11. data/data/strelka/templates/admin/console.tmpl +21 -0
  12. data/data/strelka/templates/layout.tmpl +30 -0
  13. data/lib/strelka/app/defaultrouter.rb +85 -0
  14. data/lib/strelka/app/filters.rb +70 -0
  15. data/lib/strelka/app/parameters.rb +64 -0
  16. data/lib/strelka/app/plugins.rb +205 -0
  17. data/lib/strelka/app/routing.rb +140 -0
  18. data/lib/strelka/app/templating.rb +157 -0
  19. data/lib/strelka/app.rb +175 -0
  20. data/lib/strelka/behavior/plugin.rb +36 -0
  21. data/lib/strelka/constants.rb +53 -0
  22. data/lib/strelka/httprequest.rb +52 -0
  23. data/lib/strelka/logging.rb +241 -0
  24. data/lib/strelka/mixins.rb +143 -0
  25. data/lib/strelka/process.rb +19 -0
  26. data/lib/strelka.rb +40 -0
  27. data/spec/data/layout.tmpl +3 -0
  28. data/spec/data/main.tmpl +1 -0
  29. data/spec/lib/constants.rb +32 -0
  30. data/spec/lib/helpers.rb +134 -0
  31. data/spec/strelka/app/defaultrouter_spec.rb +215 -0
  32. data/spec/strelka/app/parameters_spec.rb +74 -0
  33. data/spec/strelka/app/plugins_spec.rb +167 -0
  34. data/spec/strelka/app/routing_spec.rb +139 -0
  35. data/spec/strelka/app/templating_spec.rb +169 -0
  36. data/spec/strelka/app_spec.rb +160 -0
  37. data/spec/strelka/httprequest_spec.rb +54 -0
  38. data/spec/strelka/logging_spec.rb +72 -0
  39. data/spec/strelka_spec.rb +27 -0
  40. metadata +226 -0
data/bin/leash ADDED
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2'
4
+ require 'mongrel2/config'
5
+ require 'strelka'
6
+
7
+ require 'pp'
8
+ require 'fileutils'
9
+ require 'shellwords'
10
+ require 'tnetstring'
11
+
12
+ require 'trollop'
13
+ require 'highline'
14
+
15
+ # Have to do it this way to avoid the vendored 'sysexits' under OSX.
16
+ gem 'sysexits'
17
+ require 'sysexits'
18
+
19
+
20
+ # A tool for setting up and controlling a Mongrel2 cluster.
21
+ class Strelka::LeashCommand
22
+ extend ::Sysexits
23
+ include Sysexits,
24
+ Strelka::Loggable,
25
+ Strelka::Constants
26
+
27
+ # Make a HighLine color scheme
28
+ COLOR_SCHEME = HighLine::ColorScheme.new do |scheme|
29
+ scheme[:header] = [ :bold, :yellow ]
30
+ scheme[:subheader] = [ :bold, :white ]
31
+ scheme[:key] = [ :white ]
32
+ scheme[:value] = [ :bold, :white ]
33
+ scheme[:error] = [ :red ]
34
+ scheme[:warning] = [ :yellow ]
35
+ scheme[:message] = [ :reset ]
36
+ end
37
+
38
+
39
+ # Path to the default history file for 'shell' mode
40
+ HISTORY_FILE = Pathname( "~/.leash.history" )
41
+
42
+ # Number of items to store in history by default
43
+ DEFAULT_HISTORY_SIZE = 100
44
+
45
+ # The prompt the 'shell' mode should show
46
+ PROMPT = 'leash> '
47
+
48
+ # The data directory in the project if that exists, otherwise the gem datadir
49
+ DATADIR = if File.directory?( 'data/strelka' )
50
+ Pathname( 'data/strelka' )
51
+ elsif path = Gem.datadir('strelka')
52
+ Pathname( path )
53
+ else
54
+ raise ScriptError, "can't find the data directory!"
55
+ end
56
+
57
+
58
+ # Class instance variables
59
+ @command_help = Hash.new {|h,k| h[k] = { :desc => nil, :usage => ''} }
60
+ @prompt = @option_parser = nil
61
+
62
+
63
+ ### Add a help string for the given +command+.
64
+ def self::help( command, helpstring=nil )
65
+ if helpstring
66
+ @command_help[ command.to_sym ][:desc] = helpstring
67
+ end
68
+
69
+ return @command_help[ command.to_sym ][:desc]
70
+ end
71
+
72
+
73
+ ### Add/fetch the +usagestring+ for +command+.
74
+ def self::usage( command, usagestring=nil )
75
+ if usagestring
76
+ prefix = usagestring[ /\A(\s+)/, 1 ]
77
+ usagestring.gsub!( /^#{prefix}/m, '' ) if prefix
78
+
79
+ @command_help[ command.to_sym ][:usage] = usagestring
80
+ end
81
+
82
+ return @command_help[ command.to_sym ][:usage]
83
+ end
84
+
85
+
86
+ ### Return the global Highline prompt object, creating it if necessary.
87
+ def self::prompt
88
+ unless @prompt
89
+ @prompt = HighLine.new
90
+ @prompt.wrap_at = @prompt.output_cols - 10
91
+ end
92
+
93
+ return @prompt
94
+ end
95
+
96
+
97
+ ### Run the utility with the given +args+.
98
+ def self::run( args )
99
+ HighLine.color_scheme = COLOR_SCHEME
100
+
101
+ oparser = self.make_option_parser
102
+ opts = Trollop.with_standard_exception_handling( oparser ) do
103
+ oparser.parse( args )
104
+ end
105
+
106
+ command = oparser.leftovers.shift
107
+ self.new( opts ).run( command, *oparser.leftovers )
108
+ exit :ok
109
+
110
+ rescue => err
111
+ Strelka.logger.fatal "Oops: %s: %s" % [ err.class.name, err.message ]
112
+ Strelka.logger.debug { ' ' + err.backtrace.join("\n ") }
113
+
114
+ exit :software_error
115
+ end
116
+
117
+
118
+ ### Return a String that describes the available commands, e.g., for the 'help'
119
+ ### command.
120
+ def self::make_command_table
121
+ commands = self.available_commands
122
+
123
+ # Build the command table
124
+ col1len = commands.map( &:length ).max
125
+ return commands.collect do |cmd|
126
+ helptext = self.help( cmd.to_sym ) or next # no help == invisible command
127
+ "%s %s" % [
128
+ self.prompt.color(cmd.rjust(col1len), :key),
129
+ self.prompt.color(helptext, :value)
130
+ ]
131
+ end.compact
132
+ end
133
+
134
+
135
+ ### Return an Array of the available commands.
136
+ def self::available_commands
137
+ return self.public_instance_methods( false ).
138
+ map( &:to_s ).
139
+ grep( /_command$/ ).
140
+ map {|methodname| methodname.sub(/_command$/, '') }.
141
+ sort
142
+ end
143
+
144
+
145
+ ### Create and configure a command-line option parser for the command.
146
+ ### Returns a Trollop::Parser.
147
+ def self::make_option_parser
148
+ unless @option_parser
149
+ progname = File.basename( $0 )
150
+ default_configdb = Mongrel2::DEFAULT_CONFIG_URI
151
+
152
+ # Make a list of the log level names and the available commands
153
+ loglevels = Strelka::Logging::LOG_LEVELS.
154
+ sort_by {|name,lvl| lvl }.
155
+ collect {|name,lvl| name.to_s }.
156
+ join( ', ' )
157
+ command_table = self.make_command_table
158
+
159
+ @option_parser = Trollop::Parser.new do
160
+ banner "Take your Mongrel(2) for a walk:"
161
+
162
+ text ''
163
+ command_table.each {|line| text(line) }
164
+ text ''
165
+
166
+ text 'Global Options'
167
+ opt :config, "Specify the configfile to use.",
168
+ :default => DEFAULT_CONFIG_URI
169
+ text ''
170
+
171
+ text 'Other Options:'
172
+ opt :debug, "Turn debugging on. Also sets the --loglevel to 'debug'."
173
+ opt :loglevel, "Set the logging level. Must be one of: #{loglevels}",
174
+ :default => Strelka::Logging::LOG_LEVEL_NAMES[ Strelka.logger.level ]
175
+ end
176
+ end
177
+
178
+ return @option_parser
179
+ end
180
+
181
+
182
+ #################################################################
183
+ ### I N S T A N C E M E T H O D S
184
+ #################################################################
185
+
186
+ ### Create a new instance of the command and set it up with the given
187
+ ### +options+.
188
+ def initialize( options )
189
+ Strelka.logger.formatter = Strelka::Logging::ColorFormatter.new( Strelka.logger )
190
+ @options = options
191
+ @shellmode = false
192
+
193
+ if @options.debug
194
+ $DEBUG = true
195
+ $VERBOSE = true
196
+ Strelka.logger.level = Logger::DEBUG
197
+ elsif @options.loglevel
198
+ Strelka.logger.level = Strelka::Logging::LOG_LEVELS[ @options.loglevel ]
199
+ end
200
+
201
+ Mongrel2::Config.configure( :configdb => @options.config )
202
+ end
203
+
204
+
205
+ ######
206
+ public
207
+ ######
208
+
209
+ # The Trollop options hash the command will read its configuration from
210
+ attr_reader :options
211
+
212
+ # True if running in shell mode
213
+ attr_reader :shellmode
214
+
215
+
216
+ # Delegate the instance #prompt method to the class method instead
217
+ define_method( :prompt, &self.method(:prompt) )
218
+
219
+
220
+ ### Run the command with the specified +command+ and +args+.
221
+ def run( command, *args )
222
+ command ||= 'shell'
223
+ cmd_method = nil
224
+
225
+ begin
226
+ cmd_method = self.method( "#{command}_command" )
227
+ rescue NoMethodError => err
228
+ error "No such command"
229
+ exit :usage
230
+ end
231
+
232
+ cmd_method.call( *args )
233
+ end
234
+
235
+
236
+ #
237
+ # Commands
238
+ #
239
+
240
+ ### The 'help' command
241
+ def help_command( *args )
242
+
243
+ # Subcommand help
244
+ if !args.empty?
245
+ command = args.shift
246
+
247
+ if self.class.available_commands.include?( command )
248
+ header( self.class.help(command) )
249
+ desc = "\n" + 'Usage: ' + command + ' ' + self.class.usage(command) + "\n"
250
+ message( desc )
251
+ else
252
+ error "No such command %p" % [ command ]
253
+ end
254
+
255
+ # Help by itself show the table of available commands
256
+ else
257
+ command_table = self.class.make_command_table
258
+ header "Available Commands"
259
+ message( *command_table )
260
+ end
261
+
262
+ end
263
+ help :help, "Show help for a single COMMAND if given, or list available commands if not"
264
+ usage :help, "[COMMAND]"
265
+
266
+
267
+ ### The 'setup' command
268
+ def setup_command( *args )
269
+ port = args.shift || DEFAULT_ADMIN_PORT
270
+ self.setup_bootstrap_config( port )
271
+ m2pid = self.start_admin_mongrel2
272
+ apppid = self.start_admin_app
273
+
274
+ header "Okay, point a browser at http://localhost:#{port}/"
275
+
276
+ rescue
277
+ message "Killing admin mongrel2..."
278
+ Process.kill( :TERM, m2pid ) if m2pid
279
+
280
+ message "Killing admin app..."
281
+ Process.kill( apppid, :TERM ) if apppid
282
+
283
+ ensure
284
+ results = Process.waitall
285
+ message "Done: %p" % [ results ]
286
+ end
287
+ help :setup, "Set up a new Strelka environment and start the admin server."
288
+ usage :setup, "[PORT]"
289
+
290
+
291
+ ### Install a bootstrap config database for the admin server, which will run
292
+ ### on localhost at the specified +port+.
293
+ def setup_bootstrap_config( port )
294
+ configfile = DATADIR + 'bootstrap-config.rb'
295
+ runspace = Module.new do
296
+ extend Mongrel2::Config::DSL, FileUtils::Verbose
297
+ end
298
+
299
+ header "Loading config from #{configfile}"
300
+ source = configfile.read
301
+ Mongrel2::Config.init_database!
302
+ runspace.module_eval( source, configfile.to_s, 0 )
303
+ end
304
+
305
+
306
+ ### Start the Mongrel2 instance that serves as the front end of the admin server.
307
+ def start_admin_mongrel2
308
+ server = Mongrel2::Config.servers.find {|s| s.name == 'adminserver' } or
309
+ raise ScriptError, "Ack! No server named 'adminserver' in #{Mongrel2::Config.pathname}"
310
+
311
+ message "Starting mongrel2..."
312
+ cmd = [ 'mongrel2', 'mongrel2 (Strelka Admin)' ]
313
+ args = [ Mongrel2::Config.pathname.to_s, server.uuid ]
314
+
315
+ return spawn( cmd, *args )
316
+ end
317
+
318
+
319
+ ### Start the admin app.
320
+ def start_admin_app
321
+ message "Starting up control app..."
322
+
323
+ pid = fork do
324
+ appsdir = DATADIR + 'apps'
325
+ templatedir = DATADIR + 'templates'
326
+ strelka_admin = appsdir + 'strelka-admin'
327
+
328
+ Kernel.load( strelka_admin )
329
+
330
+ Inversion::Template.configure( :template_paths => [templatedir] )
331
+ Strelka::AdminConsole.run( 'admin-console' )
332
+
333
+ exit!
334
+ end
335
+
336
+ return pid
337
+ end
338
+
339
+
340
+ ### The 'version' command
341
+ def version_command( *args )
342
+ message( "<%= color 'Version:', :header %> " + Mongrel2.version_string(true) )
343
+ end
344
+ help :version, "Prints the Ruby-Mongrel2 version."
345
+
346
+
347
+ #
348
+ # Utility methods
349
+ #
350
+
351
+ ### Output normal output
352
+ def message( *parts )
353
+ self.prompt.say( parts.map(&:to_s).join($/) )
354
+ end
355
+
356
+
357
+ ### Output the given +text+ highlighted as a header.
358
+ def header( text )
359
+ message( self.prompt.color(text, :header) )
360
+ end
361
+
362
+
363
+ ### Output the given +text+ highlighted as an error.
364
+ def error( text )
365
+ message( self.prompt.color(text, :error) )
366
+ end
367
+
368
+
369
+ ### Read command line history from HISTORY_FILE
370
+ def read_history
371
+ histfile = HISTORY_FILE.expand_path
372
+
373
+ if histfile.exist?
374
+ lines = histfile.readlines.collect {|line| line.chomp }
375
+ self.log.debug "Read %d saved history commands from %s." % [ lines.length, histfile ]
376
+ Readline::HISTORY.push( *lines )
377
+ else
378
+ self.log.debug "History file '%s' was empty or non-existant." % [ histfile ]
379
+ end
380
+ end
381
+
382
+
383
+ ### Save command line history to HISTORY_FILE
384
+ def save_history
385
+ histfile = HISTORY_FILE.expand_path
386
+
387
+ lines = Readline::HISTORY.to_a.reverse.uniq.reverse
388
+ lines = lines[ -DEFAULT_HISTORY_SIZE, DEFAULT_HISTORY_SIZE ] if
389
+ lines.length > DEFAULT_HISTORY_SIZE
390
+
391
+ self.log.debug "Saving %d history lines to %s." % [ lines.length, histfile ]
392
+
393
+ histfile.open( File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
394
+ ofh.puts( *lines )
395
+ end
396
+ end
397
+
398
+
399
+ end # class Strelka::LeashCommand
400
+
401
+
402
+ Strelka::LeashCommand.run( ARGV.dup )
403
+
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require 'inversion'
5
+ require 'strelka'
6
+
7
+ # The Strelka admin web console.
8
+ class Strelka::AdminConsole < Strelka::App
9
+ plugins :templating, :routing
10
+
11
+ default_content_type 'text/html'
12
+
13
+ layout 'layout.tmpl'
14
+ templates \
15
+ :console => 'admin/console.tmpl'
16
+
17
+
18
+ ### Initialize some application data.
19
+ def initialize( * )
20
+ super
21
+
22
+ Mongrel2.logger = Strelka.logger
23
+ Inversion.logger = Strelka.logger
24
+ Configurability.logger = Strelka.logger
25
+
26
+ @control = Mongrel2::Control.new
27
+ end
28
+
29
+
30
+ # GET / -- console view
31
+ get do |req|
32
+ tmpl = self.template( :console )
33
+ tmpl.control = @control
34
+ tmpl.servers = Mongrel2::Config.servers
35
+
36
+ tmpl.strelka_version = Strelka.version_string( true )
37
+ tmpl.mongrel2_version = Mongrel2.version_string( true )
38
+ tmpl.inversion_version = Inversion.version_string( true )
39
+
40
+ return tmpl
41
+ end
42
+
43
+ end # class Strelka::AdminConsole
44
+
45
+ if __FILE__ == $0
46
+ datadir = Pathname( __FILE__ ).dirname.parent
47
+ templatedir = datadir + 'templates'
48
+
49
+ # Log to the screen if STDERR is opened to one
50
+ if $stderr.tty?
51
+ Strelka.logger = Logger.new( $stderr )
52
+ else
53
+ Strelka.logger = Logger.new( 'admin-console.log' )
54
+ end
55
+
56
+ Strelka.logger.level = $VERBOSE ? Logger::DEBUG : Logger::INFO
57
+
58
+ Inversion::Template.configure( :template_paths => [templatedir] )
59
+ Mongrel2::Config.configure( :configdb => Strelka::Constants::DEFAULT_CONFIG_URI )
60
+
61
+ Strelka::AdminConsole.run( 'admin-console' )
62
+ end
63
+
64
+
65
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'inversion'
4
+ require 'strelka'
5
+
6
+ # The Strelka setup web application.
7
+ class Strelka::SetupProcess < Strelka::Process
8
+
9
+ layout 'setup/layout.tmpl'
10
+
11
+ views :step1 => 'setup/step1',
12
+ :step2 => 'setup/step2'
13
+
14
+
15
+ ### Progress through the setup process.
16
+ def main( req )
17
+ params = show( :step1 )
18
+ params = show( :step2, params )
19
+
20
+ end
21
+
22
+ end # class Strelka::AdminConsole
23
+
24
+
25
+ Strelka::SetupProcess.run( 'strelka-setup' )
26
+
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2/config'
4
+ include Mongrel2::Config::DSL
5
+
6
+ require 'strelka/constants'
7
+ include Strelka::Constants
8
+
9
+ # This is the config that's loaded by 'leash setup' to get the admin server
10
+ # up and running.
11
+
12
+ server 'admin' do
13
+ name 'adminserver'
14
+ port DEFAULT_ADMIN_PORT
15
+ access_log '/logs/admin-access.log'
16
+ error_log '/logs/admin-error.log'
17
+ pid_file '/run/admin.pid'
18
+
19
+ default_host 'localhost'
20
+
21
+ host 'localhost' do
22
+ route '/', handler( 'tcp://127.0.0.1:19999', 'admin-console' )
23
+
24
+ route '/css', directory( 'data/strelka/static/css/', 'base.css', 'text/css' )
25
+ route '/images', directory( 'data/strelka/static/images/' )
26
+ route '/fonts', directory( 'data/strelka/static/fonts/' )
27
+ route '/js', directory( 'data/strelka/static/js/', 'index.js', 'text/javascript' )
28
+ end
29
+ end
30
+
31
+ setting "control_port", 'ipc://run/admin-control'
32
+
33
+ mkdir_p 'logs'
34
+ mkdir_p 'run'
@@ -0,0 +1,21 @@
1
+ <?publish title ?>Admin Console<?end publish ?>
2
+
3
+ <p>The admin console. Is you is, or is you isn't?</p>
4
+
5
+ <ul>
6
+ <li>Strelka: <?attr strelka_version ?></li>
7
+ <li>Mongrel2: <?attr mongrel2_version ?></li>
8
+ <li>Inversion: <?attr inversion_version ?></li>
9
+ </ul>
10
+
11
+
12
+ <section id="mongrel2-status">
13
+ <header>
14
+ <h1>Mongrel Status</h1>
15
+ </header>
16
+
17
+ <table>
18
+
19
+ </table>
20
+ </section>
21
+
@@ -0,0 +1,30 @@
1
+ <!doctype html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
5
+
6
+ <title>Strelka: <?subscribe title ?></title>
7
+ <meta name="author" content="Michael Granger">
8
+
9
+ <meta name="viewport" content="width=device-width,initial-scale=1">
10
+
11
+ <link rel="stylesheet" href="/css/master.css" type="text/css" media="screen"
12
+ title="no title" charset="utf-8" />
13
+ </head>
14
+
15
+ <body>
16
+
17
+ <header>
18
+ <h1><?subscribe title ?></h1>
19
+ </header>
20
+
21
+ <section id="dump">
22
+ <?attr body ?>
23
+ </section>
24
+
25
+ <footer>
26
+ <p><tt>$Id$</tt></p>
27
+ </footer>
28
+
29
+ </body>
30
+ </html>
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'strelka' unless defined?( Strelka )
4
+ require 'strelka/app' unless defined?( Strelka::App )
5
+
6
+ # Simple (dumb?) request router for Strelka::App-based applications.
7
+ class Strelka::App::DefaultRouter
8
+ include Strelka::Loggable
9
+
10
+ ### Create a new router that will route requests according to the specified
11
+ ### +routes+. Each route is a tuple of the form:
12
+ ###
13
+ ### [
14
+ ### <http_verb>, # The HTTP verb as a Symbol (e.g., :GET, :POST, etc.)
15
+ ### <path_array>, # An Array of the parts of the path, as Strings and Regexps.
16
+ ### <action>, # A #to_proc-able object to invoke when the route is matched
17
+ ### <options_hash>, # The hash of route config options
18
+ ### ]
19
+ def initialize( routes=[] )
20
+ @routes = Hash.new {|routes, verb| routes[verb] = {} }
21
+ routes.each do |tuple|
22
+ self.log.debug " adding route: %p" % [ tuple ]
23
+ self.add_route( *tuple )
24
+ end
25
+ end
26
+
27
+
28
+ ######
29
+ public
30
+ ######
31
+
32
+ # A Hash, keyed by Regexps, that contains the routing logic
33
+ attr_reader :routes
34
+
35
+
36
+ ### Add a route for the specified +verb+, +path+, and +options+ that will return
37
+ ### +action+ when a request matches them.
38
+ def add_route( verb, path, action, options={} )
39
+ re = Regexp.compile( path.join('/') )
40
+
41
+ # Make the Hash for the specified HTTP verb if it hasn't been
42
+ self.routes[ verb ][ re ] = { :options => options, :action => action }
43
+ end
44
+
45
+
46
+ ### Determine the most-specific route for the specified +request+ and return
47
+ ### the #to_proc-able object that handles it.
48
+ def route_request( request )
49
+ verb = request.verb
50
+ path = request.app_path || ''
51
+ route = nil
52
+
53
+ # Strip the leading '/'
54
+ path.slice!( 0, 1 ) if path.start_with?( '/' )
55
+
56
+ verbroutes = @routes[ verb ] or return nil
57
+ longestmatch = verbroutes.keys.inject( nil ) do |longestmatch, pattern|
58
+ self.log.debug "Matching pattern %p; longest match so far: %p" %
59
+ [ pattern, longestmatch ]
60
+
61
+ # If the pattern doesn't match, keep the longest match and move on to the next
62
+ match = pattern.match( path ) or next longestmatch
63
+
64
+ # If there was no previous match, or this match was longer, keep it
65
+ self.log.debug " matched: %p (size = %d)" % [ match[0], match[0].length ]
66
+ next match if longestmatch.nil? || match[0].length > longestmatch[0].length
67
+
68
+ # Otherwise just keep the previous match
69
+ self.log.debug " kept longer match %p (size = %d)" %
70
+ [ longestmatch[0], longestmatch[0].length ]
71
+ longestmatch
72
+ end
73
+
74
+ # If there wasn't a match, abort
75
+ return nil unless longestmatch
76
+
77
+ # The best route is the one with the key of the regexp of the
78
+ # longest match
79
+ route = verbroutes[ longestmatch.regexp ]
80
+
81
+ # Bind the method to the app and
82
+ return route[:action]
83
+ end
84
+
85
+ end # class Strelka::App::DefaultRouter