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.
Files changed (166) hide show
  1. data/bin/waves +4 -3
  2. data/doc/VERSION +1 -1
  3. data/lib/waves.rb +52 -40
  4. data/lib/{caches → waves/caches}/file.rb +3 -1
  5. data/lib/waves/caches/memcached.rb +56 -0
  6. data/lib/{caches → waves/caches}/simple.rb +6 -7
  7. data/lib/{caches → waves/caches}/synchronized.rb +15 -1
  8. data/lib/{commands → waves/commands}/console.rb +4 -4
  9. data/lib/{commands → waves/commands}/generate.rb +6 -5
  10. data/lib/{commands → waves/commands}/help.rb +0 -0
  11. data/lib/{commands → waves/commands}/server.rb +1 -1
  12. data/lib/{dispatchers → waves/dispatchers}/base.rb +17 -31
  13. data/lib/waves/dispatchers/default.rb +19 -0
  14. data/lib/{ext → waves/ext}/float.rb +0 -0
  15. data/lib/{ext → waves/ext}/hash.rb +0 -0
  16. data/lib/{ext → waves/ext}/integer.rb +16 -1
  17. data/lib/{ext → waves/ext}/kernel.rb +3 -7
  18. data/lib/{ext → waves/ext}/module.rb +3 -3
  19. data/lib/{ext → waves/ext}/object.rb +2 -0
  20. data/lib/waves/ext/string.rb +73 -0
  21. data/lib/{ext → waves/ext}/symbol.rb +0 -1
  22. data/lib/{ext → waves/ext}/tempfile.rb +0 -0
  23. data/lib/waves/ext/time.rb +5 -0
  24. data/lib/{foundations → waves/foundations}/classic.rb +9 -21
  25. data/lib/{foundations → waves/foundations}/compact.rb +15 -20
  26. data/lib/waves/foundations/rest.rb +311 -0
  27. data/lib/waves/helpers/basic.rb +13 -0
  28. data/lib/{helpers → waves/helpers}/doc_type.rb +3 -0
  29. data/lib/waves/helpers/form.rb +94 -0
  30. data/lib/waves/helpers/formatting.rb +14 -0
  31. data/lib/waves/layers/mvc.rb +65 -0
  32. data/lib/{layers → waves/layers}/mvc/controllers.rb +0 -0
  33. data/lib/{layers → waves/layers}/mvc/extensions.rb +23 -11
  34. data/lib/{layers → waves/layers}/orm/migration.rb +0 -0
  35. data/lib/{layers → waves/layers}/orm/providers/active_record.rb +2 -5
  36. data/lib/{layers → waves/layers}/orm/providers/active_record/migrations/empty.rb.erb +0 -0
  37. data/lib/{layers → waves/layers}/orm/providers/active_record/tasks/generate.rb +1 -1
  38. data/lib/{layers → waves/layers}/orm/providers/active_record/tasks/schema.rb +1 -1
  39. data/lib/{layers → waves/layers}/orm/providers/data_mapper.rb +0 -0
  40. data/lib/{layers → waves/layers}/orm/providers/filebase.rb +0 -0
  41. data/lib/{layers → waves/layers}/orm/providers/sequel.rb +28 -29
  42. data/lib/{layers → waves/layers}/orm/providers/sequel/migrations/empty.rb.erb +0 -0
  43. data/lib/{layers → waves/layers}/orm/providers/sequel/tasks/generate.rb +1 -1
  44. data/lib/{layers → waves/layers}/orm/providers/sequel/tasks/schema.rb +2 -0
  45. data/lib/waves/layers/rack/rack_cache.rb +32 -0
  46. data/lib/waves/layers/renderers/erubis.rb +52 -0
  47. data/lib/waves/layers/renderers/haml.rb +67 -0
  48. data/lib/waves/layers/renderers/markaby.rb +41 -0
  49. data/lib/waves/layers/text/inflect/english.rb +42 -0
  50. data/lib/waves/matchers/accept.rb +47 -0
  51. data/lib/waves/matchers/ext.rb +27 -0
  52. data/lib/waves/matchers/path.rb +72 -0
  53. data/lib/waves/matchers/query.rb +43 -0
  54. data/lib/waves/matchers/request.rb +86 -0
  55. data/lib/waves/matchers/requested.rb +31 -0
  56. data/lib/{matchers → waves/matchers}/resource.rb +8 -1
  57. data/lib/waves/matchers/traits.rb +30 -0
  58. data/lib/waves/matchers/uri.rb +69 -0
  59. data/lib/waves/media/mime_types.rb +542 -0
  60. data/lib/waves/renderers/mixin.rb +9 -0
  61. data/lib/waves/request/accept.rb +92 -0
  62. data/lib/{runtime → waves/request}/request.rb +77 -61
  63. data/lib/waves/resources/file_mixin.rb +11 -0
  64. data/lib/{resources → waves/resources}/mixin.rb +42 -44
  65. data/lib/waves/resources/paths.rb +132 -0
  66. data/lib/waves/response/client_errors.rb +10 -0
  67. data/lib/waves/response/packaged.rb +19 -0
  68. data/lib/waves/response/redirects.rb +35 -0
  69. data/lib/{runtime → waves/response}/response.rb +29 -11
  70. data/lib/{runtime → waves/response}/response_mixin.rb +30 -17
  71. data/lib/waves/runtime/applications.rb +18 -0
  72. data/lib/{runtime → waves/runtime}/configuration.rb +31 -25
  73. data/lib/waves/runtime/console.rb +24 -0
  74. data/lib/{runtime → waves/runtime}/logger.rb +3 -3
  75. data/lib/{runtime → waves/runtime}/mocks.rb +2 -2
  76. data/lib/waves/runtime/rackup.rb +37 -0
  77. data/lib/waves/runtime/runtime.rb +48 -0
  78. data/lib/waves/runtime/server.rb +33 -0
  79. data/lib/{servers → waves/servers}/base.rb +0 -0
  80. data/lib/{servers → waves/servers}/mongrel.rb +0 -0
  81. data/lib/{servers → waves/servers}/webrick.rb +0 -0
  82. data/lib/{tasks → waves/tasks}/gem.rb +0 -0
  83. data/lib/{tasks → waves/tasks}/generate.rb +0 -0
  84. data/lib/waves/views/cassy.rb +173 -0
  85. data/lib/{views → waves/views}/errors.rb +8 -7
  86. data/lib/waves/views/mixin.rb +23 -0
  87. data/lib/waves/views/templated.rb +40 -0
  88. data/samples/basic/basic_startup.rb +70 -0
  89. data/samples/basic/config.ru +9 -0
  90. data/samples/blog/configurations/development.rb +17 -16
  91. data/samples/blog/configurations/production.rb +0 -11
  92. data/samples/blog/resources/entry.rb +3 -3
  93. data/samples/blog/resources/map.rb +10 -3
  94. data/samples/blog/startup.rb +4 -3
  95. data/templates/classic/Rakefile +28 -29
  96. data/templates/classic/configurations/default.rb.erb +8 -3
  97. data/templates/classic/configurations/development.rb.erb +1 -20
  98. data/templates/classic/configurations/production.rb.erb +2 -16
  99. data/templates/classic/public/images/favicon.ico +0 -0
  100. data/templates/classic/resources/server.rb.erb +9 -0
  101. data/templates/classic/startup.rb.erb +3 -3
  102. data/templates/classic/views/css.rb.erb +14 -0
  103. data/templates/classic/views/default.rb.erb +17 -0
  104. data/templates/classic/views/errors.rb.erb +10 -0
  105. data/templates/classic/views/pages.rb.erb +14 -0
  106. data/templates/compact/startup.rb.erb +8 -3
  107. data/test/ext/object.rb +55 -0
  108. data/test/ext/shortcuts.rb +73 -0
  109. data/test/helpers.rb +17 -0
  110. data/test/match/accept.rb +78 -0
  111. data/test/match/ext.rb +156 -0
  112. data/test/match/methods.rb +22 -0
  113. data/test/match/params.rb +33 -0
  114. data/test/match/path.rb +106 -0
  115. data/test/match/query.rb +60 -0
  116. data/test/match/request.rb +91 -0
  117. data/test/match/requested.rb +149 -0
  118. data/test/match/uri.rb +136 -0
  119. data/test/process/request.rb +75 -0
  120. data/test/process/resource.rb +53 -0
  121. data/test/resources/path.rb +166 -0
  122. data/test/runtime/configurations.rb +19 -0
  123. data/test/runtime/request.rb +63 -0
  124. data/test/runtime/response.rb +85 -0
  125. data/test/views/views.rb +40 -0
  126. metadata +243 -157
  127. data/lib/caches/memcached.rb +0 -40
  128. data/lib/dispatchers/default.rb +0 -25
  129. data/lib/ext/string.rb +0 -20
  130. data/lib/helpers/basic.rb +0 -11
  131. data/lib/helpers/extended.rb +0 -21
  132. data/lib/helpers/form.rb +0 -42
  133. data/lib/helpers/formatting.rb +0 -30
  134. data/lib/helpers/layouts.rb +0 -37
  135. data/lib/helpers/model.rb +0 -37
  136. data/lib/helpers/view.rb +0 -22
  137. data/lib/layers/inflect/english.rb +0 -67
  138. data/lib/layers/mvc.rb +0 -54
  139. data/lib/layers/renderers/erubis.rb +0 -60
  140. data/lib/layers/renderers/haml.rb +0 -47
  141. data/lib/layers/renderers/markaby.rb +0 -29
  142. data/lib/matchers/accept.rb +0 -21
  143. data/lib/matchers/base.rb +0 -30
  144. data/lib/matchers/content_type.rb +0 -17
  145. data/lib/matchers/path.rb +0 -67
  146. data/lib/matchers/query.rb +0 -21
  147. data/lib/matchers/request.rb +0 -27
  148. data/lib/matchers/traits.rb +0 -19
  149. data/lib/matchers/uri.rb +0 -20
  150. data/lib/renderers/mixin.rb +0 -36
  151. data/lib/resources/paths.rb +0 -34
  152. data/lib/runtime/console.rb +0 -23
  153. data/lib/runtime/mime_types.rb +0 -536
  154. data/lib/runtime/monitor.rb +0 -32
  155. data/lib/runtime/runtime.rb +0 -67
  156. data/lib/runtime/server.rb +0 -20
  157. data/lib/runtime/session.rb +0 -27
  158. data/lib/runtime/worker.rb +0 -86
  159. data/lib/views/mixin.rb +0 -62
  160. data/samples/blog/blog.db +0 -0
  161. data/samples/blog/log/waves.production +0 -3
  162. data/templates/classic/resources/map.rb.erb +0 -8
  163. data/templates/classic/templates/errors/not_found_404.mab +0 -7
  164. data/templates/classic/templates/errors/server_error_500.mab +0 -7
  165. data/templates/classic/templates/layouts/default.mab +0 -14
  166. data/templates/classic/tmp/sessions/.gitignore +0 -0
