maveric 0.1.0 → 0.2.0
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/maveric.rb +350 -493
- data/maveric/extensions.rb +94 -0
- data/maveric/mongrel.rb +2 -2
- data/maveric/sessions.rb +73 -0
- metadata +5 -4
- data/maveric/stadik-impl.rb +0 -113
data/maveric.rb
CHANGED
@@ -1,11 +1,29 @@
|
|
1
1
|
##
|
2
2
|
# = Maveric: A simple, non-magical, framework
|
3
3
|
#
|
4
|
+
# == Resources
|
5
|
+
#
|
6
|
+
# Maveric can normally be found on {rubyforge}[http://maveric.rubyforge.org/]
|
7
|
+
# with it's project page {here}[http://rubyforge.org/projects/maveric/].
|
8
|
+
#
|
9
|
+
# Maveric is also listed at the RAA as
|
10
|
+
# {maveric}[http://raa.ruby-lang.org/project/maveric/]. This page lags a bit
|
11
|
+
# behind rubyforge.
|
12
|
+
#
|
13
|
+
# Maveric's home away from home is http://code.stadik.net which contains
|
14
|
+
# many other things.
|
15
|
+
#
|
4
16
|
# == Version
|
5
17
|
#
|
6
|
-
# This is 0.1.0
|
18
|
+
# This is 0.1.0, the first real release of Maveric. It's rough around the edges
|
7
19
|
# and several squishy bits in the middle. At the moment it's still operation
|
8
20
|
# conceptualization over algorithm implementation. It's usable, but kinda.
|
21
|
+
#
|
22
|
+
# Ahh, 0.2.0, wherein we reevaluate ourselves and our ways and eliminate the
|
23
|
+
# cruft and improve our efficiency. We have a new implementation of Routing
|
24
|
+
# going on, they're now regexps. The ServerError class has been removed, now any subclass of Exception can be raised. One of the larger differences is greater
|
25
|
+
# customization of Maveric and Controller instances through extending methods.
|
26
|
+
# These are touched upon in their respective docs. Have fun!
|
9
27
|
#
|
10
28
|
# == Authors
|
11
29
|
#
|
@@ -57,14 +75,22 @@
|
|
57
75
|
#
|
58
76
|
# Because ehird from #ruby-lang was whining about it so much I removed its
|
59
77
|
# dependancy on Mongrel sooner than later. Maveric should now be very maveric.
|
78
|
+
#
|
79
|
+
# Because Maveric 0.1.0 worked, but still didn't do things the way I wanted
|
80
|
+
# I revised and refactored codes and algorithms into 0.2.0 which will hopefully
|
81
|
+
# be all that I want. Everything beyond 1.0.0 will be improvements and
|
82
|
+
# extensions.
|
60
83
|
#
|
61
84
|
# = Features
|
62
|
-
# *
|
85
|
+
# * Optional sessions
|
63
86
|
# * Flexible and strong route setup
|
64
87
|
# * Sharing of a Controller between Maveric instances is facilitated
|
65
88
|
# * Inheritance is highly respected
|
66
89
|
#
|
67
90
|
# == Not so Maveric
|
91
|
+
#
|
92
|
+
# NOTE: Maveric 0.2.0 does not include extension enablers, further releases
|
93
|
+
# should include them.
|
68
94
|
#
|
69
95
|
# For these examples we will utilize the simplest functional Maveric possible
|
70
96
|
# require 'maveric'
|
@@ -101,25 +127,9 @@
|
|
101
127
|
# pointless time checks in place. Anything greater than the WARN level on the
|
102
128
|
# logging output tends to output a good deal of information. DEBUG is not for
|
103
129
|
# the meek.
|
104
|
-
|
105
130
|
require 'log4r'
|
106
|
-
require 'benchmark'
|
107
131
|
require 'stringio'
|
108
|
-
|
109
|
-
class Module
|
110
|
-
protected
|
111
|
-
##
|
112
|
-
# Decends through the heirarchy of classes and modules looking fot
|
113
|
-
# objects in constants that &test[const] == true
|
114
|
-
def nested_search d=0, &test
|
115
|
-
constants.map do |c| c, r = const_get(c), []
|
116
|
-
next if c == self
|
117
|
-
r << c if test[c]
|
118
|
-
r.concat c.nested_search(d+1, &test) if c.is_a? Class or c.is_a? Module
|
119
|
-
r
|
120
|
-
end.flatten.compact
|
121
|
-
end
|
122
|
-
end
|
132
|
+
require 'maveric/extensions'
|
123
133
|
|
124
134
|
##
|
125
135
|
# = The Maveric: Yeargh.
|
@@ -130,6 +140,7 @@ end
|
|
130
140
|
#
|
131
141
|
# = Usage
|
132
142
|
# We could technically have a running Maveric with:
|
143
|
+
#
|
133
144
|
# require 'maveric'
|
134
145
|
# class Maveric::Index < Maveric::Controller
|
135
146
|
# def get
|
@@ -137,46 +148,29 @@ end
|
|
137
148
|
# end
|
138
149
|
# end
|
139
150
|
# maveric = Maveric.new # I'm a real boy now!
|
140
|
-
#
|
151
|
+
#
|
152
|
+
# but that's not very useful.
|
141
153
|
class Maveric
|
154
|
+
## Standard end of line for HTTP
|
142
155
|
EOL="\r\n"
|
156
|
+
## Group 1 wil contain a boundary for multipart/form-data bodies.
|
143
157
|
MP_BOUND_REGEX = /\Amultipart\/form-data.*boundary=\"?([^\";, ]+)\"?/n
|
144
158
|
|
145
|
-
|
146
|
-
|
147
|
-
@log.level = Log4r::INFO
|
148
|
-
#@sessions = Hash.new {|h,k| s = Maveric::Session.new; h[s.id]=s; }
|
149
|
-
@@sessions = Hash.new {|h,k| s = Maveric::Session.new; h[s.id]=s; }
|
150
|
-
|
151
|
-
@log.info "#{self.class} integrated at #{Time.now}"
|
152
|
-
|
159
|
+
# I hate putting utility methods here, rather than in some module, but for
|
160
|
+
# somereason things aren't working as they should that way.
|
153
161
|
class << self
|
154
|
-
##
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
def inherited klass
|
162
|
-
Maveric.log.info "Maveric: #{klass} inherits from #{self}."
|
163
|
-
# By default a Maveric's logger is its parent's
|
164
|
-
klass.instance_variable_set :@log, @log
|
165
|
-
parent = self
|
166
|
-
klass.class_eval do
|
167
|
-
const_set(:Models, Module.new).
|
168
|
-
module_eval { include parent::Models }
|
169
|
-
const_set(:Views, Module.new).
|
170
|
-
module_eval { include parent::Views }
|
162
|
+
## Builds a logging object if there's not one yet, then returns it.
|
163
|
+
def log
|
164
|
+
unless defined? @@maveric_logger
|
165
|
+
@@maveric_logger = Log4r::Logger.new 'mvc'
|
166
|
+
@@maveric_logger.outputters = Log4r::Outputter['stderr']
|
167
|
+
@@maveric_logger.level = Log4r::INFO
|
168
|
+
@@maveric_logger.info "#{self} integrated at #{Time.now}"
|
171
169
|
end
|
170
|
+
@@maveric_logger
|
172
171
|
end
|
173
172
|
|
174
|
-
|
175
|
-
|
176
|
-
def sessions; @@sessions; end
|
177
|
-
|
178
|
-
##
|
179
|
-
# Performs URI escaping.
|
173
|
+
## Performs URI escaping.
|
180
174
|
def escape(s)
|
181
175
|
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
182
176
|
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
@@ -194,21 +188,23 @@ class Maveric
|
|
194
188
|
# Parses a query string by breaking it up around the
|
195
189
|
# delimiting characters. You can also use this to parse
|
196
190
|
# cookies by changing the characters used in the second
|
197
|
-
# parameter (which defaults to ';,'.
|
191
|
+
# parameter (which defaults to ';,').
|
192
|
+
#
|
193
|
+
# This will return a hash of parameters, values being contained in an
|
194
|
+
# array. As a warning, the default value is an empty array, not nil.
|
198
195
|
def query_parse(qs, delim = '&;')
|
199
|
-
(qs||'').split(/[#{delim}] */n).inject(
|
196
|
+
(qs||'').split(/[#{delim}] */n).inject(Hash.new([])) { |h,p|
|
200
197
|
k, v = unescape(p).split('=',2)
|
201
|
-
|
202
|
-
if h[k].is_a? Array then h[k] << v
|
203
|
-
else h[k] = [h[k], v] end
|
204
|
-
else h[k] = v end
|
198
|
+
(h[k]||=[]) << v
|
205
199
|
h
|
206
200
|
}
|
207
201
|
end
|
208
202
|
|
209
203
|
##
|
210
204
|
# Parse a multipart/form-data entity. Adapated from cgi.rb. The body
|
211
|
-
# argument may either be a StringIO or a IO of
|
205
|
+
# argument may either be a StringIO or a IO of subclass thereof.
|
206
|
+
#
|
207
|
+
# Might need to be rehauled to match query_parse's behaviour.
|
212
208
|
def parse_multipart boundary, body
|
213
209
|
::Maveric.type_check :body, body, IO
|
214
210
|
values = {}
|
@@ -239,12 +235,11 @@ class Maveric
|
|
239
235
|
values
|
240
236
|
end
|
241
237
|
|
242
|
-
##
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
## So we don't have ginormous test+raise statements everywhere.
|
238
|
+
##
|
239
|
+
# I have a penchant for static typing, but just as a general assertion
|
240
|
+
# and insurance that the correct types of data are being passed I created
|
241
|
+
# this little checking method. It will test value to be an instance of
|
242
|
+
# any of the trailing class, or if the block provided evalutates as true.
|
248
243
|
def type_check name, value, *klasses, &test
|
249
244
|
if klasses.any? {|klass| value.is_a? klass } then return true
|
250
245
|
elsif test and test[value] then return true
|
@@ -253,517 +248,379 @@ class Maveric
|
|
253
248
|
" got #{value.class}:#{value.inspect}."
|
254
249
|
end
|
255
250
|
end
|
256
|
-
end
|
257
251
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
# By default we use a global session repository
|
272
|
-
@sessions = @@sessions
|
273
|
-
|
274
|
-
# defualt settings
|
275
|
-
@path_prefix = ''
|
276
|
-
# let's hande some options!
|
277
|
-
if (hopts = opts[-1]).is_a? Hash
|
278
|
-
# should not end with / !!!!!
|
279
|
-
@path_prefix = hopts[:path_prefix] if hopts.key? :path_prefix
|
252
|
+
##################### Non-utility methods
|
253
|
+
|
254
|
+
##
|
255
|
+
# A recursive method to hunt out subclasses of Controller nested
|
256
|
+
# within a Maveric subclass. Used in the setting up of autoroutes.
|
257
|
+
def nested_controllers realm=self, stk=[]
|
258
|
+
stk << realm #We don't need to visit the same thing twice.
|
259
|
+
realm.constants.map do |c|
|
260
|
+
next if stk.include?(c = realm.const_get(c)) or not c.is_a? Module
|
261
|
+
a = []
|
262
|
+
a << c if c < ::Maveric::Controller
|
263
|
+
a += nested_controllers c, stk
|
264
|
+
end.compact.flatten
|
280
265
|
end
|
281
266
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
267
|
+
##
|
268
|
+
# Family is important.
|
269
|
+
#
|
270
|
+
# Sets up a new Maveric with everything it needs for normal
|
271
|
+
# operation. Many things are either copied or included from it's
|
272
|
+
# parent The logger is copied and Models and Views are included by
|
273
|
+
# their respective modules.
|
274
|
+
def inherited klass
|
275
|
+
::Maveric.log.info "#{klass} inherits from #{self}."
|
276
|
+
super klass
|
277
|
+
parent = self
|
278
|
+
klass.class_eval do
|
279
|
+
const_set(:Models, Module.new).
|
280
|
+
module_eval { include parent::Models }
|
281
|
+
const_set(:Views, Module.new).
|
282
|
+
module_eval { include parent::Views }
|
292
283
|
end
|
293
284
|
end
|
294
285
|
end
|
295
286
|
|
296
|
-
attr_reader :routings, :sessions
|
297
|
-
def log; self.class.log; end
|
298
|
-
|
299
287
|
##
|
300
|
-
#
|
301
|
-
#
|
302
|
-
def
|
303
|
-
log.info "#{self}
|
304
|
-
|
305
|
-
@
|
288
|
+
# When instantiated, the Maveric look search through its constants for
|
289
|
+
# nested Controllers and adds them by their roots.
|
290
|
+
def initialize opts={}
|
291
|
+
::Maveric.log.info "#{self.class} instantiated at #{Time.now}"
|
292
|
+
::Maveric.type_check :opts, opts, Hash
|
293
|
+
@options = opts
|
294
|
+
@routings = Hash.new # should be an associative array or ordered hash.
|
295
|
+
self.class.nested_controllers.
|
296
|
+
each {|c| add_routes c, c.routes, {:maveric => self} }
|
306
297
|
end
|
307
298
|
|
308
|
-
|
309
|
-
def add_routes c, *r
|
310
|
-
r.flatten.map {|route| add_route c, route }
|
311
|
-
end
|
299
|
+
attr_reader :options, :routings
|
312
300
|
|
313
|
-
##
|
314
|
-
|
315
|
-
|
316
|
-
def set_routes c, *r
|
317
|
-
log.info "#{self}#set_route #{c} #{r.inspect}"
|
318
|
-
@routings.delete_if{|e| e.run == c }
|
319
|
-
add_routes c, *r
|
301
|
+
## Returns an array of controllers that are being routed to.
|
302
|
+
def controllers
|
303
|
+
@routings.values.uniq
|
320
304
|
end
|
321
305
|
|
322
306
|
##
|
323
|
-
#
|
324
|
-
#
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
env[:maveric] = self
|
334
|
-
|
335
|
-
# determines the appropriate route and build the route info.
|
336
|
-
url = Maveric.unescape env['REQUEST_URI']
|
337
|
-
route, rinfo = routings.
|
338
|
-
find {|r| o = r.match_url(url) and break [r, o] }
|
339
|
-
env.update :route => route, :route_info => rinfo
|
340
|
-
#::Maveric.log.warn env.map{|k,v|"#{k}: #{v.inspect}"}*"\n"
|
341
|
-
|
342
|
-
# determine if there's a multipart boundary
|
343
|
-
if env['REQUEST_METHOD'] =~ /^post$/i \
|
344
|
-
and env['CONTENT_TYPE'] =~ MP_BOUND_REGEX
|
345
|
-
env[:multipart_boundary] = $1
|
346
|
-
end
|
307
|
+
# Places a route to a controller. Will generate a new Routing if route is a
|
308
|
+
# String.
|
309
|
+
def add_route controller, route, opts={}
|
310
|
+
::Maveric.log.info "#{self.class}#add_route"+
|
311
|
+
" #{controller.inspect} #{route.inspect}"
|
312
|
+
route = ::Maveric::Routing.new route, opts if route.instance_of? String
|
313
|
+
::Maveric.type_check :controller, controller, Class
|
314
|
+
::Maveric.type_check :route, route, ::Maveric::Routing
|
315
|
+
@routings[route] = controller
|
316
|
+
end
|
347
317
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
session_id = session_id[0] if session_id.is_a? Array
|
352
|
-
env[:session] = sessions[ session_id ]
|
353
|
-
env
|
318
|
+
## Used for adding multiple routes via #add_route
|
319
|
+
def add_routes controller, routes, opts={}
|
320
|
+
routes.flatten.map {|route| add_route controller, route, opts }
|
354
321
|
end
|
355
322
|
|
356
323
|
##
|
357
324
|
# A Maveric's cue to start doing the heavy lifting. The env should be
|
358
325
|
# a hash with the typical assignments from an HTTP environment or crying
|
359
|
-
# and whining will ensue. The req_body argument should be a
|
360
|
-
#
|
361
|
-
def
|
362
|
-
log.info "#{self.class}#
|
326
|
+
# and whining will ensue. The req_body argument should be a StringIO.
|
327
|
+
# TODO: More doc! :P
|
328
|
+
def process req_body=$stdin, env=ENV, opts={}
|
329
|
+
::Maveric.log.info "#{self.class}#process\n "+
|
363
330
|
"[#{Time.now}] #{env['REMOTE_ADDR']} => #{env['REQUEST_URI']}"
|
364
|
-
::Maveric.type_check :req_body, req_body,
|
331
|
+
::Maveric.type_check :req_body, req_body, StringIO
|
365
332
|
::Maveric.type_check :env, env, Hash
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
parse_multipart(env[:multipart_boundary], req_body) :
|
375
|
-
Maveric.query_parse(req_body.read)
|
376
|
-
env[:params].update qparams
|
377
|
-
end
|
378
|
-
|
379
|
-
env[:route].run[req_body, env, *opts]
|
380
|
-
rescue ServerError then $!
|
381
|
-
rescue
|
382
|
-
log.fatal "#{Time.now}: #{$!.to_s}\n#{$!.backtrace*"\n"}"
|
383
|
-
# have a nice fail, then have a hard fail if something fails there.
|
384
|
-
begin
|
385
|
-
ServerError.new($!)
|
386
|
-
end
|
333
|
+
begin
|
334
|
+
prepare_environment env unless env.key? :maveric
|
335
|
+
raise RuntimeError, [404, "Page not found."] unless env[:route]
|
336
|
+
|
337
|
+
if env['REQUEST_METHOD'] =~ /^post$/i
|
338
|
+
env[:params].update env.key?(:multipart_boundary) ?
|
339
|
+
parse_multipart(env[:multipart_boundary], req_body) :
|
340
|
+
::Maveric.query_parse(req_body.read)
|
387
341
|
end
|
388
|
-
end
|
389
|
-
log.info "#{self.class}#dispatch #{id}\n#{n}".chomp
|
390
|
-
response
|
391
|
-
end
|
392
|
-
end
|
393
342
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
#
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
# and simple way to generate fast HTTP response error thingies. As long as
|
414
|
-
# you're within a call to dispatch (with no further rescue clauses) the
|
415
|
-
# ServerError will propogate up and be returned. As a ServerError has similar
|
416
|
-
# accessors as a Controller, they should be compatible with any outputting
|
417
|
-
# implimentation.
|
418
|
-
#
|
419
|
-
# If the status code is 5xx (500 by default) then a backtrace is appended to
|
420
|
-
# the response body.
|
421
|
-
#
|
422
|
-
# raise ServerErrpr; # 500 error
|
423
|
-
# raise ServerError, 'Crap!'; # 500 error with message set to 'Crap!'
|
424
|
-
# raise ServerError, $!; # 500 error with same message and backtrace as
|
425
|
-
# # $! with a note in the message as to the original exception class
|
426
|
-
# raise ServerError, [status, headers, body, *other_data] # Magic!
|
427
|
-
#
|
428
|
-
# In the final example line an Array is passed to raise as a second argument
|
429
|
-
# rather than a String. This only works as intended if the first element is
|
430
|
-
# an Integer. This is taken as the HTTP status code. The next element is
|
431
|
-
# tested to be a Hash, if so then it's values are merged into the HTTP
|
432
|
-
# headers. Then next item is tested to be a String, if so it is appended to
|
433
|
-
# the response body. All remaining elements are appended to the response
|
434
|
-
# body in inspect format.
|
435
|
-
def initialize data=nil
|
436
|
-
@status, @headers, @body = 500, {'Content-Type'=>'text/plain'}, ''
|
437
|
-
case msg = data
|
438
|
-
when Exception
|
439
|
-
msg = @body << data.class.to_s+" "+data.message
|
440
|
-
set_backtrace data.backtrace
|
441
|
-
@body << "\n\n"+data.backtrace*"\n"
|
442
|
-
when String
|
443
|
-
@body << msg
|
444
|
-
when Array
|
445
|
-
if data[0].is_a? Integer
|
446
|
-
# [status, headers, body, request]
|
447
|
-
@status = data.shift
|
448
|
-
@headers.update data.shift if data.first.is_a? Hash
|
449
|
-
@body.concat data.shift if data.first.is_a? String
|
450
|
-
msg = @body.dup
|
451
|
-
@body << "\n\n#{data.map{|e|e.inspect}*"\n"}" unless data.empty?
|
343
|
+
if (controller = env[:route][:controller]) < ::Maveric::Controller
|
344
|
+
controller.new req_body, env, opts
|
345
|
+
elsif controller.is_a? String
|
346
|
+
::Maveric.log.warn "We have not implemented dynamic route dispatch "+
|
347
|
+
"just yet. Please explicitly state :controller."
|
348
|
+
raise "Dynamic dispatching failed."
|
349
|
+
else
|
350
|
+
raise TypeError, "Lapse in handling. Something fugly got dropped."+
|
351
|
+
" Notify Maveric coder please."
|
352
|
+
end
|
353
|
+
rescue # we catch exception.is_a? StandardError
|
354
|
+
::Maveric.log.error "#{Time.now}:\n#{$!.inspect}\n#{$!.backtrace*"\n"}"
|
355
|
+
begin
|
356
|
+
raise $! # this makes sense in a certain context...
|
357
|
+
# Here is where we should have transformational stuffs for accessorizing
|
358
|
+
# or customizing error pages. As long as someone doesn't get stupid
|
359
|
+
# about it.
|
360
|
+
rescue
|
361
|
+
return $!
|
452
362
|
end
|
453
363
|
end
|
454
|
-
if @status/100%10 == 5
|
455
|
-
@body << "\n\n#{$!}\n#{$@[0..10]*"\n"}"
|
456
|
-
end
|
457
|
-
super msg
|
458
|
-
end
|
459
|
-
attr_reader :status, :headers, :body
|
460
|
-
|
461
|
-
## Allows fun with splat (*).
|
462
|
-
def to_a
|
463
|
-
[
|
464
|
-
(@status.dup.freeze rescue @status),
|
465
|
-
@headers.dup.freeze,
|
466
|
-
@body.dup.freeze,
|
467
|
-
(@env.dup.freeze rescue @env)
|
468
|
-
].freeze
|
469
|
-
end
|
470
|
-
|
471
|
-
## Fun with raw output!
|
472
|
-
def to_s
|
473
|
-
status, headers, body, e, i = to_a
|
474
|
-
response = "Status: #{status}"+Maveric::EOL
|
475
|
-
response << headers.map{|(k,v)| "#{k}: #{v}" }*Maveric::EOL
|
476
|
-
response << Maveric::EOL*2
|
477
|
-
response << body
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
##
|
482
|
-
# Contains session data and provides conveniance in generating an appropriate
|
483
|
-
# cookie.
|
484
|
-
class Maveric::Session
|
485
|
-
COOKIE_NAME = 'SESSIONID'
|
486
|
-
DURATION = 15*60
|
487
|
-
KEY_GRADE = 16
|
488
|
-
KEY_LENGTH = 16
|
489
|
-
|
490
|
-
## Do you want to alter the duration of sessions? M'kay.
|
491
|
-
def initialize duration=DURATION
|
492
|
-
@id = Array.new(KEY_LENGTH){rand(KEY_GRADE).to_s(KEY_GRADE)}*''
|
493
|
-
@duration, @data = duration, {}
|
494
|
-
Maveric.log.debug self
|
495
364
|
end
|
496
|
-
attr_reader :id, :expires, :duration, :data
|
497
|
-
attr_writer :duration
|
498
365
|
|
499
366
|
##
|
500
|
-
#
|
501
|
-
#
|
502
|
-
#
|
503
|
-
#
|
504
|
-
#
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
367
|
+
# The env argument should be a normal environment hash derived from HTTP.
|
368
|
+
# Runs through a list of sorted methods, looking for methods beginning with
|
369
|
+
# 'env_' followed by a number followed by a '_' and then whatever descriptive
|
370
|
+
# ending the programmer gives the method. It passes the env hash to those
|
371
|
+
# methods for them to update and mess around with the env in appropriate
|
372
|
+
# ways. See default/included methods for example usage.
|
373
|
+
# NOTE: Override the defaults at your own risk!
|
374
|
+
def prepare_environment env
|
375
|
+
::Maveric.type_check :env, env, Hash
|
376
|
+
env.update :maveric => self
|
377
|
+
methods.sort.each{|m| __send__ m, env if /^env_\d+_/=~m }
|
509
378
|
end
|
510
379
|
|
511
|
-
##
|
512
|
-
|
513
|
-
|
514
|
-
def to_s env=Hash.new{|h,k|"#{k} not defined"}
|
515
|
-
touch unless @expires
|
516
|
-
c = "#{COOKIE_NAME}=#{id};" #required
|
517
|
-
c << " expires=#{@expires.httpdate}" if @expires
|
518
|
-
c << " path=#{env['SCRIPT_NAME']};"
|
519
|
-
c << " domain=#{env['SERVER_NAME']};"
|
520
|
-
c << " secure" if false
|
521
|
-
c
|
380
|
+
## Cookies!
|
381
|
+
def env_0_cookies env
|
382
|
+
env[:cookies] = ::Maveric.query_parse env['HTTP_COOKIE'], ';,'
|
522
383
|
end
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
## Seperator characters.
|
534
|
-
URI_CHAR = '[^/?:,&#]'
|
535
|
-
|
536
|
-
##
|
537
|
-
# Create a Route from the String route where varying parts of the url are
|
538
|
-
# represented as Symbol like sigils. The run argument should be a Proc or
|
539
|
-
# a Maveric::Controller subclass.
|
540
|
-
# Route.new '/:page', proc{|request| p request.params[:params][:page] }
|
541
|
-
# Route.new '/:page', MyController # MyController < Maveric::Controller
|
542
|
-
def initialize route, run
|
543
|
-
::Maveric.type_check :route, route, String
|
544
|
-
::Maveric.type_check :run, run, Proc, ::Maveric::Controller do |k|
|
545
|
-
k < ::Maveric::Controller
|
384
|
+
## Params!
|
385
|
+
def env_0_params env
|
386
|
+
env[:params] = ::Maveric.query_parse env['QUERY_STRING']
|
387
|
+
end
|
388
|
+
## More Params!?
|
389
|
+
def env_0_multipart_detect env
|
390
|
+
if not env.key? :multipart_boundary \
|
391
|
+
and env['REQUEST_METHOD'] =~ /^post$/i \
|
392
|
+
and env['CONTENT_TYPE'] =~ MP_BOUND_REGEX
|
393
|
+
env[:multipart_boundary] = $1
|
546
394
|
end
|
547
|
-
@run = run
|
548
|
-
@route = route.gsub /\/+/, '/'
|
549
|
-
@params = []
|
550
|
-
@regex = Regexp.new '^'+@route.gsub(/\/:(#{URI_CHAR}+)/){
|
551
|
-
@params << $1.to_sym
|
552
|
-
raise 'Duplicated parameters.' unless @params.uniq.size == @params.size
|
553
|
-
"/(#{URI_CHAR}+)"
|
554
|
-
}+'$'
|
555
|
-
Maveric.log.debug self
|
556
395
|
end
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
# Hash.
|
564
|
-
def build_url hsh
|
565
|
-
Maveric.log.debug "#{self.class}#build_url"+
|
566
|
-
" #{hsh.inspect} : #{self.inspect}"
|
567
|
-
#@params.inject(@route){|r,k| r.gsub(/#{k.inspect}/, hsh[k]) }
|
568
|
-
hsh.inject(@route){|r,(k,v)| r.gsub(/:#{k}/, v.to_s) }
|
396
|
+
## What am I doing in this basket...
|
397
|
+
def env_0_route env
|
398
|
+
url = ::Maveric.unescape env['REQUEST_URI']
|
399
|
+
env[:route] = routings.eject do |(r,c)|
|
400
|
+
b=r.route(url) and {:controller=>c}.update b
|
401
|
+
end
|
569
402
|
end
|
570
403
|
|
571
|
-
##
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
return nil unless md = @regex.match(url)
|
578
|
-
Hash[ *@params.zip(md.captures).flatten ]
|
404
|
+
## Holds views related methods and helpers
|
405
|
+
module Views
|
406
|
+
def path_to c, a={}
|
407
|
+
::Maveric.log.info "#{self.class}#path_to #{c} #{a.inspect}"
|
408
|
+
@env[:maveric].routings.eject{|(r,d)| r.build a if d == c }
|
409
|
+
end
|
579
410
|
end
|
580
411
|
|
581
|
-
##
|
582
|
-
|
583
|
-
# well as subclasses of Maveric::Controller.
|
584
|
-
def === arg
|
585
|
-
Maveric.log.debug "#{self.class}#==="+
|
586
|
-
" #{arg.inspect} : #{self.inspect}"
|
587
|
-
case arg
|
588
|
-
when Array then arg.size == @params.size and \
|
589
|
-
not arg.any?{|v|!@params.include?(v)}
|
590
|
-
when Hash then self === arg.keys
|
591
|
-
when String then @regex.match(arg).nil?
|
592
|
-
when Proc then @run == arg
|
593
|
-
when Class then @run == arg if arg < Maveric::Controller
|
594
|
-
end
|
412
|
+
## Repository for models. Still not realy utilized.
|
413
|
+
module Models
|
595
414
|
end
|
596
415
|
end
|
597
416
|
|
598
417
|
##
|
599
|
-
#
|
600
|
-
#
|
601
|
-
#
|
602
|
-
#
|
603
|
-
#
|
604
|
-
#
|
605
|
-
#
|
606
|
-
#
|
607
|
-
#
|
608
|
-
#
|
609
|
-
# If the methods get, post, put, or delete are called (typically called in
|
610
|
-
# Controller.new when setting the body of the response) and they haven't been
|
611
|
-
# defined then a ServerError is thrown with a status of 503.
|
612
|
-
#
|
613
|
-
# * Access to the raw request is provided through @request.
|
614
|
-
# * Cookies are reflected in @cookies, alterations will be passed to the
|
615
|
-
# client response.
|
616
|
-
# * Alteration of the session id cookie is not recommended.
|
617
|
-
# * HTTP headers should be set in @headers. The HTTP status should be set
|
618
|
-
# in @status. Cookies from @cookies are included at the end of processing.
|
418
|
+
# Controllers are the classes that do the actual handling and processing of
|
419
|
+
# requests. The number of normal methods is kept low to prevent clobbering by
|
420
|
+
# user defined methods.
|
421
|
+
#
|
422
|
+
# They may be defined in any location, but if they are defined in a nested
|
423
|
+
# location within the Maveric class being used to serve at a particular
|
424
|
+
# location, on instantialization of the Maveric they will automatically
|
425
|
+
# be added at either the routes specified in their class definition or
|
426
|
+
# at the default route which is derived from their name and their nesting.
|
619
427
|
class Maveric::Controller
|
620
|
-
REQUEST_METHODS = [:post, :get, :put, :delete, :head] #
|
428
|
+
REQUEST_METHODS = [:post, :get, :put, :delete, :head] # CRUDY
|
621
429
|
|
622
|
-
@routes = []
|
623
430
|
class << self
|
624
|
-
##
|
625
|
-
|
626
|
-
|
431
|
+
## All children should have routes.
|
432
|
+
def inherited klass
|
433
|
+
klass.class_eval{ @routes = [] }
|
434
|
+
end
|
435
|
+
## Removes currently set routes and adds the stated ones.
|
436
|
+
def set_routes r, o={}
|
437
|
+
::Maveric.type_check :r, r, String, Array
|
438
|
+
clear_routes
|
439
|
+
r = [r] unless r.is_a? Array
|
440
|
+
r.flatten.map{|e| add_route e }
|
441
|
+
end
|
442
|
+
## Add a route to the Controller.
|
443
|
+
def add_route r, o={}
|
444
|
+
::Maveric.type_check :r, r, String, ::Maveric::Routing
|
445
|
+
r = ::Maveric::Routing.new r, o if r.is_a? String
|
627
446
|
@routes << r
|
628
447
|
end
|
629
|
-
|
448
|
+
## Removes all currently set routes.
|
449
|
+
def clear_routes
|
630
450
|
@routes.clear
|
631
|
-
r.flatten.map{|e| add_route e }
|
632
451
|
end
|
633
|
-
|
634
|
-
|
452
|
+
## Returns a default Routing based on the #nesting_path if no routes.
|
453
|
+
def routes
|
454
|
+
@routes.empty? ?
|
455
|
+
::Maveric::Routing.new(nesting_path) :
|
456
|
+
@routes
|
635
457
|
end
|
636
|
-
attr_reader :routes
|
637
458
|
end
|
638
459
|
|
639
460
|
##
|
640
|
-
# Main processing method of Controller.
|
641
|
-
#
|
642
|
-
#
|
643
|
-
|
644
|
-
|
645
|
-
|
461
|
+
# Main processing method of Controller.
|
462
|
+
#
|
463
|
+
# The response body is set with the result of the specified action method
|
464
|
+
# if the result is a String and @body has not been set to a String. The
|
465
|
+
# action method is determined by env[:route][:action], REQUEST_METHOD in
|
466
|
+
# downcased form, or 'get' by default.
|
467
|
+
#
|
468
|
+
# Directly before the action method is called, methods beginning with
|
469
|
+
# 'prepare_', a number, another '_', and an optional label are called.
|
470
|
+
# Similarly, after the action method is called, methods beginning with
|
471
|
+
# 'cleanup_', etc., are called.
|
472
|
+
#
|
473
|
+
# By the time the Controller is be instantiated, it should have @status,
|
474
|
+
# @headers, and @body set. @status should be an Integer matching the
|
475
|
+
# http status code, @headers a string keyed hash of http headers, and @body
|
476
|
+
# to a string of the http response body.
|
477
|
+
def initialize req_body, env, opts={}
|
478
|
+
::Maveric.log.warn "Provided env has not been properly processed, results "+
|
479
|
+
"may vary!" unless ([:maveric, :route, :params, :cookies]-env.keys).empty?
|
646
480
|
::Maveric.type_check :req_body, req_body, IO, StringIO
|
647
|
-
Maveric.
|
481
|
+
::Maveric.type_check :env, env, Hash
|
482
|
+
::Maveric.type_check :opts, opts, Hash
|
483
|
+
|
648
484
|
@status, @headers = 200, {'Content-Type'=>'text/html'}
|
649
|
-
@env = env
|
650
|
-
@in = req_body
|
485
|
+
@env, @in = env, req_body
|
651
486
|
@cookies = @env[:cookies].dup
|
652
|
-
method = (@env['REQUEST_METHOD'] || 'get').downcase
|
653
487
|
|
654
|
-
|
655
|
-
|
488
|
+
action = if @env.key? :route and @env[:route].key? :action
|
489
|
+
@env[:route][:action]
|
490
|
+
elsif @env.key? 'REQUEST_METHOD'
|
491
|
+
@env['REQUEST_METHOD'].downcase
|
492
|
+
else 'get' end
|
493
|
+
raise NoMethodError, [503, "#{action} not implemented.", nil, @env] if \
|
494
|
+
REQUEST_METHODS.include? action and not respond_to? action
|
656
495
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
Maveric.log.debug self
|
496
|
+
methods.sort.each{|m| __send__ m if /^prepare_\d+_/=~m }
|
497
|
+
result = __send__(action)
|
498
|
+
@body = result unless @body.is_a? String or not result.is_a? String
|
499
|
+
methods.sort.each{|m| __send__ m if /^cleanup_\d+_/=~m }
|
500
|
+
|
501
|
+
::Maveric.log.debug self # omg, masochistic.
|
664
502
|
end
|
665
503
|
|
666
504
|
attr_reader :status, :headers, :body
|
667
505
|
|
668
|
-
##
|
669
|
-
# This is the new renderer. It uses nothing. Yay, configurability!
|
506
|
+
## TODO: Doc me.
|
670
507
|
def render view, s=''
|
671
|
-
Maveric.log.debug "#{self.class}#render"+
|
672
|
-
" #{view.inspect}, #{s}"
|
508
|
+
::Maveric.log.debug "#{self.class}#render"+
|
509
|
+
" #{view.inspect}, #{s.inspect}" # s is painful to logs.
|
673
510
|
::Maveric.type_check :view, view, Symbol, String
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
Maveric.log.info "#{self.class}#render #{view.inspect}\n#{n}".chomp
|
511
|
+
::Maveric.type_check :s, s, String
|
512
|
+
s = yield s if block_given?
|
513
|
+
s = __send__ view, s if respond_to? view
|
514
|
+
s = render :layout, s unless view.to_s[/^(_|layout$)/] \
|
515
|
+
or caller.any?{|l|l=~/`render'/} # i don't like this
|
680
516
|
return s
|
681
517
|
end
|
682
518
|
|
683
|
-
##
|
684
|
-
def
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
@body.dup.freeze,
|
689
|
-
(@env.dup.freeze rescue @env)
|
690
|
-
].freeze
|
519
|
+
## For quick and raw response printing!
|
520
|
+
def to_http
|
521
|
+
response = "Status: #{@status}" + ::Maveric::EOL
|
522
|
+
response << @headers.map{|k,v| "#{k}: #{v}" }*::Maveric::EOL
|
523
|
+
response << ::Maveric::EOL*2 + @body
|
691
524
|
end
|
692
525
|
|
693
|
-
##
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
526
|
+
##
|
527
|
+
# Extends the Controller instance with the Views and Models modules of the
|
528
|
+
# pertinant Maveric instance.
|
529
|
+
def prepare_0_imports
|
530
|
+
extend @env[:maveric].class::Models
|
531
|
+
extend @env[:maveric].class::Views
|
698
532
|
end
|
699
|
-
|
700
533
|
##
|
701
|
-
#
|
702
|
-
#
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
REQUEST_METHODS.include? m
|
709
|
-
super m, *a, &b
|
534
|
+
# Folds the datasets of @cookie to @headers if they have been altered or
|
535
|
+
# are new.
|
536
|
+
def cleanup_0_cookies
|
537
|
+
@cookies.each do |k,v|
|
538
|
+
next unless v != @env[:cookies][k]
|
539
|
+
@headers['Set-Cookie'] << "#{k}=#{::Maveric.escape(v)}"
|
540
|
+
end
|
710
541
|
end
|
711
542
|
end
|
712
543
|
|
713
544
|
##
|
714
|
-
#
|
715
|
-
#
|
716
|
-
|
717
|
-
|
718
|
-
#
|
719
|
-
|
720
|
-
#
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
545
|
+
# Instances of Route are used for determining how to act upon particular urls
|
546
|
+
# in each Maveric instance. They are magical and simply complex creatures.
|
547
|
+
class Maveric::Routing < Regexp
|
548
|
+
URI_CHAR = '[^/?:,&#]'
|
549
|
+
PARAM_MATCH= %r~:(#{URI_CHAR}+):?~
|
550
|
+
##
|
551
|
+
# The String path is turned into a regex in a very straightforward manner.
|
552
|
+
# Fragments of the path that begin with ':' followed by any number of
|
553
|
+
# characters that aren't a typical URI delimiter or reserved character, and
|
554
|
+
# optionally ending with another ':', are indicators of paramæters.
|
555
|
+
#
|
556
|
+
# Parameters are typically replaced with the regexp of /(#{URI_CHAR}+)/ in
|
557
|
+
# the Routing instance. If the parameter label matches a key in opts, the
|
558
|
+
# associated value is placed into the Routing instead.
|
559
|
+
def initialize path, opts={}
|
560
|
+
::Maveric.type_check :path, path, String
|
561
|
+
::Maveric.type_check :opts, opts, Hash
|
562
|
+
|
563
|
+
@path = path.dup.freeze
|
564
|
+
@options = opts.dup.freeze
|
565
|
+
|
566
|
+
@params = []
|
567
|
+
regex = path.gsub PARAM_MATCH do
|
568
|
+
param = $1.to_sym
|
569
|
+
raise ArgumentError, "Duplicated parameters in path."+
|
570
|
+
" <#{param.inspect}>" if @params.include? param
|
571
|
+
@params << param
|
572
|
+
"(#{opts[param] || URI_CHAR+'+'})"
|
728
573
|
end
|
729
|
-
|
730
|
-
|
574
|
+
@params.freeze
|
575
|
+
super %r~^#{regex}$~iu.freeze
|
576
|
+
|
577
|
+
::Maveric.log.debug self.inspect
|
731
578
|
end
|
732
|
-
end
|
733
579
|
|
734
|
-
|
735
|
-
module Maveric::Models
|
736
|
-
end
|
580
|
+
attr_reader :path, :params
|
737
581
|
|
738
|
-
##
|
739
|
-
#
|
740
|
-
#
|
741
|
-
#
|
742
|
-
#
|
743
|
-
#
|
744
|
-
|
745
|
-
#
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
proc{|h| Maveric.query_parse(h['HTTP_COOKIE'], ';,') },
|
751
|
-
:params,
|
752
|
-
proc{|h| Maveric.query_parse(h['QUERY_STRING']) }
|
753
|
-
]
|
754
|
-
|
755
|
-
## If a key has no assigned value then test to see if there's a generator.
|
756
|
-
def default k
|
757
|
-
unless @@gen_on_key.key? k then super k
|
758
|
-
else store k, @@gen_on_key[k][self]
|
582
|
+
##
|
583
|
+
# If given a hash that contains all of and only contains keys matching its
|
584
|
+
# parameters then a path will be built by interpolating the hash values for
|
585
|
+
# their corresponding parameters.
|
586
|
+
# NOTE: Should I or should I not test the result against itself to ensure
|
587
|
+
# the generation of a matching path?
|
588
|
+
def build arg
|
589
|
+
::Maveric.log.debug "#{self.class}#build"+
|
590
|
+
" #{arg.inspect} : #{self.inspect}"
|
591
|
+
::Maveric.type_check :arg, arg, Hash
|
592
|
+
if @params.sort == arg.keys.sort
|
593
|
+
path = arg.inject(@path){|r,(k,v)| r.sub /:#{k}:?/, v }
|
759
594
|
end
|
760
595
|
end
|
761
596
|
|
762
|
-
##
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
597
|
+
##
|
598
|
+
# When given a path that matches the Routing itself, a hash is returned with
|
599
|
+
# keys being parameters and values being the associated value gathered from
|
600
|
+
# the path.
|
601
|
+
def route path
|
602
|
+
::Maveric.log.debug "#{self.class}#route"+
|
603
|
+
" #{path.inspect} : #{self.inspect}"
|
604
|
+
::Maveric.type_check :path, path, String
|
605
|
+
if self =~ path
|
606
|
+
Hash[ *@params.zip($~.captures).flatten ].update(nil => self)
|
767
607
|
end
|
768
608
|
end
|
609
|
+
|
610
|
+
##
|
611
|
+
# This is an attempt as path correction. Root is treated as a prefix to the
|
612
|
+
# generating path to be removed, from which a new Routing will be spawned.
|
613
|
+
def adjust root, opts={}
|
614
|
+
::Maveric.type_check :root, root, String
|
615
|
+
self.class.new @path.sub(/^#{root}/,'/'), @options.merge(opts)
|
616
|
+
end
|
617
|
+
|
618
|
+
##
|
619
|
+
# As Routing is a subclass of a corelib class, the inspect isn't as
|
620
|
+
# informative as we'd like. So we override it.
|
621
|
+
def inspect
|
622
|
+
v = [object_id<<1].pack('i').unpack('I')[0] # faster
|
623
|
+
"#<#{self.class}:0x#{v.to_s(16)} #{super}"+
|
624
|
+
" #{@path.inspect} #{@params.inspect}>"
|
625
|
+
end
|
769
626
|
end
|