maveric 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,59 +20,66 @@
20
20
  # conceptualization over algorithm implementation. It's usable, but kinda.
21
21
  #
22
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
23
+ # cruft and improve our efficiency. We have a new implementation of Route
24
+ # going on, they're now regexps. The ServerError class has been removed, now any
25
+ # subclass of Exception can be raised. One of the larger differences is greater
25
26
  # customization of Maveric and Controller instances through extending methods.
26
27
  # These are touched upon in their respective docs. Have fun!
27
- #
28
+ #
29
+ # I was hoping for a 0.2.1 with refined documentation, updated plugins, and a
30
+ # better execution path. Instead I got all of that as well as altering basic
31
+ # implementation details and adding pre and post processors for Controller and
32
+ # refinment of Maveric's. With the degree of changes we get 0.3.0! Nothing
33
+ # but good stuff yo.
34
+ #
28
35
  # == Authors
29
- #
36
+ #
30
37
  # Maveric is designed and coded by {blink}[mailto:blinketje@gmail.com]
31
38
  # of #ruby-lang on irc.freenode.net
32
- #
39
+ #
33
40
  # == Licence
34
- #
41
+ #
35
42
  # This software is licensed under the
36
43
  # {CC-GNU LGPL}[http://creativecommons.org/licenses/LGPL/2.1/]
37
- #
44
+ #
38
45
  # == Disclaimer
39
- #
46
+ #
40
47
  # This software is provided "as is" and without any express or
41
48
  # implied warranties, including, without limitation, the implied
42
49
  # warranties of merchantability and fitness for a particular purpose.
43
50
  # Authors are not responsible for any damages, direct or indirect.
44
- #
51
+ #
45
52
  # = Maveric: History
46
- #
53
+ #
47
54
  # Maveric was initially designed as a replacement for Camping in the style of a
48
- # Model-View-Controller framework. Early implementations aimed to reduce the
55
+ # Model-View-Controller framework. Early implementations aimed to reduce the
49
56
  # amount of "magic" to 0. Outstanding magic of Camping is that nothing is
50
57
  # related due to the reading, gsub-ing, and eval-ing of the Camping source file
51
58
  # Also, even the unobfuscated/unabridged version of Camping is a bit hard to
52
59
  # follow at times.
53
- #
54
- # However, the result of this initial attempt was a spaghetti of winding code,
60
+ #
61
+ # However, the result of this initial attempt was a spaghetti of winding code,
55
62
  # empty template modules, and rediculous inheritance paths between the template
56
- # modules and a plethora of dynamically generated classes and modules. I got
63
+ # modules and a plethora of dynamically generated classes and modules. I got
57
64
  # more complaints with that particular jumble of code than any other, evar.
58
- #
59
- # The next iteration of Maveric was a seeking of simplification and departure
60
- # from the concept of cloning Camping and its functionality and settling for
65
+ #
66
+ # The next iteration of Maveric was a seeking of simplification and departure
67
+ # from the concept of cloning Camping and its functionality and settling for
61
68
  # Camping-esqe. At this point, I started talking to ezmobius on #ruby-lang and
62
- # their Merb project. We talked a bit about merging and brainstorming but my
69
+ # their Merb project. We talked a bit about merging and brainstorming but my
63
70
  # lone wolf tendencies stirred me away from such an endeavour, but I came away
64
71
  # a compatriot and inspiration for a new routing methodology inspired by Merb's.
65
- # The result is the Route that's been stable since, only varying in method
72
+ # The result is the Route that's been stable since, only varying in method
66
73
  # placement and data management.
67
- #
74
+ #
68
75
  # After building to a version that stood on it's owned and was able to run a
69
76
  # variance of my website as functional as the Camping version. I noticed a few
70
77
  # tendncies of my coding and refactored. I also redesigned the concept from a
71
78
  # modules that included the Maveric module to that of subclassing Maveric, which
72
- # is a Mongrel::HttpHandlerPlugin. This allowed the running of Maveric apps
79
+ # is a Mongrel::HttpHandlerPlugin. This allowed the running of Maveric apps
73
80
  # without using Mongrel::CampingHandler as well as not worrying about namespace
74
81
  # clobbering.
75
- #
82
+ #
76
83
  # Because ehird from #ruby-lang was whining about it so much I removed its
77
84
  # dependancy on Mongrel sooner than later. Maveric should now be very maveric.
78
85
  #
@@ -80,50 +87,54 @@
80
87
  # I revised and refactored codes and algorithms into 0.2.0 which will hopefully
81
88
  # be all that I want. Everything beyond 1.0.0 will be improvements and
82
89
  # extensions.
83
- #
90
+ #
84
91
  # = Features
85
92
  # * Optional sessions
86
93
  # * Flexible and strong route setup
87
94
  # * Sharing of a Controller between Maveric instances is facilitated
88
95
  # * Inheritance is highly respected
89
- #
96
+ #
90
97
  # == Not so Maveric
91
98
  #