@@ -0,0 +1,9 @@
1
+ module Waves
2
+ module Renderers
3
+ module Mixin
4
+
5
+ # Placeholder
6
+
7
+ end
8
+ end
9
+ end
@@ -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
- def initialize( env )
16
+ #
17
+ def initialize(env)
17
18
  @traits = Class.new { include Attributes }.new( :waves => {} )
18
- @request = Rack::Request.new( env ).freeze
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
- def rack_request; @request; end
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
- def []( key ) ; @request.env[ key.to_s.upcase ] ; end
53
-
54
- # access HTTP headers as methods
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
- # Raise a not found exception.
62
- def not_found
63
- raise Waves::Dispatchers::NotFoundError, "#{@request.url} not found."
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
- # Issue a redirect for the given path.
67
- def redirect( path, status = '302' )
68
- raise Waves::Dispatchers::Redirect.new( path, status )
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
- class Accept < Array
72
-
73
- def =~(arg) ; self.include? arg ; end
74
- def ===(arg) ; self.include? arg ; end
75
-
76
- def include?(arg)
77
- return arg.any? { |pat| self.include?( pat ) } if arg.is_a? Array
78
- arg = arg.to_s.split('/')
79
- self.any? do |entry|
80
- false if entry == '*/*' or entry == '*'
81
- entry = entry.split('/')
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
- # this is a hack - need to incorporate browser variations for "accept" here ...
102
- # def accept ; Accept.parse(@request.env['HTTP_ACCEPT']).unshift( Waves.config.mime_types[ path ] ).compact.uniq ; end
103
- def accept ; @accept ||= Accept.parse( Waves.config.mime_types[ path.downcase ] || 'text/html' ) ; end
104
- def accept_charset ; @charset ||= Accept.parse(@request.env['HTTP_ACCEPT_CHARSET']) ; end
105
- def accept_language ; @lang ||= Accept.parse(@request.env['HTTP_ACCEPT_LANGUAGE']) ; end
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
@@ -0,0 +1,11 @@
1
+ module Waves::Resources::FileMixin
2
+
3
+ def load_from_file( path )
4
+ if File.exist?( path )
5
+ http_cache( File.mtime( path ) ) {
6
+ File.read( path )
7
+ }
8
+ end
9
+ end
10
+
11
+ 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 ; body = send( request.method ) ; after
81
- rescue Waves::Dispatchers::Redirect => e
82
- raise e
91
+ before ; rval = send( request.method ) ; after
83
92
  rescue Exception => e
84
- response.status = ( StatusCodes[ e.class ] || 500 )
85
- ( body = handler( e ) ) rescue raise e
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
- return body
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