maveric 0.2.0 → 0.3.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.
@@ -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: []