92
- # NOTE: Maveric 0.2.0 does not include extension enablers, further releases
93
- # should include them.
94
- #
99
+ # Maveric has additional classes to allow you to run Maveric behind different
100
+ # http server implementations. CGI, FastCGI, and Mongrel are supported. If
101
+ # another service exists that you'd like to have supported please submit a
102
+ # patch of a good reason why.
103
+ #
104
+ # NOTE: Beyond Maveric 0.1.0 extensions have not been touched, future releases
105
+ # should include updates. AFAIK they should work.
106
+ #
95
107
  # For these examples we will utilize the simplest functional Maveric possible
96
108
  # require 'maveric'
97
- # class Maveric::HelloWorld < Maveric::Controller
109
+ # class Maveric::Index < Maveric::Controller
98
110
  # def get
99
- # 'Hello'
111
+ # 'Hello, world!'
100
112
  # end
101
113
  # end
102
- #
114
+ #
115
+ # To use CGI:
116
+ # Maveric.new.dispatch.to_s
117
+ #
118
+ # To use FastCGI:
119
+ # require 'maveric/fastcgi'
120
+ # ::Maveric::FCGI.new(MyMaveric)
121
+ #
103
122
  # To use Mongrel:
104
123
  # require 'maveric/mongrel'
105
124
  # h = ::Mongrel::HttpHandler ip, port
106
125
  # h.register mountpoint, ::Maveric::MongrelHandler.new(MyMaveric)
107
126
  # h.join.run
108
- #
109
- # To use FastCGI:
110
- # require 'maveric/fastcgi'
111
- # ::Maveric::FCGI.new(MyMaveric)
112
- #
113
- # To use WEBrick, which I have come to loathe:
114
- # server = ::WEBrick::HTTPServer.new(:Port => 9090)
115
- # trap('INT'){ puts "Shutting down server."; server.stop }
116
- # server.mount '/', ::Maveric::WEBrickServlet, Maveric
117
- # server.start
118
- #
127
+ #
119
128
  # However, if your script is mounted at a point other than /, use the
120
129
  # :path_prefix option to adjust your routes. This affects all routes generated
121
130
  # from MyMaveric.
122
- # ::Maveric::FCGI.new(MyMaveric, :path_prefix => '/mount/point/here'
123
- #
131
+ # ::Maveric::FCGI.new(MyMaveric, :path_prefix => mountpoint
132
+ # h.register mountpoint, ::Maveric::MongrelHandler.
133
+ # new(MyMaveric, :path_prefix => mountpoint)
134
+ #
124
135
  # --------------------------------
125
- #
126
- # NOTE: Due to rampant debugging and benchmarking there is a plethora of
136
+ #
137
+ # NOTE: Due to rampant debugging and benchmarking there is a plethora of
127
138
  # pointless time checks in place. Anything greater than the WARN level on the
128
139
  # logging output tends to output a good deal of information. DEBUG is not for
129
140
  # the meek.
@@ -135,29 +146,19 @@ require 'maveric/extensions'
135
146
  # = The Maveric: Yeargh.
136
147
  # The Maveric may be used alone or may be used in a cadre of loosely aligned
137
148
  # Maveric instances. The Maveric stands tall and proud, relying on it's fine
138
- # family and it's inventory of goods to get things done with little magic or
149
+ # family and it's inventory of goods to get things done with little magic or
139
150
  # trickery.
140
- #
141
- # = Usage
142
- # We could technically have a running Maveric with:
143
- #
144
- # require 'maveric'
145
- # class Maveric::Index < Maveric::Controller
146
- # def get
147
- # 'Hello, world!'
148
- # end
149
- # end
150
- # maveric = Maveric.new # I'm a real boy now!
151
- #
152
- # but that's not very useful.
153
151
  class Maveric
152
+ ## Implementation details.
153
+ VERSION='0.3.0'
154
+
154
155
  ## Standard end of line for HTTP
155
156
  EOL="\r\n"
156
157
  ## Group 1 wil contain a boundary for multipart/form-data bodies.
157
158
  MP_BOUND_REGEX = /\Amultipart\/form-data.*boundary=\"?([^\";, ]+)\"?/n
158
159
 
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.
160
+ # I hate putting utility methods here, rather than in some module, but
161
+ # I don't see how to do it without getting messy.
161
162
  class << self
162
163
  ## Builds a logging object if there's not one yet, then returns it.
163
164
  def log
@@ -165,7 +166,8 @@ class Maveric
165
166
  @@maveric_logger = Log4r::Logger.new 'mvc'
166
167
  @@maveric_logger.outputters = Log4r::Outputter['stderr']
167
168
  @@maveric_logger.level = Log4r::INFO
168
- @@maveric_logger.info "#{self} integrated at #{Time.now}"
169
+ @@maveric_logger.info "#{self} #{::Maveric::VERSION}"+
170
+ " integrated at #{Time.now}"
169
171
  end
170
172
  @@maveric_logger
171
173
  end
@@ -174,14 +176,14 @@ class Maveric
174
176
  def escape(s)
