waves 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+