dyoder-waves 0.7.3

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.
Files changed (61) hide show
  1. data/app/Rakefile +14 -0
  2. data/app/bin/waves-console +3 -0
  3. data/app/bin/waves-server +3 -0
  4. data/app/configurations/development.rb.erb +31 -0
  5. data/app/configurations/mapping.rb.erb +14 -0
  6. data/app/configurations/production.rb.erb +30 -0
  7. data/app/lib/application.rb.erb +3 -0
  8. data/app/startup.rb +5 -0
  9. data/app/templates/errors/not_found_404.mab +2 -0
  10. data/app/templates/errors/server_error_500.mab +2 -0
  11. data/app/templates/layouts/default.mab +14 -0
  12. data/bin/waves +66 -0
  13. data/bin/waves-console +4 -0
  14. data/bin/waves-server +4 -0
  15. data/lib/commands/waves-console.rb +24 -0
  16. data/lib/commands/waves-server.rb +55 -0
  17. data/lib/controllers/mixin.rb +158 -0
  18. data/lib/dispatchers/base.rb +52 -0
  19. data/lib/dispatchers/default.rb +67 -0
  20. data/lib/foundations/default.rb +28 -0
  21. data/lib/foundations/simple.rb +17 -0
  22. data/lib/helpers/common.rb +62 -0
  23. data/lib/helpers/form.rb +39 -0
  24. data/lib/helpers/formatting.rb +30 -0
  25. data/lib/helpers/model.rb +33 -0
  26. data/lib/helpers/view.rb +24 -0
  27. data/lib/layers/default_errors.rb +24 -0
  28. data/lib/layers/simple_errors.rb +17 -0
  29. data/lib/mapping/mapping.rb +252 -0
  30. data/lib/mapping/pretty_urls.rb +94 -0
  31. data/lib/renderers/erubis.rb +61 -0
  32. data/lib/renderers/markaby.rb +33 -0
  33. data/lib/renderers/mixin.rb +53 -0
  34. data/lib/runtime/application.rb +65 -0
  35. data/lib/runtime/configuration.rb +180 -0
  36. data/lib/runtime/console.rb +20 -0
  37. data/lib/runtime/debugger.rb +9 -0
  38. data/lib/runtime/logger.rb +52 -0
  39. data/lib/runtime/mime_types.rb +22 -0
  40. data/lib/runtime/request.rb +77 -0
  41. data/lib/runtime/response.rb +40 -0
  42. data/lib/runtime/response_mixin.rb +35 -0
  43. data/lib/runtime/response_proxy.rb +27 -0
  44. data/lib/runtime/server.rb +94 -0
  45. data/lib/runtime/session.rb +56 -0
  46. data/lib/tasks/cluster.rb +25 -0
  47. data/lib/tasks/gem.rb +31 -0
  48. data/lib/tasks/generate.rb +15 -0
  49. data/lib/utilities/inflect.rb +194 -0
  50. data/lib/utilities/integer.rb +17 -0
  51. data/lib/utilities/kernel.rb +34 -0
  52. data/lib/utilities/module.rb +17 -0
  53. data/lib/utilities/object.rb +17 -0
  54. data/lib/utilities/proc.rb +9 -0
  55. data/lib/utilities/string.rb +47 -0
  56. data/lib/utilities/symbol.rb +7 -0
  57. data/lib/verify/mapping.rb +29 -0
  58. data/lib/verify/request.rb +40 -0
  59. data/lib/views/mixin.rb +108 -0
  60. data/lib/waves.rb +80 -0
  61. metadata +260 -0