175
177
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
176
178
  '%'+$1.unpack('H2'*$1.size).join('%').upcase
177
- }.tr(' ', '+')
179
+ }.tr(' ', '+')
178
180
  end
179
181
 
180
182
  ## Unescapes a URI escaped string.
181
183
  def unescape(s)
182
184
  s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
183
185
  [$1.delete('%')].pack('H*')
184
- }
186
+ }
185
187
  end
186
188
 
187
189
  ##
@@ -206,7 +208,7 @@ class Maveric
206
208
  #
207
209
  # Might need to be rehauled to match query_parse's behaviour.
208
210
  def parse_multipart boundary, body
209
- ::Maveric.type_check :body, body, IO
211
+ ::Maveric.type_check :body, body, IO, StringIO
210
212
  values = {}
211
213
  bound = /(?:\r?\n|\A)#{Regexp::quote('--'+boundary)}(?:--)?\r$/
212
214
  until body.eof?
@@ -217,7 +219,7 @@ class Maveric
217
219
  fv[:type] = $1
218
220
  when /^Content-Disposition: form-data;/
219
221
  $'.scan(/(?:\s(\w+)="([^"]+)")/) {|w| fv[w[0].intern] = w[1] }
220
- end
222
+ end
221
223
  end
222
224
 
223
225
  o = unless fv[:filename] then ''
@@ -236,13 +238,15 @@ class Maveric
236
238
  end
237
239
 
238
240
  ##
239
- # I have a penchant for static typing, but just as a general assertion
241
+ # I have a penchant for static typing, so as a general assertion
240
242
  # and insurance that the correct types of data are being passed I created
241
243
  # this little checking method. It will test value to be an instance of
242
244
  # any of the trailing class, or if the block provided evalutates as true.
243
245
  def type_check name, value, *klasses, &test
244
- if klasses.any? {|klass| value.is_a? klass } then return true
245
- elsif test and test[value] then return true
246
+ return unless $DEBUG
247
+ # order of speed: #include?, #index, #any?
248
+ if i = klasses.any?{|k|value.is_a? k} then return i
249
+ elsif test and r = test[value] then return r
246
250
  else
247
251
  raise TypeError, "Expected #{klasses*' or '} for #{name},"+
248
252
  " got #{value.class}:#{value.inspect}."
@@ -265,8 +269,6 @@ class Maveric
265
269
  end
266
270
 
267
271
  ##
268
- # Family is important.
269
- #
270
272
  # Sets up a new Maveric with everything it needs for normal
271
273
  # operation. Many things are either copied or included from it's
272
274
  # parent The logger is copied and Models and Views are included by
@@ -280,51 +282,51 @@ class Maveric
280
282
  module_eval { include parent::Models }
281
283
  const_set(:Views, Module.new).
282
284
  module_eval { include parent::Views }
285
+ @adj_env = parent.adjust_env.dup
283
286
  end
284
287
  end
288
+
289
+ ##
290
+ # If no argument is given, the array of actions is returned.
291
+ #
292
+ # If a Symbol or String is given with no block, in prepare_env, the
293
+ # corresponding method is called with the environment hash as an argument.
294
+ # If a block is given then, in prepare_env, that block will be called
295
+ # with the environment hash as the single argument.
296
+ #
297
+ # If you are specifying other options you must explicitly state
298
+ # :name => <chosen label> as an argument.
299
+ # Additional arguments include :test, which should be a Proc that
300
+ # accepts a single argument. The argument will be the environment
301
+ # hash and the boolean result of the block will determine if the
302
+ # block will run.
303
+ def adjust_env act={}, &block
304
+ ::Maveric.type_check :act, act, Hash, Symbol, String
305
+ return @adj_env if act.is_a? Hash and act.empty?
306
+ act = {:name => act} unless act.is_a? Hash
307
+ act[:do] = block
308
+ @adj_env << act
309
+ end
285
310
  end
311
+ @adj_env = []
286
312
 
287
313
  ##
288
- # When instantiated, the Maveric look search through its constants for
289
- # nested Controllers and adds them by their roots.
314
+ # When instantiated, the Maveric decends through its constants for
315
+ # nested Controllers and adds them by their routes.
290
316
  def initialize opts={}
291
317
  ::Maveric.log.info "#{self.class} instantiated at #{Time.now}"
292
318
  ::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} }
319
+ @options = {:path_prefix => '/'}.merge opts
320
+ @router = ::Maveric::Router.new
321
+ self.class.nested_controllers.each {|c| router.add c.routes, c }
297
322
  end
298
323
 
299
- attr_reader :options, :routings
300
-
301
- ## Returns an array of controllers that are being routed to.
302
- def controllers
303
- @routings.values.uniq
304
- end
324
+ attr_reader :options, :router
305
325
 
306
326
  ##
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
317
-
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 }
321
- end
322
-
323
- ##
324
- # A Maveric's cue to start doing the heavy lifting. The env should be
327
+ # Maveric's cue to start doing the heavy lifting. The env should be
325
328
  # a hash with the typical assignments from an HTTP environment or crying
