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
@@ -1,20 +1,35 @@
1
1
  module Waves
2
2
  module Ext
3
3
  module Integer
4
+ # @todo: we need to credit where this code came from originally, if anywhere.
4
5
  def seconds ; self ; end
6
+ alias_method :second, :seconds
5
7
  def minutes ; self * 60 ; end
8
+ alias_method :minute, :minutes
6
9
  def hours ; self * 60.minutes ; end
10
+ alias_method :hour, :hours
7
11
  def days ; self * 24.hours ; end
12
+ alias_method :day, :days
8
13
  def weeks ; self * 7.days ; end
14
+ alias_method :week, :weeks
9
15
  def bytes ; self ; end
16
+ alias_method :byte, :bytes
10
17
  def kilobytes ; self * 1024 ; end
18
+ alias_method :kilobyte, :kilobytes
11
19
  def megabytes ; self * 1024.kilobytes ; end
20
+ alias_method :megabyte, :megabytes
12
21
  def gigabytes ; self * 1024.megabytes ; end
22
+ alias_method :gigabyte, :gigabytes
13
23
  def terabytes ; self * 1024.gigabytes ; end
24
+ alias_method :terabyte, :terabytes
14
25
  def petabytes ; self * 1024.terabytes ; end
26
+ alias_method :petabyte, :petabytes
15
27
  def exabytes ; self * 1024.petabytes ; end
28
+ alias_method :exabyte, :exabytes
16
29
  def zettabytes ; self * 1024.exabytes ; end
30
+ alias_method :zettabyte, :zettabytes
17
31
  def yottabytes ; self * 1024.zettabytes ; end
32
+ alias_method :yottabyte, :yottabytes
18
33
  def to_delimited(delim=',')
19
34
  self.to_s.gsub(/(\d)(?=(\d\d\d)+$)/, "\\1#{delim}")
20
35
  end
@@ -22,6 +37,6 @@ module Waves
22
37
  end
23
38
  end
24
39
 
25
- class Integer # :nodoc:
40
+ class Integer # :nodoc:
26
41
  include Waves::Ext::Integer
27
42
  end
@@ -1,20 +1,16 @@
1
1
  module Kernel
2
- unless respond_to?(:debugger)
2
+
3
+ unless respond_to?( :debugger )
3
4
  # Starts a debugging session if ruby-debug has been loaded (call waves-server --debugger to do load it).
4
5
  def debugger
5
6
  Waves::Logger.info "Debugger invoked but not loaded. Start server with --debugger to enable."
6
7
  end
7
8
  end
8
9
 
9
- unless respond_to?(:engine)
10
+ unless respond_to?( :engine )
10
11
  # 'engine' exists to provide a quick and easy (and MRI-compatible!) interface to the RUBY_ENGINE constant
11
12
  def engine; defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'; end
12
13
  end
13
14
 
14
- def safe_trap(*signals)
15
- signals.each { |s| trap(s) { yield } }
16
- Thread.new { loop { sleep 1 } } if RUBY_PLATFORM =~ /mswin32/
17
- end
18
-
19
15
 
20
16
  end
@@ -10,11 +10,11 @@ module Waves
10
10
  # you cannot do const_get, because that will also attempt to deref the cname
11
11
  # at global scope. So it is more efficient to just use eval.
12
12
  def []( cname ) ; eval( "self::#{cname.to_s.camel_case}" ) ; end
13
-
13
+
14
14
  end
15
- end
15
+ end
16
16
  end
17
17
 
18
18
  class Module # :nodoc:
19
19
  include Waves::Ext::Module
20
- end
20
+ end
@@ -1,6 +1,7 @@
1
1
  module Waves
2
2
  module Ext
3
3
  module Object
4
+
4
5
  # This is an extremely powerful little function that will be built-in to Ruby 1.9.
5
6
  # This version is from Mauricio Fernandez via ruby-talk. Works like instance_eval
6
7
  # except that you can pass parameters to the block.
@@ -14,6 +15,7 @@ module Waves
14
15
  end
15
16
  ret
16
17
  end
18
+
17
19
  end
18
20
  end
19
21
  end
