waves 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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
|
40
|
+
class Integer # :nodoc:
|
26
41
|
include Waves::Ext::Integer
|
27
42
|
end
|
@@ -1,20 +1,16 @@
|
|
1
1
|
module Kernel
|
2
|
-
|
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
|
@@ -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 '
|
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
|
-
|
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
|
-
|
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 :
|
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( :
|
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(
|
14
|
-
Waves::Views::Errors.new( request ).
|
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
|
-
|
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
|
+
|