326
329
  # and whining will ensue. The req_body argument should be a StringIO.
327
- # TODO: More doc! :P
328
330
  def process req_body=$stdin, env=ENV, opts={}
329
331
  ::Maveric.log.info "#{self.class}#process\n "+
330
332
  "[#{Time.now}] #{env['REMOTE_ADDR']} => #{env['REQUEST_URI']}"
@@ -334,28 +336,25 @@ class Maveric
334
336
  prepare_environment env unless env.key? :maveric
335
337
  raise RuntimeError, [404, "Page not found."] unless env[:route]
336
338
 
339
+ # If this is a POST request we need to load data from the body
337
340
  if env['REQUEST_METHOD'] =~ /^post$/i
338
341
  env[:params].update env.key?(:multipart_boundary) ?
339
342
  parse_multipart(env[:multipart_boundary], req_body) :
340
343
  ::Maveric.query_parse(req_body.read)
341
344
  end
342
345
 
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."
346
+ unless env[:route][:controller] < ::Maveric::Controller
347
+ raise TypeError, "Route Controller of "+
348
+ "#{env[:route][:controller].class}. Expecting ::Maveric::Controller."
352
349
  end
350
+
351
+ env[:route][:controller].new req_body, env, opts
353
352
  rescue # we catch exception.is_a? StandardError
354
353
  ::Maveric.log.error "#{Time.now}:\n#{$!.inspect}\n#{$!.backtrace*"\n"}"
355
354
  begin
356
355
  raise $! # this makes sense in a certain context...
357
356
  # Here is where we should have transformational stuffs for accessorizing
358
- # or customizing error pages. As long as someone doesn't get stupid
357
+ # or customizing error pages. As long as someone doesn't get stupid
359
358
  # about it.
360
359
  rescue
361
360
  return $!
@@ -365,51 +364,53 @@ class Maveric
365
364
 
366
365
  ##
367
366
  # 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
367
  def prepare_environment env
375
368
  ::Maveric.type_check :env, env, Hash
376
369
  env.update :maveric => self
377
- methods.sort.each{|m| __send__ m, env if /^env_\d+_/=~m }
370
+ self.class.adjust_env.
371
+ select {|act| act[:test].nil? or act[:test][env] }.
372
+ each {|act| act[:do] ? act[:do][env] : __send__(act[:name], env) }
378
373
  end
379
374
 
380
- ## Cookies!
381
- def env_0_cookies env
382
- env[:cookies] = ::Maveric.query_parse env['HTTP_COOKIE'], ';,'
375
+ ### Does this count as magic? No, just defaults.
376
+ adjust_env :route do |env|
377
+ url = ::Maveric.unescape env['REQUEST_URI']
378
+ env[:route] = env[:maveric].router[url]
383
379
  end
384
- ## Params!
385
- def env_0_params env
380
+
381
+ adjust_env :cookies do |env|
382
+ env[:cookies] = ::Maveric.query_parse env['HTTP_COOKIE'], ';,'
383
+ end
384
+
385
+ adjust_env :params do |env|
386
386
  env[:params] = ::Maveric.query_parse env['QUERY_STRING']
387
387
  end
388
- ## More Params!?
389
- def env_0_multipart_detect env
388
+
389
+ adjust_env :multipart_detect do |env|
390
390
  if not env.key? :multipart_boundary \
391
391
  and env['REQUEST_METHOD'] =~ /^post$/i \
392
392
  and env['CONTENT_TYPE'] =~ MP_BOUND_REGEX
393
393
  env[:multipart_boundary] = $1
394
394
  end
395
395
  end
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
402
- end
403
396
 
404
- ## Holds views related methods and helpers
397
+ ##
398
+ # Holds views related methods and helpers
399
+ #
400
+ # As long as Controller#prepare_0_imports is not overridden, Views extends
401
+ # the Controller instance just before the action is called.
405
402
  module Views
406
403
  def path_to c, a={}
407
404
  ::Maveric.log.info "#{self.class}#path_to #{c} #{a.inspect}"
408
- @env[:maveric].routings.eject{|(r,d)| r.build a if d == c }
405
+ @env[:maveric].router.to(c).eject{|r| r.build a }
409
406
  end
410
407
  end
411
408
 
412
- ## Repository for models. Still not realy utilized.
409
+ ##
410
+ # Repository for models. Still not really utilized.
411
+ #
412
+ # As long as Controller#prepare_0_imports is not overridden, Models extends
413
+ # the Controller instance just before the action is called.
413
414
  module Models
414
415
  end
415
416
  end
@@ -419,19 +420,42 @@ end
419
420
  # requests. The number of normal methods is kept low to prevent clobbering by
420
421
  # user defined methods.
421
422
  #
423
+ # === Placing a Controller
424
+ #
422
425
  # They may be defined in any location, but if they are defined in a nested
423
426
  # location within the Maveric class being used to serve at a particular
424
427
  # location, on instantialization of the Maveric they will automatically
425
428
  # be added at either the routes specified in their class definition or