@@ -0,0 +1,73 @@
1
+ # Utility methods mixed into String.
2
+ module Waves::Ext::String
3
+
4
+ # Syntactic sugar for using File.join to concatenate the argument to the receiver.
5
+ #
6
+ # require "lib" / "utilities" / "string"
7
+ #
8
+ # The idea is not original, but we can't remember where we first saw it.
9
+ # Waves::Ext::Symbol defines the same method, allowing for :files / 'afilename.txt'
10
+ #
11
+
12
+ def / ( s ) ; File.join( self, s.to_s ); end
13
+
14
+ #
15
+ # Originally based on English gem. That code was (a) deprecated,
16
+ # (b) used confusing naming conventions (based on Rails original
17
+ # names, like 'camelize' instead of 'camel_case'), and (c) was
18
+ # not thread-safe (making use of $ variables in gsub).
19
+ #
20
+ # I have dispensed with things like "modulize" since (a) the
21
+ # meaning of that is sort of vague and (b) it is easy (and
22
+ # considerably clearer) to just use a method chain, like
23
+ # this: module.name.snake_case.fqn2path or (the reverse):
24
+ # path.camel_case.path2fqn
25
+ #
26
+
27
+ def lowercase ; downcase ; end
28
+ alias_method :lower_case, :lowercase
29
+ def uppercase ; upcase ; end
30
+ alias_method :upper_case, :uppercase
31
+ def titlecase ; gsub( /\b\w/ ) { |x| x.upcase } ; end
32
+ alias_method :title_case, :titlecase
33
+ def fqn2path ; gsub(/::/, '/') ; end
34
+ def basename ; gsub(%r{^.*(::|/)}, '') ; end
35
+ def in_words ; gsub(%r{_|/|::}, ' ') ; end
36
+ def path2fqn ; gsub(%r{/}, '::') ; end
37
+
38
+ def snakecase
39
+ gsub( /(^|\W)[A-Z]/) { |x| x.downcase }.
40
+ gsub(/[A-Z]/) { |x| "_#{x.downcase}" }
41
+ end
42
+ alias_method :snake_case, :snakecase
43
+
44
+ def camelcase( style = :upper )
45
+ if style == :upper
46
+ gsub( /_\w/ ) { |x| x[1,1].upcase }.gsub(/(^|\W)\w/) { |x| x.upcase }
47
+ else
48
+ gsub( /_\w/ ) { |x| x[1,1].upcase }.gsub(/(^|\W)\w/) { |x| x.downcase }
49
+ end
50
+ end
51
+ alias_method :camel_case, :camelcase
52
+
53
+ def ordinal
54
+ gsub(/\d+$/) { |x|
55
+ x = x.to_i
56
+ if (11..13).include?(x % 100)
57
+ "#{i}th"
58
+ else
59
+ case x % 10
60
+ when 1 then "#{x}st"
61
+ when 2 then "#{x}nd"
62
+ when 3 then "#{x}rd"
63
+ else "#{x}th"
64
+ end
65
+ end
66
+ }
67
+ end
68
+
69
+ end
70
+
71
+ class String # :nodoc:
72
+ include Waves::Ext::String
73
+ end
@@ -5,7 +5,6 @@ class Symbol
5
5
  # require :lib / :utilities / :string
6
6
  #
7
7
  # The idea is not original, but we can't remember where we first saw it.
8
- # Waves::Ext::Symbol defines the same method, allowing for :files / 'afilename.txt'
9
8
 
10
9
  def / ( s ) ; File.join( self.to_s, s.to_s ) ; end
11
10
  end
File without changes
@@ -0,0 +1,5 @@
1
+ class Time
2
+ def to_http_timestamp
3
+ clone.gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
4
+ end
5
+ end
@@ -10,49 +10,37 @@ module Waves
10
10
  def self.included( app )
11
11
 
12
12
  require 'autocode'
13
- require 'layers/mvc'
14
- require 'layers/inflect/english'
15
- require 'helpers/extended'
16
- require 'layers/renderers/erubis'
17
- require 'layers/renderers/markaby'
13
+ require 'waves/layers/mvc'
14
+ require 'waves/layers/text/inflect/english'
15
+ require 'waves/views/templated'
16
+ require 'waves/layers/renderers/erubis'
18
17
 
19
18
  app.module_eval do
20
19
 
21
20
  include AutoCode
22
21
 
23
22
  app.auto_create_module( :Configurations ) do
24
- include AutoCode
25
23
  auto_create_class :Default, Waves::Configurations::Default
26
24
  auto_load :Default, :directories => [ :configurations ]
27
- end
28
-
29
- app.auto_eval( :Configurations ) do
30
- auto_create_class true, app::Configurations::Default
25
+ auto_create_class true, :Default
31
26
  auto_load true, :directories => [ :configurations ]