@@ -0,0 +1,52 @@
1
+ module Waves
2
+
3
+ module Dispatchers
4
+
5
+ class NotFoundError < Exception ; end
6
+
7
+ class Redirect < Exception
8
+ attr_reader :path, :status
9
+ def initialize( path, status = '302' )
10
+ @path = path
11
+ @status = status
12
+ end
13
+ end
14
+
15
+ # The Base dispatcher simply makes it easier to write dispatchers by inheriting
16
+ # from it. It creates a Waves request, ensures the request processing is done
17
+ # within a mutex, benchmarks the request processing, logs it, and handles common
18
+ # exceptions and redirects. Derived classes need only process the request within
19
+ # their +safe+ method, which takes a Waves::Request and returns a Waves::Response.
20
+
21
+ class Base
22
+
23
+ # Like any Rack application, Waves' dispatchers must provide a call method
24
+ # taking an +env+ parameter.
25
+ def call( env )
26
+ Waves::Application.instance.synchronize do
27
+ request = Waves::Request.new( env )
28
+ response = request.response
29
+ t = Benchmark.realtime do
30
+ begin
31
+ safe( request )
32
+ rescue Dispatchers::Redirect => redirect
33
+ response.status = redirect.status
34
+ response.location = redirect.path
35
+ end
36
+ end
37
+ Waves::Logger.info "#{request.method}: #{request.url} handled in #{(t*1000).round} ms."
38
+ response.finish
39
+ end
40
+ end
41
+
42
+ # Called by event driven servers like thin and ebb. Return true if
43
+ # the server should run the request in a separate thread.
44
+ def deferred?( env )
45
+ Waves::Application.instance.mapping.threaded?( env )
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,67 @@
1
+ module Waves
2
+
3
+ module Dispatchers
4
+
5
+ #
6
+ # The default dispatcher essentially checks the application's mapping to see
7
+ # what to do with the request URL. It checks before and after filters (wrap
8
+ # filters are just a combination of both) as well as path and url mappings.
9
+ #
10
+ # The default dispatcher also attempts to set the content type based on the
11
+ # MIME type implied by any file extension used in the request URL using Mongrel's
12
+ # MIME types YAML file.
13
+ #
14
+ # You can write your own dispatcher and use it in your application's configuration
15
+ # file. By default, they use the default dispatcher, like this:
16
+ #
17
+ # application do
18
+ # use Rack::ShowExceptions
19
+ # run Waves::Dispatchers::Default.new
20
+ # end
21
+ #
22
+
23
+ class Default < Base
24
+
25
+ # All dispatchers using the Dispatchers::Base to provide thread-safety, logging, etc.
26
+ # must provide a +safe+ method to handle creating a response from a request.
27
+ # Takes a Waves::Request and returns a Waves::Reponse
28
+ def safe( request )
29
+
30
+ begin
31
+ response = request.response
32
+
33
+ Waves::Application.instance.reload if Waves::Application.instance.debug?
34
+ response.content_type = Waves::Application.instance.config.mime_types[ request.path ] || 'text/html'
35
+
36
+ mapping = Waves::Application.instance.mapping[ request ]
37
+
38
+ mapping[:before].each do | block, args |
39
+ ResponseProxy.new(request).instance_exec(*args,&block)
40
+ end
41
+
42
+ request.not_found unless mapping[:action]
43
+
44
+ block, args = mapping[:action]
45
+ response.write( ResponseProxy.new(request).instance_exec(*args, &block) )
46
+
47
+ mapping[:after].each do | block, args |
48
+ ResponseProxy.new(request).instance_exec(*args,&block)
49
+ end
50
+ rescue Exception => e
51
+ handler = mapping[:handlers].detect do | exception, block, args |
52
+ e.is_a? exception
53
+ end
54
+ if handler
55
+ ResponseProxy.new(request).instance_exec(*handler[2], &handler[1])
56
+ else
57
+ raise e
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,28 @@
1
+ require 'layers/orm/sequel'
2
+ module Waves
3
+ module Foundations
4
+ module Default
5
+
6
+ def self.included( app )
7
+
8
+ app.instance_eval do
9
+
10
+ include Waves::Layers::Simple
11
+ include Waves::Layers::DefaultErrors
12
+ include Waves::Layers::MVC
13
+ include Waves::Layers::ORM::Sequel
14
+
15
+ # Set autoloading from default.rb files
16
+ #autoinit :Configurations do
17
+ # autoload_class true
18
+ #end
19
+
20
+ end
21
+
22
+ Waves << app
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,17 @@
1
+ module Waves
2
+ module Foundations
3
+
4
+ module Simple
5
+
6
+ def self.included( app )
7
+
8
+ app.instance_eval do
9
+ include Waves::Layers::Simple
10
+ end
11
+
12
+ Waves << app
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,62 @@
1
+ module Waves
2
+ module Helpers
3
+
4
+ # Common helpers are helpers that are needed for just about any Web page. For example,
5
+ # each page will likely have a layout and a doctype.
6
+
7
+ module Common
8
+
9
+ DOCTYPES = {
10
+ :html3 => "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n",
11
+ :html4_transitional =>
12
+ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " <<
13
+ "\"http://www.w3.org/TR/html4/loose.dtd\">\n",
14
+ :html4_strict =>
15
+ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " <<
16
+ "\"http://www.w3.org/TR/html4/strict.dtd\">\n",
17
+ :html4_frameset =>
18
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\" " <<
19
+ "\"http://www.w3.org/TR/html4/frameset.dtd\">\n",
20
+ :xhtml1_transitional =>
21
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " <<
22
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n",
23
+ :xhtml1_strict =>
24
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " <<
25
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n",
26
+ :xhtml1_frameset =>
27
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" " <<
28
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">\n",
29
+ :xhtml2 => "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
30
+ }
31
+
32
+ # Invokes a layout view (i.e., a view from the layouts template directory), using
33
+ # the assigns parameter to define instance variables for the view. The block is
34
+ # evaluated and also passed into the view as the +layout_content+ instance variable.
35
+ #
36
+ # You can define a layout just by creating a template and then calling the
37
+ # +layout_content+ accessor when you want to embed the caller's content.
38
+ #
39
+ # == Example
40
+ #
41
+ # doctype :html4_transitional
42
+ # html do
43
+ # title @title # passed as an assigns parameter
44
+ # end
45
+ # body do
46
+ # layout_content
47
+ # end
48
+ #
49
+ def layout( name, assigns = {}, &block )
50
+ assigns[ :layout_content ] = capture(&block)
51
+ self << Waves.application.views[:layouts].process( request ) do
52
+ send( name, assigns )
53
+ end
54
+ end
55
+
56
+ # The doctype method simply generates a valid DOCTYPE declaration for your page.
57
+ # Valid options are defined in the +DOCTYPES+ constant.
58
+ def doctype(type) ; self << DOCTYPES[type||:html4_strict] ; end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ module Waves
2
+
3
+ module Helpers
4
+
5
+ # Form helpers are used in generating forms. Since Markaby already provides Ruby
6
+ # methods for basic form generation, the focus of this helper is on providing templates
7
+ # to handle things that go beyond the basics. You must define a form template
8
+ # directory with templates for each type of form element you wish to use. The names
9
+ # of the template should match the +type+ option provided in the property method.
10
+ #
11
+ # For example, this code:
12
+ #
13
+ # property :name => 'blog.title', :type => :text, :value => @blog.title
14
+ #
15
+ # will invoke the +text+ form view (the template in +templates/form/text.mab+),
16
+ # passing in the name ('blog.title') and the value (@blog.title) as instance variables.
17
+ #
18
+ module Form
19
+
20
+ # This method really is a place-holder for common wrappers around groups of
21
+ # properties. You will usually want to override this. As is, it simply places
22
+ # a DIV element with class 'properties' around the block.
23
+ def properties(&block)
24
+ div.properties do
25
+ yield
26
+ end
27
+ end
28
+
29
+ # Invokes the form view for the +type+ given in the option.
30
+ def property( options )
31
+ self << view( :form, options[:type], options )
32
+ end
33
+
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,30 @@
1
+ require 'redcloth'
2
+ module Waves
3
+ module Helpers
4
+
5
+ # Formatting helpers are used to convert specialized content, like Markaby or
6
+ # Textile, into valid HTML. It also provides common escaping functions.
7
+ module Formatting
8
+
9
+ # Escape a string as HTML content.
10
+ def escape_html(s); Rack::Utils.escape_html(s); end
11
+
12
+ # Escape a URI, converting quotes and spaces and so on.
13
+ def escape_uri(s); Rack::Utils.escape(s); end
14
+
15
+ # Treat content as Markaby and evaluate (only works within a Markaby template).
16
+ # Used to pull Markaby content from a file or database into a Markaby template.
17
+ def markaby( content ); self << eval( content ); end
18
+
19
+ # Treat content as Textile.
20
+ def textile( content )
21
+ return if content.nil? or content.empty?
22
+ ( ::RedCloth::TEXTILE_TAGS << [ 96.chr, '&8216;'] ).each do |pat,ent|
23
+ content.gsub!( pat, ent.gsub('&','&#') )
24
+ end
25
+ self << ::RedCloth.new( content ).to_html
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Waves
2
+ module Helpers
3
+
4
+ # Model helpers allow you to directly access a model from within a view.
5
+ # This is useful when creating things like select boxes that need data
6
+ # from anther model. For example, a Markaby select box for authors might look like:
7
+ #
8
+ # select do
9
+ # all(:user).each do |user|
10
+ # option user.full_name, :value => user.id
11
+ # end
12
+ # end
13
+ #
14
+ # You could also use these within a view class to keep model-based logic out
15
+ # of the templates themselves. For example, in the view class you might define
16
+ # a method called +authors+ that returns an array of name / id pairs. This could
17
+ # then be called from the template instead of the model helper.
18
+ #
19
+ module Model
20
+
21
+ # Just like model.all. Returns all the instances of that model.
22
+ def all( model )
23
+ Waves.application.models[ model ].all( domain )
24
+ end
25
+
26
+ # Finds a specific instance using the name field
27
+ def find( model, name )
28
+ Waves.application.models[ model ][ :name => name ] rescue nil
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ module Waves
2
+ module Helpers
3
+
4
+ # View helpers are intended to help reuse views from within other views.
5
+ # Both the +layout+ method in the common helpers and the +property+ method
6
+ # of the form helpers are specialized instance of this.
7
+ #
8
+ # The star of our show here is the +view+ method. This takes a model, view,
9
+ # and assigns hash (which are converted into instance variables in the target
10
+ # view) and returns the result of evaluating the view as content in the current
11
+ # template.
12
+ module View
13
+
14
+ # Invokes the view for the given model, passing the assigns as instance variables.
15
+ def view( model, view, assigns = {} )
16
+ self << Waves.application.views[ model ].process( request ) do
17
+ send( view, assigns )
18
+ end
19
+ end
20
+
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Waves
2
+ module Layers
3
+ module DefaultErrors
4
+
5
+ def self.included( app )
6
+
7
+ app.instance_eval do
8
+
9
+ autoinit 'Configurations::Mapping' do
10
+ handle(Waves::Dispatchers::NotFoundError) do
11
+ html = Waves.application.views[:errors].process( request ) do
12
+ not_found_404( :error => Waves::Dispatchers::NotFoundError )
13
+ end
14
+ response.status = '404'
15
+ response.content_type = 'text/html'
16
+ response.write( html )
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Waves
2
+ module Layers
3
+ module SimpleErrors
4
+
5
+ def self.included( app )
6
+
7
+ app.instance_eval do
8
+
9
+ autoinit 'Configurations::Mapping' do
10
+ handle(Waves::Dispatchers::NotFoundError) { response.status = 404 }
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,252 @@
1
+ module Waves
2
+
3
+ # Waves::Mapping is a mixin for defining Waves URI mappings (mapping a request to Ruby code).
4
+ # Mappings can work against the request url, path, and elements of the request (such as the
5
+ # request method or accept header). Mappings may also include before, after, or wrap filters
6
+ # to be run if they match the request. Mappings are created using an appropriate mapping method
7
+ # along with a URL pattern (a string or regular expression), a hash of constraint options, and
8
+ # a block, which is the code to run if the pattern matches.
9
+ #
10
+ # == Examples
11
+ #
12
+ # resource = '([\w\-]+)'
13
+ # name = '([\w\-\_\.\+\@]+)'
14
+ #
15
+ # path %r{^/#{resource}/#{name}/?$} do |resource, name|
16
+ # "Hello from a #{resource} named #{name.capitalize}."
17
+ # end
18
+ #
19
+ # In this example, we are using binding regular expressions defined by +resource+
20
+ # and +name+. The matches are passed into the block as parameters. Thus, this
21
+ # rule, given the URL '/person/john' will return:
22
+ #
23
+ # Hello from a person named John.
24
+ #
25
+ # The given block may simple return a string. The content type is inferred from the request
26
+ # if possible, otherwise it defaults to +text+/+html+.
27
+ #
28
+ # path '/critters', :method => :post do
29
+ # request.content_type
30
+ # end
31
+ #
32
+ # /critters # => 'text/html'
33
+ #
34
+ # In this example, we match against a string and check to make sure that the request is a
35
+ # POST. If so, we return the request content_type. The request (and response) objects are
36
+ # available from within the block implicitly.
37
+ #
38
+ # = Invoking Controllers and Views
39
+ #
40
+ # You may invoke a controller or view method for the primary application by using the
41
+ # corresponding methods, preceded by the +use+ directive.
42
+ #
43
+ # == Examples
44
+ #
45
+ # path %r{^/#{resource}/#{name}/?$} do |resource, name|
46
+ # resource( resource ) do
47
+ # controller { find( name ) } | view { | instance | show( resource => instance ) }
48
+ # end
49
+ # end
50
+ #
51
+ # In this example, we take the same rule from above but invoke a controller and view method.
52
+ # We use the +resource+ directive and the resource parameter to set the MVC instances we're going
53
+ # to use. This is necessary to use the +controller+ or +view+ methods. Each of these take
54
+ # a block as arguments which are evaluated in the context of the instance. The +view+ method
55
+ # can further take an argument which is "piped" from the result of the controller block. This
56
+ # isn't required, but helps to clarify the request processing. Within a view block, a hash
57
+ # may also be passed in to the view method, which is converted into instance variables for the
58
+ # view instance. In this example, the +show+ method is assigned to an instance variable with the
59
+ # same name as the resource type.
60
+ #
61
+ # So given the same URL as above - /person/john - what will happen is the +find+ method for
62
+ # the +Person+ controller will be invoked and the result passed to the +Person+ view's +show+
63
+ # method, with +@person+ holding the value returned.
64
+ #
65
+ # Crucially, the controller does not need to know what variables the view depends on. This is
66
+ # the job of the mapping block, to act as the "glue" between the controller and view. The
67
+ # controller and view can thus be completely decoupled and become easier to reuse separately.
68
+ #
69
+ # url 'http://admin.foobar.com:/' do
70
+ # resource( :admin ) { view { console } }
71
+ # end
72
+ #
73
+ # In this example, we are using the +url+ method to map a subdomain of +foobar.com+ to the
74
+ # console method of the Admin view. In this case, we did not need a controller method, so
75
+ # we simply didn't call one.
76
+ #
77
+ # = Mapping Modules
78
+ #
79
+ # You may encapsulate sets of related rules into modules and simply include them into your
80
+ # mapping module. Some rule sets come packaged with Waves, such as PrettyUrls (rules for
81
+ # matching resources using names instead of ids). The simplest way to define such modules for
82
+ # reuse is by defining the +included+ class method for the rules module, and then define
83
+ # the rules using +module_eval+. See the PrettyUrls module for an example of how to do this.
84
+ #
85
+ # *Important:* Using pre-packaged mapping rules does not prevent you from adding to or
86
+ # overriding these rules. However, order does matter, so you should put your own rules
87
+ # ahead of those your may be importing. Also, place rules with constraints (for example,
88
+ # rules that require a POST) ahead of those with no constraints, otherwise the constrainted
89
+ # rules may never be called.
90
+
91
+ module Mapping
92
+
93
+ # If the pattern matches and constraints given by the options hash are satisfied, run the
94
+ # block before running any +path+ or +url+ actions. You can have as many +before+ matches
95
+ # as you want - they will all run, unless one of them calls redirect, generates an
96
+ # unhandled exception, etc.
97
+ def before( path, options = {}, &block )
98
+ if path.is_a? Hash
99
+ options = path
100
+ else
101
+ options[:path] = path
102
+ end
103
+ filters[:before] << [ options, block ]
104
+ end
105
+
106
+ # Similar to before, except it runs its actions after any matching +url+ or +path+ actions.
107
+ def after( path, options = {}, &block )
108
+ if path.is_a? Hash
109
+ options = path
110
+ else
111
+ options[:path] = path
112
+ end
113
+ filters[:after] << [ options, block ]
114
+ end
115
+
116
+ # Run the action before and after the matching +url+ or +path+ action.
117
+ def wrap( path, options = {}, &block )
118
+ if path.is_a? Hash
119
+ options = path
120
+ else
121
+ options[:path] = path
122
+ end
123
+ filters[:before] << [ options, block ]
124
+ filters[:after] << [ options, block ]
125
+ end
126
+
127
+ # Maps a request to a block. Don't use this method directly unless you know what
128
+ # you're doing. Use +path+ or +url+ instead.
129
+ def map( path, options = {}, params = {}, &block )
130
+ if path.is_a? Hash
131
+ params = options
132
+ options = path
133
+ else
134
+ options[:path] = path
135
+ end
136
+ mapping << [ options, params, block ]
137
+ end
138
+
139
+ # Match pattern against the +request.path+, along with satisfying any constraints
140
+ # specified by the options hash. If the pattern matches and the constraints are satisfied,
141
+ # run the block. Only one +path+ or +url+ match will be run (the first one).
142
+ def path( pat, options = {}, params = {}, &block )
143
+ options[:path] = pat; map( options, params, &block )
144
+ end
145
+
146
+ # Match pattern against the +request.url+, along with satisfying any constraints
147
+ # specified by the options hash. If the pattern matches and the constraints are satisfied,
148
+ # run the block. Only one +path+ or +url+ match will be run (the first one).
149
+ def url( pat, options = {}, params = {}, &block )
150
+ options[:url] = pat; map( options, params, &block )
151
+ end
152
+
153
+ # Maps the root of the application to a block. If an options hash is specified it must
154
+ # satisfy those constraints in order to run the block.
155
+ def root( options = {}, params = {}, &block )
156
+ path( %r{^/?$}, options, params, &block )
157
+ end
158
+
159
+ # Maps an exception handler to a block.
160
+ def handle(exception, options = {}, &block )
161
+ handlers << [exception,options, block]
162
+ end
163
+
164
+ # Maps a request to a block that will be executed within it's
165
+ # own thread. This is especially useful when you're running
166
+ # with an event driven server like thin or ebb, and this block
167
+ # is going to take a relatively long time.
168
+ def threaded( pat, options = {}, params = {}, &block)
169
+ params[:threaded] = true
170
+ map( pat, options, params, &block)
171
+ end
172
+
173
+ # Determines whether the request should be handled in a separate thread. This is used
174
+ # by event driven servers like thin and ebb, and is most useful for those methods that
175
+ # take a long time to complete, like for example upload processes. E.g.:
176
+ #
177
+ # threaded("/upload", :method => :post) do
178
+ # handle_upload
179
+ # end
180
+ #
181
+ # You typically wouldn't use this method directly.
182
+ def threaded?( request )
183
+ mapping.find do | options, params, function |
184
+ match = match( request, options, function )
185
+ return params[:threaded] == true if match
186
+ end
187
+ return false
188
+ end
189
+
190
+ # Match the given request against the defined rules. This is typically only called
191
+ # by a dispatcher object, so you shouldn't typically use it directly.
192
+ def []( request )
193
+
194
+ rx = { :before => [], :after => [], :action => nil, :handlers => [] }
195
+
196
+ ( filters[:before] + filters[:wrap] ).each do | options, function |
197
+ matches = match( request, options, function )
198
+ rx[:before] << matches if matches
199
+ end
200
+
201
+ mapping.find do | options, params, function |
202
+ rx[:action] = match( request, options, function )
203
+ break if rx[:action]
204
+ end
205
+
206
+ ( filters[:after] + filters[:wrap] ).each do | options, function |
207
+ matches = match( request, options, function )
208
+ rx[:after] << matches if matches
209
+ end
210
+
211
+ handlers.each do | exception, options, function |
212
+ matches = match( request, options, function )
213
+ rx[:handlers] << matches.unshift(exception) if matches
214
+ end
215
+
216
+ return rx
217
+ end
218
+
219
+ # Clear all mapping rules
220
+ def clear
221
+ @mapping = @filters = nil;
222
+ end
223
+
224
+ private
225
+
226
+ def mapping; @mapping ||= []; end
227
+
228
+ def filters; @filters ||= { :before => [], :after => [], :wrap => [] }; end
229
+
230
+ def handlers; @handlers ||= []; end
231
+
232
+ def match ( request, options, function )
233
+ return nil unless satisfy( request, options )
234
+ if options[:path]
235
+ matches = options[:path].match( request.path )
236
+ elsif options[:url]
237
+ matches = options[:url].match( request.url )
238
+ end
239
+ return [ function, matches ? matches[1..-1] : nil ]
240
+ end
241
+
242
+ def satisfy( request, options )
243
+ options.nil? or options.all? do |name,wanted|
244
+ got = request.send( name ) rescue request.env[ ( name =~ /^rack\./ ) ?
245
+ name.to_s.downcase : name.to_s.upcase ]
246
+ ( ( wanted.is_a?(Regexp) && wanted.match( got.to_s ) ) or
247
+ got.to_s == wanted.to_s ) unless ( wanted.nil? or got.nil? )
248
+ end
249
+ end
250
+ end
251
+
252
+ end