426
429
  # at the default route which is derived from their name and their nesting.
430
+ #
431
+ # === Working in Controller
432
+ #
433
+ # Within an instance of Controller: @env contains the environment hash; @in
434
+ # contains the request body, and @cookies contains a hash of cookie values.
435
+ #
436
+ # In addition @status, @header, and @body may be set to appropriate values
437
+ # for the response. Note that @headers should be a Hash, @status should be an
438
+ # Integer corresponding to a real HTTP status code, and @body should contain
439
+ # a String.
440
+ #
441
+ # Those are the only instance variables you should be warned against playing
442
+ # with frivously.
427
443
  class Maveric::Controller
428
444
  REQUEST_METHODS = [:post, :get, :put, :delete, :head] # CRUDY
445
+ @before = []
446
+ @after = []
429
447
 
430
448
  class << self
431
- ## All children should have routes.
449
+ ## Family is important.
432
450
  def inherited klass
433
- klass.class_eval{ @routes = [] }
451
+ parent = self
452
+ klass.class_eval do
453
+ @routes = []
454
+ @before = parent.before.dup
455
+ @after = parent.after.dup
456
+ end
434
457
  end
458
+
435
459
  ## Removes currently set routes and adds the stated ones.
436
460
  def set_routes r, o={}
437
461
  ::Maveric.type_check :r, r, String, Array
@@ -439,22 +463,79 @@ class Maveric::Controller
439
463
  r = [r] unless r.is_a? Array
440
464
  r.flatten.map{|e| add_route e }
441
465
  end
466
+
442
467
  ## Add a route to the Controller.
443
468
  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
469
+ ::Maveric.type_check :r, r, String, ::Maveric::Route
470
+ r = ::Maveric::Route.new r, o if r.is_a? String
446
471
  @routes << r
447
472
  end
473
+
448
474
  ## Removes all currently set routes.
449
475
  def clear_routes
450
476
  @routes.clear
451
477
  end
452
- ## Returns a default Routing based on the #nesting_path if no routes.
478
+
479
+ ## Returns a default Route based on the #nesting_path if no routes.
453
480
  def routes
454
481
  @routes.empty? ?
455
- ::Maveric::Routing.new(nesting_path) :
482
+ ::Maveric::Route.new(nesting_path) :
456
483
  @routes
457
484
  end
485
+
486
+ ##
487
+ # If no argument is given, the array of actions is returned.
488
+ #
489
+ # If a Symbol or String is given with no block, during Controller
490
+ # initialization before the action is called, the corresponding
491
+ # method is called with the Controller instance as an argument.
492
+ # If a block is given, the block is called with the controller
493
+ # instance as an argument instead.
494
+ #
495
+ # If you are specifying other options, you must explicitly state
496
+ # :name => <chosen label> as an argument.
497
+ # Additional arguments include :only and :exclude, whose values should
498
+ # be a Symbol or an Array of such that correspond with actions that
499
+ # they should only run on or not run on.
500
+ #
501
+ # NOTE: If you are referencing instance variables within the action,
502
+ # it is recommended that you create a method rather than a block.
503
+ def before act={}, &block
504
+ ::Maveric.type_check :act, act, Hash, Symbol, String
505
+ return @before if act.is_a? Hash and act.empty?
506
+ act = {:name => act} unless act.is_a? Hash
507
+ act[:do] = block
508
+ act[:only]=[*act[:only]] if act.key? :only
509
+ act[:exclude]=[*act[:exclude]] if act.key? :exclude
510
+ @before << act
511
+ end
512
+
513
+ ##
514
+ # If no argument is given, the array of actions is returned.
515
+ #
516
+ # If a Symbol or String is given with no block, during Controller
517
+ # initialization after the action is called, the corresponding
518
+ # method is called with the Controller instance as an argument.
519
+ # If a block is given, the block is called with the controller
520
+ # instance as an argument instead.
521
+ #
522
+ # If you are specifying other options, you must explicitly state
523
+ # :name => <chosen label> as an argument.
524
+ # Additional arguments include :only and :exclude, whose values should
525
+ # be a Symbol or an Array of such that correspond with actions that
526
+ # they should only run on or not run on.
527
+ #
528
+ # NOTE: If you are referencing instance variables within the action,
529
+ # it is recommended that you create a method rather than a block.
530
+ def after act={}, &block
531
+ ::Maveric.type_check :act, act, Hash, Symbol, String
532
+ return @after if act.is_a? Hash and act.empty?
533
+ act = {:name => act} unless act.is_a? Hash
534
+ act[:do] = block
535
+ act[:only]=[*act[:only]] if act.key? :only
536
+ act[:exclude]=[*act[:exclude]] if act.key? :exclude
537
+ @after << act
538
+ end
458
539
  end
459
540
 
460
541
  ##
@@ -462,22 +543,17 @@ class Maveric::Controller
462
543
  #
463
544
  # The response body is set with the result of the specified action method
464
545
  # 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.