32
27
  end
33
28
 
34
29
  app.auto_create_module( :Resources ) do
35
- include AutoCode
36
30
  auto_create_class :Default, Waves::Resources::Base
37
31
  auto_load :Default, :directories => [ :resources ]
38
- end
39
-
40
- app.auto_eval( :Resources ) do
41
- auto_create_class true, app::Resources::Default
32
+ auto_create_class true, :Default
42
33
  auto_load true, :directories => [ :resources ]
43
- auto_eval :Map do
44
-
45
- handler( Waves::Dispatchers::NotFoundError ) do
34
+ auto_eval :Server do
35
+ handler( Waves::Response::ClientErrors::NotFound ) do
46
36
  app::Views::Errors.new( request ).not_found_404
47
37
  end
48
-
49
38
  end
50
39
  end
51
40
 
52
- include Waves::Layers::Inflect::English
41
+ include Waves::Layers::Text::Inflect::English
53
42
  include Waves::Layers::MVC
54
43
  include Waves::Renderers::Erubis
55
- include Waves::Renderers::Markaby
56
44
 
57
45
  end
58
46
 
@@ -4,15 +4,18 @@ module Waves
4
4
  def self.included( app )
5
5
  app.module_eval {
6
6
  const_set( :Resources, Module.new {
7
- const_set( :Map, Class.new {
7
+ const_set( :Server, Class.new {
8
+
8
9
  include Waves::Resources::Mixin
9
- handler( Exception ) {
10
- Waves::Views::Errors.new( request ).server_error_500
11
- }
12
10
 
13
- handler( Waves::Dispatchers::NotFoundError ) {
14
- Waves::Views::Errors.new( request ).not_found_404
15
- }
11
+ handler( Exception ) do |e|
12
+ Waves.debug? ? raise( e ) : Waves::Views::Errors.new( request ).server_error_500
13
+ end
14
+
15
+ handler( Waves::Response::ClientErrors::NotFound ) do |e|
16
+ Waves.debug? ? raise( e ) : Waves::Views::Errors.new( request ).not_found_404
17
+ end
18
+
16
19
  })
17
20
  })
18
21
  const_set( :Configurations, Module.new {
@@ -20,25 +23,17 @@ module Waves
20
23
  log :level => :debug
21
24
  host '127.0.0.1'
22
25
  port 3000
26
+ use Rack::Session::Pool, :expire_after => 1.day
27
+ resource app::Resources::Server
28
+ dispatcher ::Waves::Dispatchers::Default
23
29
  server Waves::Servers::Mongrel
24
- resource app::Resources::Map
25
30
  })
26
31
  const_set( :Production, Class.new( self::Development ) {
32
+ debug false
27
33
  log :level => :error, :output => ( "log.#{$$}" ), :rotation => :weekly
28
34
  port 80
29
35
  host '0.0.0.0'
30
- server Waves::Servers::Mongrel
31
- application {
32
- use Rack::Session::Cookie, :key => 'rack.session',
33
- # :domain => 'foo.com',
34
- :path => '/',
35
- :expire_after => 2592000,
36
- :secret => 'Change it'
37
-
38
- run ::Waves::Dispatchers::Default.new
39
- }
40
-
41
- })
36
+ })
42
37
  })
43
38
  }
44
39
  Waves << app
