waves 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/waves +4 -3
- data/doc/VERSION +1 -1
- data/lib/waves.rb +52 -40
- data/lib/{caches → waves/caches}/file.rb +3 -1
- data/lib/waves/caches/memcached.rb +56 -0
- data/lib/{caches → waves/caches}/simple.rb +6 -7
- data/lib/{caches → waves/caches}/synchronized.rb +15 -1
- data/lib/{commands → waves/commands}/console.rb +4 -4
- data/lib/{commands → waves/commands}/generate.rb +6 -5
- data/lib/{commands → waves/commands}/help.rb +0 -0
- data/lib/{commands → waves/commands}/server.rb +1 -1
- data/lib/{dispatchers → waves/dispatchers}/base.rb +17 -31
- data/lib/waves/dispatchers/default.rb +19 -0
- data/lib/{ext → waves/ext}/float.rb +0 -0
- data/lib/{ext → waves/ext}/hash.rb +0 -0
- data/lib/{ext → waves/ext}/integer.rb +16 -1
- data/lib/{ext → waves/ext}/kernel.rb +3 -7
- data/lib/{ext → waves/ext}/module.rb +3 -3
- data/lib/{ext → waves/ext}/object.rb +2 -0
- data/lib/waves/ext/string.rb +73 -0
- data/lib/{ext → waves/ext}/symbol.rb +0 -1
- data/lib/{ext → waves/ext}/tempfile.rb +0 -0
- data/lib/waves/ext/time.rb +5 -0
- data/lib/{foundations → waves/foundations}/classic.rb +9 -21
- data/lib/{foundations → waves/foundations}/compact.rb +15 -20
- data/lib/waves/foundations/rest.rb +311 -0
- data/lib/waves/helpers/basic.rb +13 -0
- data/lib/{helpers → waves/helpers}/doc_type.rb +3 -0
- data/lib/waves/helpers/form.rb +94 -0
- data/lib/waves/helpers/formatting.rb +14 -0
- data/lib/waves/layers/mvc.rb +65 -0
- data/lib/{layers → waves/layers}/mvc/controllers.rb +0 -0
- data/lib/{layers → waves/layers}/mvc/extensions.rb +23 -11
- data/lib/{layers → waves/layers}/orm/migration.rb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/active_record.rb +2 -5
- data/lib/{layers → waves/layers}/orm/providers/active_record/migrations/empty.rb.erb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/active_record/tasks/generate.rb +1 -1
- data/lib/{layers → waves/layers}/orm/providers/active_record/tasks/schema.rb +1 -1
- data/lib/{layers → waves/layers}/orm/providers/data_mapper.rb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/filebase.rb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/sequel.rb +28 -29
- data/lib/{layers → waves/layers}/orm/providers/sequel/migrations/empty.rb.erb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/sequel/tasks/generate.rb +1 -1
- data/lib/{layers → waves/layers}/orm/providers/sequel/tasks/schema.rb +2 -0
- data/lib/waves/layers/rack/rack_cache.rb +32 -0
- data/lib/waves/layers/renderers/erubis.rb +52 -0
- data/lib/waves/layers/renderers/haml.rb +67 -0
- data/lib/waves/layers/renderers/markaby.rb +41 -0
- data/lib/waves/layers/text/inflect/english.rb +42 -0
- data/lib/waves/matchers/accept.rb +47 -0
- data/lib/waves/matchers/ext.rb +27 -0
- data/lib/waves/matchers/path.rb +72 -0
- data/lib/waves/matchers/query.rb +43 -0
- data/lib/waves/matchers/request.rb +86 -0
- data/lib/waves/matchers/requested.rb +31 -0
- data/lib/{matchers → waves/matchers}/resource.rb +8 -1
- data/lib/waves/matchers/traits.rb +30 -0
- data/lib/waves/matchers/uri.rb +69 -0
- data/lib/waves/media/mime_types.rb +542 -0
- data/lib/waves/renderers/mixin.rb +9 -0
- data/lib/waves/request/accept.rb +92 -0
- data/lib/{runtime → waves/request}/request.rb +77 -61
- data/lib/waves/resources/file_mixin.rb +11 -0
- data/lib/{resources → waves/resources}/mixin.rb +42 -44
- data/lib/waves/resources/paths.rb +132 -0
- data/lib/waves/response/client_errors.rb +10 -0
- data/lib/waves/response/packaged.rb +19 -0
- data/lib/waves/response/redirects.rb +35 -0
- data/lib/{runtime → waves/response}/response.rb +29 -11
- data/lib/{runtime → waves/response}/response_mixin.rb +30 -17
- data/lib/waves/runtime/applications.rb +18 -0
- data/lib/{runtime → waves/runtime}/configuration.rb +31 -25
- data/lib/waves/runtime/console.rb +24 -0
- data/lib/{runtime → waves/runtime}/logger.rb +3 -3
- data/lib/{runtime → waves/runtime}/mocks.rb +2 -2
- data/lib/waves/runtime/rackup.rb +37 -0
- data/lib/waves/runtime/runtime.rb +48 -0
- data/lib/waves/runtime/server.rb +33 -0
- data/lib/{servers → waves/servers}/base.rb +0 -0
- data/lib/{servers → waves/servers}/mongrel.rb +0 -0
- data/lib/{servers → waves/servers}/webrick.rb +0 -0
- data/lib/{tasks → waves/tasks}/gem.rb +0 -0
- data/lib/{tasks → waves/tasks}/generate.rb +0 -0
- data/lib/waves/views/cassy.rb +173 -0
- data/lib/{views → waves/views}/errors.rb +8 -7
- data/lib/waves/views/mixin.rb +23 -0
- data/lib/waves/views/templated.rb +40 -0
- data/samples/basic/basic_startup.rb +70 -0
- data/samples/basic/config.ru +9 -0
- data/samples/blog/configurations/development.rb +17 -16
- data/samples/blog/configurations/production.rb +0 -11
- data/samples/blog/resources/entry.rb +3 -3
- data/samples/blog/resources/map.rb +10 -3
- data/samples/blog/startup.rb +4 -3
- data/templates/classic/Rakefile +28 -29
- data/templates/classic/configurations/default.rb.erb +8 -3
- data/templates/classic/configurations/development.rb.erb +1 -20
- data/templates/classic/configurations/production.rb.erb +2 -16
- data/templates/classic/public/images/favicon.ico +0 -0
- data/templates/classic/resources/server.rb.erb +9 -0
- data/templates/classic/startup.rb.erb +3 -3
- data/templates/classic/views/css.rb.erb +14 -0
- data/templates/classic/views/default.rb.erb +17 -0
- data/templates/classic/views/errors.rb.erb +10 -0
- data/templates/classic/views/pages.rb.erb +14 -0
- data/templates/compact/startup.rb.erb +8 -3
- data/test/ext/object.rb +55 -0
- data/test/ext/shortcuts.rb +73 -0
- data/test/helpers.rb +17 -0
- data/test/match/accept.rb +78 -0
- data/test/match/ext.rb +156 -0
- data/test/match/methods.rb +22 -0
- data/test/match/params.rb +33 -0
- data/test/match/path.rb +106 -0
- data/test/match/query.rb +60 -0
- data/test/match/request.rb +91 -0
- data/test/match/requested.rb +149 -0
- data/test/match/uri.rb +136 -0
- data/test/process/request.rb +75 -0
- data/test/process/resource.rb +53 -0
- data/test/resources/path.rb +166 -0
- data/test/runtime/configurations.rb +19 -0
- data/test/runtime/request.rb +63 -0
- data/test/runtime/response.rb +85 -0
- data/test/views/views.rb +40 -0
- metadata +243 -157
- data/lib/caches/memcached.rb +0 -40
- data/lib/dispatchers/default.rb +0 -25
- data/lib/ext/string.rb +0 -20
- data/lib/helpers/basic.rb +0 -11
- data/lib/helpers/extended.rb +0 -21
- data/lib/helpers/form.rb +0 -42
- data/lib/helpers/formatting.rb +0 -30
- data/lib/helpers/layouts.rb +0 -37
- data/lib/helpers/model.rb +0 -37
- data/lib/helpers/view.rb +0 -22
- data/lib/layers/inflect/english.rb +0 -67
- data/lib/layers/mvc.rb +0 -54
- data/lib/layers/renderers/erubis.rb +0 -60
- data/lib/layers/renderers/haml.rb +0 -47
- data/lib/layers/renderers/markaby.rb +0 -29
- data/lib/matchers/accept.rb +0 -21
- data/lib/matchers/base.rb +0 -30
- data/lib/matchers/content_type.rb +0 -17
- data/lib/matchers/path.rb +0 -67
- data/lib/matchers/query.rb +0 -21
- data/lib/matchers/request.rb +0 -27
- data/lib/matchers/traits.rb +0 -19
- data/lib/matchers/uri.rb +0 -20
- data/lib/renderers/mixin.rb +0 -36
- data/lib/resources/paths.rb +0 -34
- data/lib/runtime/console.rb +0 -23
- data/lib/runtime/mime_types.rb +0 -536
- data/lib/runtime/monitor.rb +0 -32
- data/lib/runtime/runtime.rb +0 -67
- data/lib/runtime/server.rb +0 -20
- data/lib/runtime/session.rb +0 -27
- data/lib/runtime/worker.rb +0 -86
- data/lib/views/mixin.rb +0 -62
- data/samples/blog/blog.db +0 -0
- data/samples/blog/log/waves.production +0 -3
- data/templates/classic/resources/map.rb.erb +0 -8
- data/templates/classic/templates/errors/not_found_404.mab +0 -7
- data/templates/classic/templates/errors/server_error_500.mab +0 -7
- data/templates/classic/templates/layouts/default.mab +0 -14
- data/templates/classic/tmp/sessions/.gitignore +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Waves
|
|
2
|
+
class Accept < Array
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# RFC 2616 section 14.1.
|
|
6
|
+
#
|
|
7
|
+
# Returns an array of elements of the form:
|
|
8
|
+
#
|
|
9
|
+
# [ term, params ]
|
|
10
|
+
#
|
|
11
|
+
# where is term is an array of MIME type components,
|
|
12
|
+
# with the true value as the wildcard (*)
|
|
13
|
+
#
|
|
14
|
+
# and where params is a hash of parameters (q, level, etc.),
|
|
15
|
+
# where the q param is auto-converted to a float and
|
|
16
|
+
# defaulted to 1.0.
|
|
17
|
+
#
|
|
18
|
+
# sorted by q value and then specificity, with ties going
|
|
19
|
+
# to HTML-related media-types
|
|
20
|
+
#
|
|
21
|
+
#
|
|
22
|
+
# TODO parsing must be optimized. This parses Accept,
|
|
23
|
+
# lang and charset
|
|
24
|
+
#
|
|
25
|
+
def Accept.parse(str)
|
|
26
|
+
return self.new if str.nil?
|
|
27
|
+
self.new( Accept.sort( str.split(',').map { |term| Accept.parse_media_type( term ) } ).uniq )
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def Accept.parse_media_type( term )
|
|
31
|
+
t, *p = term.to_s.split(';').map(&:strip)
|
|
32
|
+
[ Accept.parse_media_range( t ), Accept.convert_params_to_hash( p ) ]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def Accept.parse_media_range( t )
|
|
36
|
+
t.split('/').map(&:strip).map { |x| x=='*' ? true : x }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def Accept.convert_params_to_hash( p )
|
|
40
|
+
rval = p.inject({}) { |h,p|
|
|
41
|
+
k,v = p.split('=').map(&:strip)
|
|
42
|
+
( v = v.to_f ) if k == 'q'
|
|
43
|
+
h[k] = v ; h
|
|
44
|
+
}
|
|
45
|
+
rval['q'] ||= 1.0
|
|
46
|
+
rval
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def Accept.sort( terms )
|
|
50
|
+
terms.sort { |t1,t2|
|
|
51
|
+
# first compare on quality
|
|
52
|
+
c = t2[1]['q'] <=> t1[1]['q']
|
|
53
|
+
# next compare on specificity of the media type
|
|
54
|
+
c = t2[0].size <=> t1[0].size if ( c == 0 )
|
|
55
|
+
# finally, compare on specificity of parameters
|
|
56
|
+
c = t2[1].size <=> t1[1].size if ( c == 0 )
|
|
57
|
+
c
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def Accept.to_media_type( term )
|
|
62
|
+
term.first.map { |x| x==true ? '*' : x }.join("/")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def =~(arg) ; self.include? arg ; end
|
|
66
|
+
def ===(arg) ; self.include? arg ; end
|
|
67
|
+
|
|
68
|
+
# Check these Accepts against constraints.
|
|
69
|
+
#
|
|
70
|
+
def include?(arg)
|
|
71
|
+
# recursively test for any possibility if we get an array
|
|
72
|
+
# thus you can match against, say, %w( png jpg gif )
|
|
73
|
+
return arg.any? {|pat| self.include? pat } if arg.kind_of? Array
|
|
74
|
+
term = Accept.parse_media_type( arg ).first
|
|
75
|
+
self.map(&:first).any? { | type, subtype |
|
|
76
|
+
case term
|
|
77
|
+
when [ type ], [ subtype ], [ true ],
|
|
78
|
+
[ type, subtype ], [ type, true ],
|
|
79
|
+
[ true, true ] then true
|
|
80
|
+
else false
|
|
81
|
+
end
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Again, we play favorites here: in the absence of any accept header
|
|
86
|
+
# we go with 'text/html' as our favorite
|
|
87
|
+
def preferred_media_type
|
|
88
|
+
Accept.to_media_type( first ) || 'text/html'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Waves
|
|
2
|
-
|
|
3
|
-
# Waves::Request represents an HTTP request and provides convenient methods for accessing request attributes.
|
|
2
|
+
|
|
3
|
+
# Waves::Request represents an HTTP request and provides convenient methods for accessing request attributes.
|
|
4
4
|
# See Rack::Request for documentation of any method not defined here.
|
|
5
5
|
|
|
6
6
|
class Request
|
|
@@ -13,26 +13,44 @@ module Waves
|
|
|
13
13
|
|
|
14
14
|
# Create a new request. Takes a env parameter representing the request passed in from Rack.
|
|
15
15
|
# You shouldn't need to call this directly.
|
|
16
|
-
|
|
16
|
+
#
|
|
17
|
+
def initialize(env)
|
|
17
18
|
@traits = Class.new { include Attributes }.new( :waves => {} )
|
|
18
|
-
@request = Rack::Request.new(
|
|
19
|
+
@request = Rack::Request.new(env).freeze
|
|
19
20
|
@response = Waves::Response.new( self )
|
|
20
|
-
@session = Waves::Session.new( self )
|
|
21
21
|
end
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
|
|
23
|
+
# Rack request object.
|
|
24
|
+
#
|
|
25
|
+
def rack_request()
|
|
26
|
+
@request
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
# Methods delegated directly to rack
|
|
26
30
|
%w( url scheme host port body query_string content_type media_type content_length referer ).each do |m|
|
|
27
31
|
define_method( m ) { @request.send( m ) }
|
|
28
32
|
end
|
|
29
33
|
|
|
34
|
+
# access common HTTP headers as methods
|
|
35
|
+
%w( user_agent cache_control ).each do |name|
|
|
36
|
+
key = "HTTP_#{name.to_s.upcase}"
|
|
37
|
+
define_method( name ) { @request.env[ key ] if @request.env.has_key?( key ) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def if_modified_since
|
|
41
|
+
@if_modified_since ||=
|
|
42
|
+
( Time.parse( @request.env[ 'HTTP_IF_MODIFIED_SINCE' ] ) if
|
|
43
|
+
@request.env.has_key?( 'HTTP_IF_MODIFIED_SINCE' ) )
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def []( key ) ; @request.env[ key.to_s ] ; end
|
|
47
|
+
|
|
30
48
|
# The request path (PATH_INFO). Ex: +/entry/2008-01-17+
|
|
31
49
|
def path ; @request.path_info ; end
|
|
32
50
|
|
|
33
51
|
# Access to "params" - aka the query string - as a hash
|
|
34
52
|
def query ; @request.params ; end
|
|
35
|
-
|
|
53
|
+
|
|
36
54
|
alias_method :params, :query
|
|
37
55
|
alias_method :domain, :host
|
|
38
56
|
|
|
@@ -45,67 +63,65 @@ module Waves
|
|
|
45
63
|
# field named '_method' and a value with 'PUT' or 'DELETE'. Also
|
|
46
64
|
# accepted is when a query parameter named '_method' is provided.
|
|
47
65
|
def method
|
|
48
|
-
@method ||= ( ( ( m = @request.request_method.downcase ) == 'post' and
|
|
66
|
+
@method ||= ( ( ( m = @request.request_method.downcase ) == 'post' and
|
|
49
67
|
( n = @request['_method'] ) ) ? n.downcase : m ).intern
|
|
50
68
|
end
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def method_missing( name, *args, &body )
|
|
56
|
-
return super unless args.empty? and body.nil?
|
|
57
|
-
key = "HTTP_#{name.to_s.upcase}"
|
|
58
|
-
@request.env[ key ] if @request.env.has_key?( key )
|
|
69
|
+
|
|
70
|
+
# Requested representation MIME type
|
|
71
|
+
def accept()
|
|
72
|
+
@accept ||= Accept.parse(@request.env['HTTP_ACCEPT'])
|
|
59
73
|
end
|
|
60
74
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
# Combination of Accept and file extension for matching.
|
|
76
|
+
#
|
|
77
|
+
# A file extension takes precedence over the Accept
|
|
78
|
+
# header, the Accept is ignored.
|
|
79
|
+
#
|
|
80
|
+
# The absence of a file extension is indicated using
|
|
81
|
+
# the special MIME type MimeTypes::Unspecified, which
|
|
82
|
+
# allows specialised handling thereof. The resource
|
|
83
|
+
# must specifically accept Unspecified for it to have
|
|
84
|
+
# an effect.
|
|
85
|
+
#
|
|
86
|
+
# @see matchers/requested.rb
|
|
87
|
+
# @see #accept
|
|
88
|
+
# @see #ext
|
|
89
|
+
# @see runtime/mime_types.rb for the actual definition
|
|
90
|
+
# of the Unspecified type.
|
|
91
|
+
#
|
|
92
|
+
def requested()
|
|
93
|
+
@requested ||= ( extension ? Accept.new( Accept.parse( MimeTypes[ extension ].join(",") ) + accept ).uniq : accept )
|
|
64
94
|
end
|
|
65
95
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
# Requested charset(s).
|
|
97
|
+
#
|
|
98
|
+
# @see matchers/accept.rb
|
|
99
|
+
#
|
|
100
|
+
def accept_charset()
|
|
101
|
+
@charset ||= Accept.parse(@request.env['HTTP_ACCEPT_CHARSET'])
|
|
69
102
|
end
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if arg.size == 1 # implicit wildcard in arg
|
|
83
|
-
arg[0] == entry[0] or arg[0] == entry[1]
|
|
84
|
-
else
|
|
85
|
-
arg == entry
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def self.parse(string)
|
|
91
|
-
string.split(',').inject(self.new) { |a, entry| a << entry.split( ';' ).first.strip; a }
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def default
|
|
95
|
-
return 'text/html' if self.include?('text/html')
|
|
96
|
-
find { |entry| ! entry.match(/\*/) } || 'text/html'
|
|
97
|
-
end
|
|
98
|
-
|
|
103
|
+
|
|
104
|
+
# Requested language(s).
|
|
105
|
+
#
|
|
106
|
+
# @see matchers/accept.rb
|
|
107
|
+
#
|
|
108
|
+
def accept_language()
|
|
109
|
+
@lang ||= Accept.parse(@request.env['HTTP_ACCEPT_LANGUAGE'])
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# File extension of path, with leading dot
|
|
113
|
+
def extension
|
|
114
|
+
@ext ||= ( ( e = File.extname( path ) ).empty? ? nil : e )
|
|
99
115
|
end
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
alias :ext :extension
|
|
118
|
+
|
|
119
|
+
def basename
|
|
120
|
+
@basename ||= File.basename( path )
|
|
121
|
+
end
|
|
106
122
|
|
|
107
123
|
module Utilities
|
|
108
|
-
|
|
124
|
+
|
|
109
125
|
def self.destructure( hash )
|
|
110
126
|
destructured = {}
|
|
111
127
|
hash.keys.map { |key| key.split('.') }.each do |keys|
|
|
@@ -136,9 +152,9 @@ module Waves
|
|
|
136
152
|
destructure_with_array_keys( hash, new_prefix, keys, destructured )
|
|
137
153
|
end
|
|
138
154
|
end
|
|
139
|
-
|
|
155
|
+
|
|
140
156
|
end
|
|
141
|
-
|
|
157
|
+
|
|
142
158
|
end
|
|
143
159
|
|
|
144
160
|
end
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
module Waves
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
module Resources
|
|
4
|
-
|
|
5
|
-
StatusCodes = {
|
|
6
|
-
Waves::Dispatchers::NotFoundError => 404
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
|
|
10
5
|
module Mixin
|
|
11
|
-
|
|
6
|
+
|
|
12
7
|
attr_reader :request
|
|
13
|
-
|
|
8
|
+
|
|
14
9
|
module ClassMethods
|
|
15
|
-
|
|
10
|
+
|
|
16
11
|
def paths
|
|
17
12
|
unless @paths
|
|
18
13
|
resource = self
|
|
@@ -46,7 +41,7 @@ module Waves
|
|
|
46
41
|
methods.each do | method |
|
|
47
42
|
functor_with_self( method, matcher, &block )
|
|
48
43
|
end
|
|
49
|
-
paths.module_eval {
|
|
44
|
+
paths.module_eval {
|
|
50
45
|
define_method( generator ) { | *args | generate( path, args ) }
|
|
51
46
|
} if generator
|
|
52
47
|
end
|
|
@@ -62,71 +57,74 @@ module Waves
|
|
|
62
57
|
end
|
|
63
58
|
def handler( exception, &block ) ; functor( :handler, exception, &block ) ; end
|
|
64
59
|
def always( &block ) ; define_method( :always, &block ) ; end
|
|
65
|
-
|
|
60
|
+
|
|
66
61
|
end
|
|
67
62
|
|
|
68
63
|
# this is necessary because you can't define functors within a module because the functor attempts
|
|
69
64
|
# to incorporate the superclass functor table into it's own
|
|
70
65
|
def self.included( resource )
|
|
71
|
-
|
|
72
|
-
resource.module_eval do
|
|
73
|
-
|
|
66
|
+
|
|
67
|
+
resource.module_eval do
|
|
68
|
+
|
|
74
69
|
include ResponseMixin, Functor::Method ; extend ClassMethods
|
|
75
70
|
|
|
76
71
|
def initialize( request ); @request = request ; end
|
|
77
|
-
|
|
72
|
+
|
|
73
|
+
# define defaults for all the functors, providing the analog
|
|
74
|
+
# of "not implemented" behaviors. this avoids complicating
|
|
75
|
+
# the error handling with having to distinguish between
|
|
76
|
+
# functor match-related errors and actual application errors
|
|
77
|
+
|
|
78
|
+
# by default, don't do anything in the wrapper methods
|
|
79
|
+
before {} ; after {} ; always {}
|
|
80
|
+
|
|
81
|
+
# if we get here, this is a 404
|
|
82
|
+
%w( post get put delete head ).each do | method |
|
|
83
|
+
on( method ) { not_found }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# default handler is just to propagate the exception
|
|
87
|
+
handler( Exception ) { |e| raise( e ) }
|
|
88
|
+
|
|
78
89
|
def process
|
|
79
90
|
begin
|
|
80
|
-
before ;
|
|
81
|
-
rescue Waves::Dispatchers::Redirect => e
|
|
82
|
-
raise e
|
|
91
|
+
before ; rval = send( request.method ) ; after
|
|
83
92
|
rescue Exception => e
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Waves::Logger.warn e.to_s
|
|
87
|
-
e.backtrace.each { |t| Waves::Logger.debug " #{t}" }
|
|
93
|
+
e.call( response ) if e.respond_to?( :call )
|
|
94
|
+
rval = handler( e )
|
|
88
95
|
ensure
|
|
89
96
|
always
|
|
90
97
|
end
|
|
91
|
-
|
|
98
|
+
# note: the dispatcher decides what to do with the
|
|
99
|
+
# return value; all we care about is returning the
|
|
100
|
+
# value from the appropriate application block
|
|
101
|
+
return rval
|
|
92
102
|
end
|
|
93
|
-
|
|
103
|
+
|
|
94
104
|
def to( resource )
|
|
95
105
|
resource = case resource
|
|
96
106
|
when Base
|
|
97
107
|
resource
|
|
98
108
|
when Symbol, String
|
|
99
|
-
begin
|
|
100
|
-
Waves.main::Resources[ resource ]
|
|
101
|
-
rescue NameError
|
|
102
|
-
raise Waves::Dispatchers::NotFoundError
|
|
103
|
-
end
|
|
104
109
|
Waves.main::Resources[ resource ]
|
|
105
110
|
end
|
|
106
111
|
r = traits.waves.resource = resource.new( request )
|
|
107
112
|
r.process
|
|
108
113
|
end
|
|
109
|
-
|
|
114
|
+
|
|
110
115
|
def redirect( path ) ; request.redirect( path ) ; end
|
|
111
|
-
|
|
112
|
-
# override for resources that may have long-running requests. this helps servers
|
|
116
|
+
|
|
117
|
+
# override for resources that may have long-running requests. this helps servers
|
|
113
118
|
# determine how to handle the request
|
|
114
119
|
def deferred? ; false ; end
|
|
115
|
-
|
|
116
|
-
before {} ; after {} ; always {}
|
|
117
|
-
# handler( Waves::Dispatchers::Redirect ) { |e| raise e }
|
|
118
|
-
|
|
119
|
-
%w( post get put delete head ).each do | method |
|
|
120
|
-
on( method ) { not_found }
|
|
121
|
-
end
|
|
122
|
-
|
|
120
|
+
|
|
123
121
|
end
|
|
124
|
-
|
|
122
|
+
|
|
125
123
|
end
|
|
126
124
|
|
|
127
125
|
end
|
|
128
|
-
|
|
129
|
-
class Base ; include Mixin ; end
|
|
126
|
+
|
|
127
|
+
class Base ; include Mixin ; end
|
|
130
128
|
|
|
131
129
|
end
|
|
132
130
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module Waves
|
|
2
|
+
|
|
3
|
+
module Resources
|
|
4
|
+
|
|
5
|
+
class Paths
|
|
6
|
+
|
|
7
|
+
def self.compiled; @compiled ||= {} ; end
|
|
8
|
+
|
|
9
|
+
def compiled_paths; self.class.compiled ; end
|
|
10
|
+
|
|
11
|
+
def generate( template, args )
|
|
12
|
+
return "/" if template.empty?
|
|
13
|
+
if template.is_a? Array
|
|
14
|
+
if args.size == 1 and args.first.is_a? Hash
|
|
15
|
+
process_hash( template, args.first )
|
|
16
|
+
else
|
|
17
|
+
process_array( template, args)
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
"/#{ args * '/' }"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def process_array( template, args )
|
|
25
|
+
template_key = template
|
|
26
|
+
compiled = compiled_paths[template_key]
|
|
27
|
+
if compiled
|
|
28
|
+
return ( compiled % args ) rescue raise [template, args].inspect
|
|
29
|
+
end
|
|
30
|
+
compilable = true
|
|
31
|
+
cpath, interpolations = "", []
|
|
32
|
+
result = ( cpath % interpolations ) if template.all? do | want |
|
|
33
|
+
case want
|
|
34
|
+
when Symbol
|
|
35
|
+
cpath << "/%s" ; interpolations << args.shift
|
|
36
|
+
when String
|
|
37
|
+
cpath << "/#{want}"
|
|
38
|
+
when true
|
|
39
|
+
compilable = false
|
|
40
|
+
cpath += "/#{args.join("/")}"; args = []
|
|
41
|
+
when Hash
|
|
42
|
+
compilable = false
|
|
43
|
+
key, value = want.to_a.first
|
|
44
|
+
case value
|
|
45
|
+
when true
|
|
46
|
+
cpath += "/#{args.join("/")}"; args = []
|
|
47
|
+
when String, Symbol
|
|
48
|
+
compilable = true
|
|
49
|
+
component = args.shift
|
|
50
|
+
cpath << "/%s"
|
|
51
|
+
component ? interpolations << component : interpolations << value
|
|
52
|
+
when Regexp
|
|
53
|
+
component = args.shift.to_s
|
|
54
|
+
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ value
|
|
55
|
+
cpath << "/%s"; interpolations << component
|
|
56
|
+
end
|
|
57
|
+
when Regexp
|
|
58
|
+
compilable = false
|
|
59
|
+
component = args.shift.to_s
|
|
60
|
+
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ want
|
|
61
|
+
cpath << "/%s"; interpolations << component
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
raise ArgumentError, "Too many args" unless args.empty?
|
|
65
|
+
compiled_paths[template_key] = cpath if compilable
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def process_hash( template, hash )
|
|
70
|
+
path = []
|
|
71
|
+
( "/#{ path * '/' }" ) if template.all? do |want|
|
|
72
|
+
case want
|
|
73
|
+
when Symbol
|
|
74
|
+
raise ArgumentError, "Path generator needs a value for #{want.inspect}" unless component = hash[want]
|
|
75
|
+
path << component
|
|
76
|
+
when String then path << want
|
|
77
|
+
when Hash
|
|
78
|
+
key, value = want.to_a.first
|
|
79
|
+
case value
|
|
80
|
+
when Regexp
|
|
81
|
+
raise ArgumentError, "Path generator needs a value for #{want.inspect}" unless component = hash[key]
|
|
82
|
+
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ value
|
|
83
|
+
path << component
|
|
84
|
+
when String, Symbol
|
|
85
|
+
hash.has_key?(key) ? path << hash[key] : path << value
|
|
86
|
+
when true
|
|
87
|
+
raise ArgumentError, "Path generator needs a value for #{want.inspect}" unless component = hash[key]
|
|
88
|
+
path += [component].flatten
|
|
89
|
+
end
|
|
90
|
+
when Regexp, true
|
|
91
|
+
raise ArgumentError, "Path generator can't take an args hash, as it contains a Regexp or the value true"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def original_generate( template, args )
|
|
97
|
+
if template.is_a? Array and not template.empty?
|
|
98
|
+
path = []
|
|
99
|
+
( "/#{ path * '/' }" ) if template.all? do | want |
|
|
100
|
+
case want
|
|
101
|
+
when true then path += args
|
|
102
|
+
when String then path << want
|
|
103
|
+
when Symbol then path << args.shift
|
|
104
|
+
when Regexp
|
|
105
|
+
component = args.shift.to_s
|
|
106
|
+
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ want
|
|
107
|
+
path << component
|
|
108
|
+
when Hash
|
|
109
|
+
key, value = want.to_a.first
|
|
110
|
+
case value
|
|
111
|
+
when true then path += args
|
|
112
|
+
when String, Symbol
|
|
113
|
+
# if no args to interpolate, use hash element value as default
|
|
114
|
+
!args.empty? ? path << args.shift : path << value
|
|
115
|
+
when Regexp
|
|
116
|
+
component = args.shift.to_s
|
|
117
|
+
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ value
|
|
118
|
+
path << component
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
"/#{ args * '/' }"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|