546
+ # action method is determined respectively by env[:route][:action],
547
+ # REQUEST_METHOD in downcased form, or 'get' by default.
467
548
  #
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,
549
+ # By the time the Controller.new is done running, it should have @status,
474
550
  # @headers, and @body set. @status should be an Integer matching the
475
551
  # http status code, @headers a string keyed hash of http headers, and @body
476
552
  # to a string of the http response body.
477
- def initialize req_body, env, opts={}
553
+ def initialize req_body=$stdin, env=ENV, opts={}
478
554
  ::Maveric.log.warn "Provided env has not been properly processed, results "+
479
555
  "may vary!" unless ([:maveric, :route, :params, :cookies]-env.keys).empty?
480
- ::Maveric.type_check :req_body, req_body, IO, StringIO
556
+ ::Maveric.type_check :req_body, req_body, StringIO, IO
481
557
  ::Maveric.type_check :env, env, Hash
482
558
  ::Maveric.type_check :opts, opts, Hash
483
559
 
@@ -485,25 +561,49 @@ class Maveric::Controller
485
561
  @env, @in = env, req_body
486
562
  @cookies = @env[:cookies].dup
487
563
 
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
564
+ action ||= @env[:route][:action] rescue nil
565
+ action ||= @env['REQUEST_METHOD'].downcase rescue nil
566
+ action ||= 'get'
567
+ action = action.to_sym
568
+
493
569
  raise NoMethodError, [503, "#{action} not implemented.", nil, @env] if \
494
570
  REQUEST_METHODS.include? action and not respond_to? action
495
571
 
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 }
572
+ self.class.before.select do |act|
573
+ (act[:only].nil? or act[:only].include? action) and \
574
+ (act[:exclude].nil? or not act[:exclude].include? action)
575
+ end.each do |act|
576
+ act[:do] ? act[:do][self] : __send__(act[:name], self)
577
+ end
578
+
579
+ @body = __send__ action
580
+
581
+ self.class.after.select do |act|
582
+ (act[:only].nil? or act[:only].include? action) and \
583
+ (act[:exclude].nil? or not act[:exclude].include? action)
584
+ end.each do |act|
585
+ act[:do] ? act[:do][self] : __send__(act[:name], self)
586
+ end
500
587
 
501
588
  ::Maveric.log.debug self # omg, masochistic.
502
589
  end
503
590
 
504
591
  attr_reader :status, :headers, :body
505
592
 
506
- ## TODO: Doc me.
593
+ ## For quick and raw response outputting!
594
+ def to_http
595
+ response = "Status: #{@status}" + ::Maveric::EOL # Status message? :/
596
+ response << @headers.map{|k,v| "#{k}: #{v}" }*::Maveric::EOL
597
+ response << ::Maveric::EOL*2 + @body
598
+ end
599
+
600
+ private
601
+
602
+ ##
603
+ # TODO: Doc me. ehird is confused
604
+ #
605
+ # This is a simple method, really. The only confusing part is the expressions
606
+ # just above the return. Controller#render calls itself. That is all.
507
607
  def render view, s=''
508
608
  ::Maveric.log.debug "#{self.class}#render"+
509
609
  " #{view.inspect}, #{s.inspect}" # s is painful to logs.
@@ -512,50 +612,72 @@ class Maveric::Controller
512
612
  s = yield s if block_given?
513
613
  s = __send__ view, s if respond_to? view
514
614
  s = render :layout, s unless view.to_s[/^(_|layout$)/] \