@@ -0,0 +1,311 @@
1
+ require "ostruct"
2
+
3
+ require "waves/ext/string"
4
+ require "waves/resources/mixin"
5
+
6
+ module Waves
7
+ module Foundations
8
+
9
+ module REST
10
+
11
+ # Some kind of a malformed resource definition
12
+ #
13
+ class BadDefinition < StandardError; end
14
+
15
+
16
+ # Applications are formal, rather than ad-hoc Waves apps.
17
+ #
18
+ class Application
19
+
20
+ class << self
21
+
22
+ # File that this Application is currently loading if any.
23
+ attr_reader :loading
24
+
25
+ # Resources this application is composed of.
26
+ attr_reader :resources
27
+
28
+ end
29
+
30
+ # Associate mountpoint with a file path for resource.
31
+ #
32
+ # Path is stored expanded to absolute for matching.
33
+ # You can leave the .rb out if you really like.
34
+ #
35
+ # @see .look_in
36
+ #
37
+ def self.at(mountpoint, path)
38
+ @composition << [path, mountpoint]
39
+ end
40
+
41
+ # Resource composition block.
42
+ #
43
+ # In this block, the Application defines all of the
44
+ # resources it is composed of (and their prefixes or
45
+ # "mountpoints.") The resources themselves are not
46
+ # defined here.
47
+ #
48
+ # The order of composition is stored and used.
49
+ #
50
+ # @see .at
51
+ # @see ConvenienceMethods#resource
52
+ #
53
+ def self.composed_of(&block)
54
+ @composition ||= []
55
+ instance_eval &block
56
+
57
+ mounts = const_set :Mounts, Class.new(Waves::Resources::Base)
58
+
59
+ # Only construct the Hash here to retain order for .on()s
60
+ @resources ||= {}
61
+
62
+ @composition.each {|path, mountpoint|
63
+ path = path.to_s.snake_case if Symbol === path
64
+
65
+ path << ".rb" unless path =~ /\.rb$/
66
+
67
+ found = if @look_in
68
+ @look_in.find {|prefix|
69
+ candidate = File.expand_path File.join(prefix, path)
70
+ break candidate if File.exist? candidate
71
+ }
72
+ else
73
+ path = File.expand_path path
74
+ path if File.exist?(path)
75
+ end
76
+
77
+ raise ArgumentError, "Path #{path} does not exist!" unless found
78
+
79
+ # Resource will register itself when loaded
80
+ @resources[found] = OpenStruct.new :mountpoint => mountpoint,
81
+ :actual => nil
82
+
83
+ # This, ladies and gentlemen, is evil.
84
+ mounts.on(true, mountpoint) {
85
+ res = Waves.main.load(found)
86
+
87
+ # TODO: This must be deterministically inserted as
88
+ # a replacement of the old one.
89
+ mounts.on(true, mountpoint) { to res }
90
+ to res
91
+ }
92
+ }
93
+ end
94
+
95
+ # Declare and load layout rendering support.
96
+ #
97
+ # For each MIME type given, / are treated as directory
98
+ # separators and + are converted to spaces (just in case.)
99
+ #
100
+ # Pattern for constant conversion is replacing any \W
101
+ # with :: and capitalising all resulting names.
102
+ #
103
+ # TODO: Provide a way to give some root to load from.
104
+ # Just as last parameter being hash probably OK. --rue
105
+ #
106
+ def self.layouts_for(*types)
107
+ @layouts ||= {}
108
+
109
+ const_set :Layouts, Module.new unless const_defined? :Layouts
110
+
111
+ basedir = if Hash === types.last
112
+ types.pop[:in]
113
+ else
114
+ File.join Dir.pwd, "layouts"
115
+ end
116
+
117
+ types.each {|t|
118
+ require File.expand_path(File.join(basedir, *t.split("/")) + ".rb")
119
+ @layouts[t] = t.split(/\W+/).inject(const_get :Layouts) {|mod, name|
120
+ mod.const_get name.capitalize
121
+ }
122
+ }
123
+ end
124
+
125
+ # Override normal loading to access file being loaded.
126
+ #
127
+ # Used by the first-load hook, see .composed_of.
128
+ # Returns the newly loaded resource.
129
+ #
130
+ def self.load(path)
131
+ @loading = path
132
+ Kernel.load path
133
+
134
+ @resources[path].actual
135
+
136
+ ensure
137
+ @loading = nil
138
+ end
139
+
140
+ # Path prefixes to look for resource files under.
141
+ #
142
+ # Optional trailing /, one or more needed. Each is
143
+ # checked in the order given.
144
+ #
145
+ def self.look_in(*prefixes)
146
+ @look_in = prefixes
147
+ end
148
+
149
+ # Allow resource to register itself when loaded.
150
+ #
151
+ # The path-indexed entry is completed with the actual resource
152
+ # and a mirror version is created, indexed by the resource itself.
153
+ #
154
+ # @todo Is there any point trying to add better failure
155
+ # if the path is unknown? Probably not. --rue
156
+ #
157
+ def self.register(resource)
158
+ entry = @resources[@loading]
159
+ entry.actual = resource
160
+
161
+ # Mirror
162
+ @resources[resource] = OpenStruct.new :mountpoint => entry.mountpoint,
163
+ :path => @loading
164
+ end
165
+
166
+ # Construct and possibly override URL for a resource.
167
+ #
168
+ # @todo This may be obsolete, move to registration? --rue
169
+ #
170
+ def self.url_for(resource, pathspec)
171
+ info = Waves.main.resources[resource]
172
+ info.mountpoint + pathspec
173
+ end
174
+
175
+ end
176
+
177
+ # Base class to use for resources.
178
+ #
179
+ # Mainly here for simple access to some convenience
180
+ # methods.
181
+ #
182
+ # @todo Should maybe insulate the term -> HTTP method
183
+ # mapping a bit more. Or less. --rue
184
+ #
185
+ class Resource
186
+ # @todo Direct include/extend to avoid having to use
187
+ # Mixin. It is cumbersome to glue in at this
188
+ # stage. --rue
189
+ include ResponseMixin, Functor::Method
190
+ extend Resources::Mixin::ClassMethods
191
+
192
+ # Creatability definition block (POST)
193
+ #
194
+ # @see .representation
195
+ #
196
+ def self.creatable(&block)
197
+ raise BadDefinition, "No .url_of_form specified!" unless @pathspec
198
+
199
+ @method = :post
200
+ instance_eval &block
201
+ ensure
202
+ @method = nil
203
+ end
204
+
205
+ # Introduce new MIME type and its extension(s)
206
+ #
207
+ # This is used to allow resources to differentiate between
208
+ # different kinds of representations (or content types.)
209
+ # For example, a Wiki page resource may introduce a MIME
210
+ # type for an "editable" representation, which then allows
211
+ # producing the appropriate editor interface. The MIME types
212
+ # added thusly should follow the normal semantics, which means
213
+ # that usually they will be of the form "application/vnd.somestring".
214
+ # As an example, the Unspecified MIME type is defined in Waves
215
+ # as "vnd.com.rubywaves.unspecified".
216
+ #
217
+ # The users can communicate the desired MIME type either the
218
+ # correct way of using the Accept header or, commonly with a
219
+ # web browser, by using the extension.
220
+ #
221
+ def self.introduce_mime(type, options)
222
+ exts = Array(options[:exts])
223
+ raise ArgumentError, "Must give file extensions for MIME!" if exts.empty?
224
+
225
+ Waves::MimeExts[type] += exts
226
+ Waves::MimeExts[type].uniq!
227
+
228
+ exts.each {|ext|
229
+ Waves::MimeTypes[ext] << type
230
+ Waves::MimeTypes[ext].uniq!
231
+ }
232
+ end
233
+
234
+ # Representation definition block
235
+ #
236
+ def self.representation(*types, &block)
237
+ # @todo Faking it.
238
+ on(@method, @pathspec, :requested => types) {}
239
+ end
240
+
241
+ # URL format specification.
242
+ #
243
+ # The resource defines its own parts, but the app
244
+ # may provide a prefix or even completely override
245
+ # its selection (so long as it can provide all the
246
+ # named captures the resource is expecting, which
247
+ # means that type of override is rare in practice.
248
+ #
249
+ def self.url_of_form(spec)
250
+ @pathspec = Application.url_for self, spec
251
+ end
252
+
253
+ # Viewability definition block (GET)
254
+ #
255
+ # @see .representation
256
+ #
257
+ def self.viewable(&block)
258
+ raise BadDefinition, "No .url_of_form specified!" unless @pathspec
259
+
260
+ @method = :get
261
+ instance_eval &block
262
+ ensure
263
+ @method = nil
264
+ end
265
+ end
266
+
267
+ # Discrete set of methods to include globally.
268
+ #
269
+ module ConvenienceMethods
270
+
271
+ # Application definition block.
272
+ #
273
+ def application(name, &block)
274
+ app = Class.new Application, &block
275
+
276
+ if app.resources.nil? or app.resources.empty?
277
+ raise BadDefinition, "No resource composition!"
278
+ end
279
+
280
+ mod = if Module === self then self else Object end
281
+ mod.const_set name, app
282
+
283
+ Waves << app
284
+ end
285
+
286
+ # Resource definition block.
287
+ #
288
+ # @todo Must change the Waves.main to *current* app. --rue
289
+ #
290
+ def resource(name, &block)
291
+ mod = if Module === self then self else Object end
292
+
293
+ res = mod.const_set name, Class.new(Resource)
294
+
295
+ Waves.main.register res
296
+
297
+ # We must eval this, because the constant really needs
298
+ # to be defined at the point we are running the body
299
+ # code. --rue
300
+ res.instance_eval &block
301
+ end
302
+
303
+ end
304
+
305
+ end # REST
306
+
307
+ end
308
+ end
309
+
310
+ include Waves::Foundations::REST::ConvenienceMethods
311
+