waves 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/waves +4 -3
- data/doc/VERSION +1 -1
- data/lib/waves.rb +52 -40
- data/lib/{caches → waves/caches}/file.rb +3 -1
- data/lib/waves/caches/memcached.rb +56 -0
- data/lib/{caches → waves/caches}/simple.rb +6 -7
- data/lib/{caches → waves/caches}/synchronized.rb +15 -1
- data/lib/{commands → waves/commands}/console.rb +4 -4
- data/lib/{commands → waves/commands}/generate.rb +6 -5
- data/lib/{commands → waves/commands}/help.rb +0 -0
- data/lib/{commands → waves/commands}/server.rb +1 -1
- data/lib/{dispatchers → waves/dispatchers}/base.rb +17 -31
- data/lib/waves/dispatchers/default.rb +19 -0
- data/lib/{ext → waves/ext}/float.rb +0 -0
- data/lib/{ext → waves/ext}/hash.rb +0 -0
- data/lib/{ext → waves/ext}/integer.rb +16 -1
- data/lib/{ext → waves/ext}/kernel.rb +3 -7
- data/lib/{ext → waves/ext}/module.rb +3 -3
- data/lib/{ext → waves/ext}/object.rb +2 -0
- data/lib/waves/ext/string.rb +73 -0
- data/lib/{ext → waves/ext}/symbol.rb +0 -1
- data/lib/{ext → waves/ext}/tempfile.rb +0 -0
- data/lib/waves/ext/time.rb +5 -0
- data/lib/{foundations → waves/foundations}/classic.rb +9 -21
- data/lib/{foundations → waves/foundations}/compact.rb +15 -20
- data/lib/waves/foundations/rest.rb +311 -0
- data/lib/waves/helpers/basic.rb +13 -0
- data/lib/{helpers → waves/helpers}/doc_type.rb +3 -0
- data/lib/waves/helpers/form.rb +94 -0
- data/lib/waves/helpers/formatting.rb +14 -0
- data/lib/waves/layers/mvc.rb +65 -0
- data/lib/{layers → waves/layers}/mvc/controllers.rb +0 -0
- data/lib/{layers → waves/layers}/mvc/extensions.rb +23 -11
- data/lib/{layers → waves/layers}/orm/migration.rb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/active_record.rb +2 -5
- data/lib/{layers → waves/layers}/orm/providers/active_record/migrations/empty.rb.erb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/active_record/tasks/generate.rb +1 -1
- data/lib/{layers → waves/layers}/orm/providers/active_record/tasks/schema.rb +1 -1
- data/lib/{layers → waves/layers}/orm/providers/data_mapper.rb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/filebase.rb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/sequel.rb +28 -29
- data/lib/{layers → waves/layers}/orm/providers/sequel/migrations/empty.rb.erb +0 -0
- data/lib/{layers → waves/layers}/orm/providers/sequel/tasks/generate.rb +1 -1
- data/lib/{layers → waves/layers}/orm/providers/sequel/tasks/schema.rb +2 -0
- data/lib/waves/layers/rack/rack_cache.rb +32 -0
- data/lib/waves/layers/renderers/erubis.rb +52 -0
- data/lib/waves/layers/renderers/haml.rb +67 -0
- data/lib/waves/layers/renderers/markaby.rb +41 -0
- data/lib/waves/layers/text/inflect/english.rb +42 -0
- data/lib/waves/matchers/accept.rb +47 -0
- data/lib/waves/matchers/ext.rb +27 -0
- data/lib/waves/matchers/path.rb +72 -0
- data/lib/waves/matchers/query.rb +43 -0
- data/lib/waves/matchers/request.rb +86 -0
- data/lib/waves/matchers/requested.rb +31 -0
- data/lib/{matchers → waves/matchers}/resource.rb +8 -1
- data/lib/waves/matchers/traits.rb +30 -0
- data/lib/waves/matchers/uri.rb +69 -0
- data/lib/waves/media/mime_types.rb +542 -0
- data/lib/waves/renderers/mixin.rb +9 -0
- data/lib/waves/request/accept.rb +92 -0
- data/lib/{runtime → waves/request}/request.rb +77 -61
- data/lib/waves/resources/file_mixin.rb +11 -0
- data/lib/{resources → waves/resources}/mixin.rb +42 -44
- data/lib/waves/resources/paths.rb +132 -0
- data/lib/waves/response/client_errors.rb +10 -0
- data/lib/waves/response/packaged.rb +19 -0
- data/lib/waves/response/redirects.rb +35 -0
- data/lib/{runtime → waves/response}/response.rb +29 -11
- data/lib/{runtime → waves/response}/response_mixin.rb +30 -17
- data/lib/waves/runtime/applications.rb +18 -0
- data/lib/{runtime → waves/runtime}/configuration.rb +31 -25
- data/lib/waves/runtime/console.rb +24 -0
- data/lib/{runtime → waves/runtime}/logger.rb +3 -3
- data/lib/{runtime → waves/runtime}/mocks.rb +2 -2
- data/lib/waves/runtime/rackup.rb +37 -0
- data/lib/waves/runtime/runtime.rb +48 -0
- data/lib/waves/runtime/server.rb +33 -0
- data/lib/{servers → waves/servers}/base.rb +0 -0
- data/lib/{servers → waves/servers}/mongrel.rb +0 -0
- data/lib/{servers → waves/servers}/webrick.rb +0 -0
- data/lib/{tasks → waves/tasks}/gem.rb +0 -0
- data/lib/{tasks → waves/tasks}/generate.rb +0 -0
- data/lib/waves/views/cassy.rb +173 -0
- data/lib/{views → waves/views}/errors.rb +8 -7
- data/lib/waves/views/mixin.rb +23 -0
- data/lib/waves/views/templated.rb +40 -0
- data/samples/basic/basic_startup.rb +70 -0
- data/samples/basic/config.ru +9 -0
- data/samples/blog/configurations/development.rb +17 -16
- data/samples/blog/configurations/production.rb +0 -11
- data/samples/blog/resources/entry.rb +3 -3
- data/samples/blog/resources/map.rb +10 -3
- data/samples/blog/startup.rb +4 -3
- data/templates/classic/Rakefile +28 -29
- data/templates/classic/configurations/default.rb.erb +8 -3
- data/templates/classic/configurations/development.rb.erb +1 -20
- data/templates/classic/configurations/production.rb.erb +2 -16
- data/templates/classic/public/images/favicon.ico +0 -0
- data/templates/classic/resources/server.rb.erb +9 -0
- data/templates/classic/startup.rb.erb +3 -3
- data/templates/classic/views/css.rb.erb +14 -0
- data/templates/classic/views/default.rb.erb +17 -0
- data/templates/classic/views/errors.rb.erb +10 -0
- data/templates/classic/views/pages.rb.erb +14 -0
- data/templates/compact/startup.rb.erb +8 -3
- data/test/ext/object.rb +55 -0
- data/test/ext/shortcuts.rb +73 -0
- data/test/helpers.rb +17 -0
- data/test/match/accept.rb +78 -0
- data/test/match/ext.rb +156 -0
- data/test/match/methods.rb +22 -0
- data/test/match/params.rb +33 -0
- data/test/match/path.rb +106 -0
- data/test/match/query.rb +60 -0
- data/test/match/request.rb +91 -0
- data/test/match/requested.rb +149 -0
- data/test/match/uri.rb +136 -0
- data/test/process/request.rb +75 -0
- data/test/process/resource.rb +53 -0
- data/test/resources/path.rb +166 -0
- data/test/runtime/configurations.rb +19 -0
- data/test/runtime/request.rb +63 -0
- data/test/runtime/response.rb +85 -0
- data/test/views/views.rb +40 -0
- metadata +243 -157
- data/lib/caches/memcached.rb +0 -40
- data/lib/dispatchers/default.rb +0 -25
- data/lib/ext/string.rb +0 -20
- data/lib/helpers/basic.rb +0 -11
- data/lib/helpers/extended.rb +0 -21
- data/lib/helpers/form.rb +0 -42
- data/lib/helpers/formatting.rb +0 -30
- data/lib/helpers/layouts.rb +0 -37
- data/lib/helpers/model.rb +0 -37
- data/lib/helpers/view.rb +0 -22
- data/lib/layers/inflect/english.rb +0 -67
- data/lib/layers/mvc.rb +0 -54
- data/lib/layers/renderers/erubis.rb +0 -60
- data/lib/layers/renderers/haml.rb +0 -47
- data/lib/layers/renderers/markaby.rb +0 -29
- data/lib/matchers/accept.rb +0 -21
- data/lib/matchers/base.rb +0 -30
- data/lib/matchers/content_type.rb +0 -17
- data/lib/matchers/path.rb +0 -67
- data/lib/matchers/query.rb +0 -21
- data/lib/matchers/request.rb +0 -27
- data/lib/matchers/traits.rb +0 -19
- data/lib/matchers/uri.rb +0 -20
- data/lib/renderers/mixin.rb +0 -36
- data/lib/resources/paths.rb +0 -34
- data/lib/runtime/console.rb +0 -23
- data/lib/runtime/mime_types.rb +0 -536
- data/lib/runtime/monitor.rb +0 -32
- data/lib/runtime/runtime.rb +0 -67
- data/lib/runtime/server.rb +0 -20
- data/lib/runtime/session.rb +0 -27
- data/lib/runtime/worker.rb +0 -86
- data/lib/views/mixin.rb +0 -62
- data/samples/blog/blog.db +0 -0
- data/samples/blog/log/waves.production +0 -3
- data/templates/classic/resources/map.rb.erb +0 -8
- data/templates/classic/templates/errors/not_found_404.mab +0 -7
- data/templates/classic/templates/errors/server_error_500.mab +0 -7
- data/templates/classic/templates/layouts/default.mab +0 -14
- data/templates/classic/tmp/sessions/.gitignore +0 -0
|
@@ -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
|
+
|