515
- or caller.any?{|l|l=~/`render'/} # i don't like this
615
+ or caller.any?{|l|l=~/`render'/} # i don't like this but it works
516
616
  return s
517
617
  end
518
618
 
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
524
- end
525
-
526
619
  ##
620
+ # Standard Controller setup.
621
+ #
527
622
  # Extends the Controller instance with the Views and Models modules of the
528
623
  # pertinant Maveric instance.
529
- def prepare_0_imports
530
- extend @env[:maveric].class::Models
531
- extend @env[:maveric].class::Views
624
+ def setup controller
625
+ controller.extend @env[:maveric].class::Models
626
+ controller.extend @env[:maveric].class::Views
532
627
  end
628
+
533
629
  ##
630
+ # Standard Controller cleanup.
631
+ #
534
632
  # Folds the datasets of @cookie to @headers if they have been altered or
535
633
  # are new.
536
- def cleanup_0_cookies
634
+ def cleanup controller
537
635
  @cookies.each do |k,v|
538
636
  next unless v != @env[:cookies][k]
539
637
  @headers['Set-Cookie'] << "#{k}=#{::Maveric.escape(v)}"
540
638
  end
541
639
  end
640
+
641
+ before :setup
642
+ after :cleanup
542
643
  end
543
644
 
544
645
  ##
545
646
  # Instances of Route are used for determining how to act upon particular urls
546
647
  # in each Maveric instance. They are magical and simply complex creatures.
547
- class Maveric::Routing < Regexp
648
+ # The Route class was inspired by Merb's implementation, which in turn were
649
+ # inspired by Rails. One thing I think Rails has done nicely, to a point.
650
+ #
651
+ # The String path is turned into a Regexp in a very straight-forward manner.
652
+ # Fragments of the path that begin with ':' followed by any number of
653
+ # characters that aren't a typical URI delimiter or reserved character, and
654
+ # optionally ending with another ':', are indicators of paramæters. The
655
+ # trailing ':' is useful if the parameter occurs within a string of similar
656
+ # characters.
657
+ #
658
+ # Parameters are replaced by default with the regexp of /(#{URI_CHAR}+)/ in
659
+ # the final Route. If a parameter symbol matches a key in the opts hash the
660
+ # associated value is placed into the Route instead.
661
+ #
662
+ class Maveric::Route < Regexp
663
+ ## A negative class of URI delimiting and reserved characters.
548
664
  URI_CHAR = '[^/?:,&#]'
665
+ ## Standard pattern for finding parameters in provided paths.
549
666
  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.
667
+
668
+ # NOTE: The following examples shows a return value of the equivalent regexp
669
+ # and not the result of Route#inspect.
555
670
  #
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.
671
+ # Route.new '/'
672
+ # => /^\/$/ # with no groups or derived values
673
+ # Route.new '/:value'
674
+ # => /^\/([^/?:,&#]+)$/ # with group 1 assigned to :value
675
+ # Route.new '/:val:ue'
676
+ # => /^\/([^/?:,&#]+)ue$/ # with group 1 assigned to :val
677
+ # Route.new '/:value', :value => 'print|find'
678
+ # => /^\/(print|find)$/
679
+ # Route.new '/:whtevr', :whtevr => '.*'
680
+ # => /^\/(.*)$/ # if you want a real catch-all
559
681
  def initialize path, opts={}
560
682
  ::Maveric.type_check :path, path, String
561
683
  ::Maveric.type_check :opts, opts, Hash
@@ -563,31 +685,25 @@ class Maveric::Routing < Regexp
563
685
  @path = path.dup.freeze
564
686
  @options = opts.dup.freeze
565
687
 
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+'+'})"
573
- end
688
+ regex, @params = __compile path, opts
574
689
  @params.freeze
575
- super %r~^#{regex}$~iu.freeze
690
+ super %r~^#{regex}$~iu
691
+ freeze
576
692
 
577
693
  ::Maveric.log.debug self.inspect
578
694
  end
579
695
 
580
- attr_reader :path, :params
696
+ attr_reader :path, :params, :options
581
697
 
582
698
  ##
583
699
  # If given a hash that contains all of and only contains keys matching its
584
700
  # parameters then a path will be built by interpolating the hash values for
585
701
  # 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?
702
+ #
703
+ # NOTE: Should I or should I not pass the result to #route to ensure
704
+ # the generation of a compatible path?
588
705
  def build arg
589
- ::Maveric.log.debug "#{self.class}#build"+
590
- " #{arg.inspect} : #{self.inspect}"
706
+ ::Maveric.log.debug "#{self.class}#build #{arg.inspect} : #{@params}"
591
707
  ::Maveric.type_check :arg, arg, Hash
592
708
  if @params.sort == arg.keys.sort
593
709
  path = arg.inject(@path){|r,(k,v)| r.sub /:#{k}:?/, v }
@@ -595,12 +711,11 @@ class Maveric::Routing < Regexp
595
711
  end
596
712
 
597
713
  ##
598
- # When given a path that matches the Routing itself, a hash is returned with
714
+ # When given a path that matches the Route itself, a hash is returned with
599
715
  # keys being parameters and values being the associated value gathered from
600
716
  # the path.
601
717
  def route path
602
- ::Maveric.log.debug "#{self.class}#route"+
603
- " #{path.inspect} : #{self.inspect}"
718
+ ::Maveric.log.debug "#{self.class}#route #{path.inspect} : #{self}"
604
719
  ::Maveric.type_check :path, path, String
605
720
  if self =~ path
606
721
  Hash[ *@params.zip($~.captures).flatten ].update(nil => self)
@@ -608,19 +723,92 @@ class Maveric::Routing < Regexp
608
723
  end
609
724
 
610
725
  ##
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={}
726
+ # This is an attempt as path correction. The String root is treated as a
727
+ # prefix to the generating path to be removed, from which a new Route will
728
+ # be generated.
729
+ #
730
+ # r = Route.new '/foo/bar/qux'
731
+ # r.reroot('/foo') == Route.new('/bar/qux')
732
+ # => true
733
+ def reroot root, opts={}
614
734
  ::Maveric.type_check :root, root, String
615
- self.class.new @path.sub(/^#{root}/,'/'), @options.merge(opts)
735
+ self.class.new @path.sub(/^#{root}\/?/,'/'), @options.merge(opts)
616
736
  end
617
737
 
618
738
  ##
619
- # As Routing is a subclass of a corelib class, the inspect isn't as
739
+ # As Route is a subclass of a corelib class, the inspect isn't as
620
740
  # informative as we'd like. So we override it.
621
741
  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}>"
742
+ "#<%s:0x%x %s %s %s>" % [
743
+ self.class,
744
+ [object_id<<1].pack('i').unpack('I')[0],
745
+ super,
746
+ @params.inspect,
747
+ @path.inspect
748
+ ]
749
+ end
750
+
751
+ private
752
+
753
+ ##
754
+ # Builds a regex from path by parsing out fragments matching PARAM_MATCH
755
+ # and replacing them with either a corresponding symbol from the opts
756
+ # hash or the default regex.
757
+ def __compile path, opts
758
+ ::Maveric.type_check :path, path, String
759
+ ::Maveric.type_check :opts, opts, Hash
760
+ params = []
761
+ regex = path.gsub PARAM_MATCH do
762
+ param = $1.to_sym
763
+ raise ArgumentError, "Duplicated parameter in path."+
764
+ " <#{param.inspect}>" if params.include? param
765
+ params << param
766
+ "(#{opts[param] || URI_CHAR+'+'})"
767
+ end
768
+ [regex, params]
769
+ end
770
+ end
771
+
772
+ ## Storage of routes and routing mechanisms.
773
+ class Maveric::Router
774
+ def initialize opts={}
775
+ @routings = Hash.new
776
+ end
777
+
778
+ ## Returns a list of the controllers with listed routes.
779
+ def controllers
780
+ @routings.values.uniq
781
+ end
782
+
783
+ ##
784
+ # Places one or more routes to a Controller. Will generate a new Route if
785
+ # a route is a String. Accepts an Array for routes to add several at a time.
786
+ def add routes, controller, opts={}
787
+ ::Maveric.log.info "#{self.class}#add"+
788
+ " #{routes.inspect} #{controller.inspect}"
789
+ ::Maveric.type_check :controller, controller, Class
790
+ ::Maveric.type_check :routes, routes, Array, String, ::Maveric::Route
791
+ routes = [routes] unless routes.is_a? Array
792
+ routes.flatten.map do |route|
793
+ ::Maveric.type_check :route, route, String, ::Maveric::Route
794
+ route = ::Maveric::Route.new route, opts if route.instance_of? String
795
+ @routings[route] = controller
796
+ end
797
+ end
798
+
799
+ ## Return an array of routes mapped to a controller.
800
+ def to controller
801
+ @routings.map{|(r,d)| r if d == controller }.compact
802
+ end
803
+
804
+ ##
805
+ # Matches the given path against the collection of routes.
806
+ # Returns nil the appropriate Route#route result with :controller
807
+ # mapped to the associated Controller.
808
+ def [] path
809
+ ::Maveric.type_check :path, path, String
810
+ @routings.eject do |(r,c)|
811
+ b=r.route(path) and {:controller=>c}.update b
812
+ end
625
813
  end
626
814
  end
@@ -1,13 +1,9 @@
1
1
  class Module
2
- alias_method :__to_s, :to_s
3
- private :__to_s
4
- ## Prepends a :: to the string represendation of itself.
5
- def to_s; "::#{__to_s}"; end
6
- ## Build a string akin toa relative url of the nesting structure.
7
- def nesting_path
8
- self.to_s.
2
+ ## Build a string akin to the absolute path of the nesting structure.
3
+ def nesting_path
4
+ ('::'+self.to_s). # '::' prepended for absolute path semantic
9
5
  gsub(/([a-z])([A-Z])/, '\1_\2').
10
- gsub(/::/, '/').
6
+ gsub(/(::)+/, '/').
11
7
  downcase
12
8
  end
13
9
  # ::nodoc::
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- module Maveric
1
+ class Maveric
2
2
  ##
3
3
  # Contains session data and provides conveniance in generating an appropriate
4
4
  # cookie.
File without changes
metadata CHANGED
@@ -3,13 +3,13 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: maveric
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.0
7
- date: 2007-01-22 00:00:00 -07:00
6
+ version: 0.3.0
7
+ date: 2007-01-26 00:00:00 -07:00
8
8
  summary: A simple, non-magical, framework.
9
9
  require_paths:
10
- - .
10
+ - lib/
11
11
  email: blinketje@gmail.com
12
- homepage:
12
+ homepage: http://maveric.rubyforge.org/
13
13
  rubyforge_project:
14
14
  description:
15
15
  autorequire: maveric
@@ -29,12 +29,12 @@ post_install_message:
29
29
  authors:
30
30
  - blink
31
31
  files:
32
- - maveric.rb
33
- - maveric/mongrel.rb
34
- - maveric/fastcgi.rb
35
- - maveric/webrick.rb
36
- - maveric/sessions.rb
37
- - maveric/extensions.rb
32
+ - lib/maveric.rb
33
+ - lib/maveric/mongrel.rb
34
+ - lib/maveric/fastcgi.rb
35
+ - lib/maveric/webrick.rb
36
+ - lib/maveric/sessions.rb
37
+ - lib/maveric/extensions.rb
38
38
  test_files: []
39
39
 
40
40
  rdoc_options: []