waves 0.6.7 → 0.6.9

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 (46) hide show
  1. data/app/Rakefile +1 -1
  2. data/app/configurations/default.rb.erb +1 -1
  3. data/app/configurations/mapping.rb.erb +6 -37
  4. data/app/controllers/default.rb.erb +10 -20
  5. data/app/lib/tasks/cluster.rb +8 -10
  6. data/app/lib/tasks/generate.rb +15 -0
  7. data/app/lib/tasks/schema.rb +6 -5
  8. data/app/schema/{migration → migrations}/templates/empty.rb.erb +0 -0
  9. data/app/templates/errors/{not_found.mab → not_found_404.mab} +0 -0
  10. data/app/templates/errors/{server_error.mab → server_error_500.mab} +0 -0
  11. data/app/templates/layouts/default.mab +1 -1
  12. data/bin/waves +1 -1
  13. data/bin/waves-console +21 -4
  14. data/lib/controllers/mixin.rb +85 -16
  15. data/lib/dispatchers/base.rb +26 -15
  16. data/lib/dispatchers/default.rb +25 -4
  17. data/lib/helpers/common.rb +50 -10
  18. data/lib/helpers/form.rb +18 -1
  19. data/lib/helpers/formatting.rb +17 -9
  20. data/lib/helpers/model.rb +20 -2
  21. data/lib/helpers/view.rb +12 -2
  22. data/lib/mapping/mapping.rb +187 -0
  23. data/lib/mapping/pretty_urls.rb +95 -0
  24. data/lib/renderers/erubis.rb +3 -1
  25. data/lib/renderers/markaby.rb +6 -4
  26. data/lib/renderers/mixin.rb +12 -0
  27. data/lib/runtime/application.rb +33 -23
  28. data/lib/runtime/configuration.rb +131 -9
  29. data/lib/runtime/console.rb +2 -2
  30. data/lib/runtime/logger.rb +42 -18
  31. data/lib/runtime/mime_types.rb +8 -4
  32. data/lib/runtime/request.rb +14 -5
  33. data/lib/runtime/response.rb +15 -1
  34. data/lib/runtime/response_mixin.rb +29 -47
  35. data/lib/runtime/server.rb +60 -35
  36. data/lib/runtime/session.rb +14 -1
  37. data/lib/utilities/integer.rb +7 -1
  38. data/lib/utilities/kernel.rb +28 -2
  39. data/lib/utilities/module.rb +3 -0
  40. data/lib/utilities/object.rb +5 -1
  41. data/lib/utilities/string.rb +7 -2
  42. data/lib/utilities/symbol.rb +2 -0
  43. data/lib/views/mixin.rb +55 -1
  44. data/lib/waves.rb +9 -2
  45. metadata +10 -8
  46. data/lib/runtime/mapping.rb +0 -82
@@ -1,21 +1,61 @@
1
1
  module Waves
2
2
  module Helpers
3
+
4
+ # Common helpers are helpers that are needed for just about any Web page. For example,
5
+ # each page will likely have a layout and a doctype.
6
+
3
7
  module Common
4
8
 
5
- def layout( name, assigns = {}, &block )
6
- assigns[ :content ] = yield
7
- Blog::Views::Layouts.process( request ) do
9
+ DOCTYPES = {
10
+ :html3 => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">',
11
+ :html4_transitional =>
12
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"' <<
13
+ '"http://www.w3.org/TR/html4/loose.dtd">',
14
+ :html4_strict =>
15
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" ' <<
16
+ '"http://www.w3.org/TR/html4/strict.dtd">',
17
+ :html4_frameset =>
18
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"' <<
19
+ '"http://www.w3.org/TR/html4/frameset.dtd">',
20
+ :xhtml1_transitional =>
21
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' <<
22
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
23
+ :xhtml1_strict =>
24
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"' <<
25
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
26
+ :xhtml1_frameset =>
27
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"' <<
28
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
29
+ :xhtml2 => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'
30
+ }
31
+
32
+ # Invokes a layout view (i.e., a view from the layouts template directory), using
33
+ # the assigns parameter to define instance variables for the view. The block is
34
+ # evaluated and also passed into the view as the +layout_content+ instance variable.
35
+ #
36
+ # You can define a layout just by creating a template and then calling the
37
+ # +layout_content+ accessor when you want to embed the caller's content.
38
+ #
39
+ # == Example
40
+ #
41
+ # doctype :html4_transitional
42
+ # html do
43
+ # title @title # passed as an assigns parameter
44
+ # end
45
+ # body do
46
+ # layout_content
47
+ # end
48
+ #
49
+ def layout( name, assigns = {}, &block )
50
+ assigns[ :layout_content ] = capture(&block)
51
+ self << Waves.application.views[:layouts].process( request ) do
8
52
  send( name, assigns )
9
53
  end
10
54
  end
11
55
 
12
- def doctype(type)
13
- case type
14
- when :html4_strict
15
- '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" ' <<
16
- '"http://www.w3.org/TR/html4/strict.dtd">'
17
- end
18
- end
56
+ # The doctype method simply generates a valid DOCTYPE declaration for your page.
57
+ # Valid options are defined in the +DOCTYPES+ constant.
58
+ def doctype(type) ; self << DOCTYPES[type||:html4_strict] ; end
19
59
 
20
60
  end
21
61
  end
data/lib/helpers/form.rb CHANGED
@@ -2,14 +2,31 @@ module Waves
2
2
 
3
3
  module Helpers
4
4
 
5
+ # Form helpers are used in generating forms. Since Markaby already provides Ruby
6
+ # methods for basic form generation, the focus of this helper is on provide templates
7
+ # to handle things that go beyond the basics. You must define a form template
8
+ # directory with templates for each type of form element you wish to use. The names
9
+ # of the template should match the +type+ option provided in the property method.
10
+ #
11
+ # For example, this code:
12
+ #
13
+ # property :name => 'blog.title', :type => :text, :value => @blog.title
14
+ #
15
+ # will invoke the +text+ form view (the template in +templates/form/text.mab+),
16
+ # passing in the name ('blog.title') and the value (@blog.title) as instance variables.
17
+ #
5
18
  module Form
6
-
19
+
20
+ # This method really is a place-holder for common wrappers around groups of
21
+ # properties. You will usually want to override this. As is, it simply places
22
+ # a DIV element with class 'properties' around the block.
7
23
  def properties(&block)
8
24
  div.properties do
9
25
  yield
10
26
  end
11
27
  end
12
28
 
29
+ # Invokes the form view for the +type+ given in the option.
13
30
  def property( options )
14
31
  self << view( :form, options[:type], options )
15
32
  end
@@ -1,20 +1,28 @@
1
1
  require 'redcloth'
2
2
  module Waves
3
3
  module Helpers
4
+
5
+ # Formatting helpers are used to convert specialized content, like Markaby or
6
+ # Textile, into valid HTML. It also provides common escaping functions.
4
7
  module Formatting
8
+
9
+ # Escape a string as HTML content.
10
+ def escape_html(s); Rack::Utils.escape_html(s); end
11
+
12
+ # Escape a URI, converting quotes and spaces and so on.
13
+ def escape_uri(s); Rack::Utils.escape(s); end
14
+
15
+ # Treat content as Markaby and evaluate (only works within a Markaby template).
16
+ # Used to pull Markaby content from a file or database into a Markaby template.
17
+ def markaby( content ); self << eval( content ); end
5
18
 
6
- # TODO: this won't work inside a erb template!
7
- # but i hate to do a whole new Builder when I'm
8
- # already inside one! test self === Builder?
9
- def mab( content )
10
- eval content
11
- end
12
-
19
+ # Treat content as Textile.
13
20
  def textile( content )
14
- ( ::RedCloth::TEXTILE_TAGS << [ 96.chr, '&8216;'] ).each do |pat,ent|
21
+ return if content.nil? or content.empty?
22
+ ( ::RedCloth::TEXTILE_TAGS << [ 96.chr, '&8216;'] ).each do |pat,ent|
15
23
  content.gsub!( pat, ent.gsub('&','&#') )
16
24
  end
17
- ::RedCloth.new( content ).to_html
25
+ self << ::RedCloth.new( content ).to_html
18
26
  end
19
27
 
20
28
  end
data/lib/helpers/model.rb CHANGED
@@ -1,13 +1,31 @@
1
1
  module Waves
2
2
  module Helpers
3
+
4
+ # Model helpers allow you to directly access a model from within a view.
5
+ # This is useful when creating things like select boxes that need data
6
+ # from anther model. For example, a Markaby select box for authors might look like:
7
+ #
8
+ # select do
9
+ # all(:user).each do |user|
10
+ # option user.full_name, :value => user.id
11
+ # end
12
+ # end
13
+ #
14
+ # You could also use these within a view class to keep model-based logic out
15
+ # of the templates themselves. For example, in the view class you might define
16
+ # a method called +authors+ that returns an array of name / id pairs. This could
17
+ # then be called from the template instead of the model helper.
18
+ #
3
19
  module Model
4
20
 
21
+ # Just like model.all. Returns all the instances of that model.
5
22
  def all( model )
6
- Application.models[ model ].all( domain )
23
+ Waves.application.models[ model ].all( domain )
7
24
  end
8
25
 
26
+ # Finds a specific instance using the name field
9
27
  def find( model, name )
10
- Application.models[ model ].find( domain, name ) rescue nil
28
+ Waves.application.models[ model ][ :name => name ] rescue nil
11
29
  end
12
30
 
13
31
  end
data/lib/helpers/view.rb CHANGED
@@ -1,10 +1,20 @@
1
1
  module Waves
2
2
  module Helpers
3
+
4
+ # View helpers are intended to help reuse views from within other views.
5
+ # Both the +layout+ method in the common helpers and the +property+ method
6
+ # of the form helpers are specialized instance of this.
7
+ #
8
+ # The star of our show here is the +view+ method. This takes a model, view,
9
+ # and assigns hash (which are converted into instance variables in the target
10
+ # view) and returns the result of evaluating the view as content in the current
11
+ # template.
3
12
  module View
4
13
 
14
+ # Invokes the view for the given model, passing the assigns as instance variables.
5
15
  def view( model, view, assigns = {} )
6
- self << Application.views[ model ].process( request ) do
7
- send view, assigns
16
+ self << Waves.application.views[ model ].process( request ) do
17
+ send( view, assigns )
8
18
  end
9
19
  end
10
20
 
@@ -0,0 +1,187 @@
1
+ module Waves
2
+
3
+ # Waves::Mapping is a mixin for defining Waves URI mappings (mapping a request to Ruby code).
4
+ # Mappings can work against the request url, path, and elements of the request (such as the
5
+ # request method or accept header). Mappings may also include before, after, or wrap filters
6
+ # to be run if they match the request. Mappings are created using an appropriate mapping method
7
+ # along with a URL pattern (a string or regular expression), a hash of constraint options, and
8
+ # a block, which is the code to run if the pattern matches.
9
+ #
10
+ # == Examples
11
+ #
12
+ # path %r{^/#{resource}/#{name}/?$} do |resource, name|
13
+ # "Hello from a #{resource} named #{name.capitalize}."
14
+ # end
15
+ #
16
+ # In this example, we are using binding regular expressions defined by +resource+
17
+ # and +name+. The matches are passed into the block as parameters. Thus, this
18
+ # rule, given the URL '/person/john' will return:
19
+ #
20
+ # Hello from a person named John.
21
+ #
22
+ # The given block may simple return a string. The content type is inferred from the request
23
+ # if possible, otherwise it defaults to +text+/+html+.
24
+ #
25
+ # path '/critters', :method => :post do
26
+ # request.content_type
27
+ # end
28
+ #
29
+ # /critters # => 'text/html'
30
+ #
31
+ # In this example, we match against a string and check to make sure that the request is a
32
+ # POST. If so, we return the request content_type. The request (and response) objects are
33
+ # available from within the block implicitly.
34
+ #
35
+ # = Invoking Controllers and Views
36
+ #
37
+ # You may invoking a controller or view method for the primary application by using the
38
+ # corresponding methods, preceded by the +use+ directive.
39
+ #
40
+ # == Examples
41
+ #
42
+ # path %r{^/#{resource}/#{name}/?$} do |resource, name|
43
+ # use( resource ) | controller { find( name ) } |
44
+ # view { | instance | show( resource => instance ) }
45
+ # end
46
+ #
47
+ # In this example, we take the same rule from above but invoke a controller and view method.
48
+ # We use the +use+ directive and the resource parameter to set the MVC instances we're going
49
+ # to use. This is necessary to use the +controller+ or +view+ methods. Each of these take
50
+ # a block as arguments which are evaluated in the context of the instance. The +view+ method
51
+ # can further take an argument which is "piped" from the result of the controller block. This
52
+ # isn't required, but helps to clarify the request processing. Within a view block, a hash
53
+ # may also be passed in, which is converted into instance variables for the view instance. In
54
+ # this example, the +show+ method is assigned to an instance variable with the same name as
55
+ # the resource type.
56
+ #
57
+ # So given the same URL as above - /person/john - what will happen is the +find+ method for
58
+ # the +Person+ controller will be invoked and the result passed to the +Person+ view's +show+
59
+ # method, with +@person+ holding the value returned.
60
+ #
61
+ # Crucially, the controller does not need to know what variables the view depends on. This is
62
+ # the job of the mapping block, to act as the "glue" between the controller and view. The
63
+ # controller and view can thus be completely decoupled and become easier to reuse separately.
64
+ #
65
+ # url 'http://admin.foobar.com:/' do
66
+ # use( admin ) | view { console }
67
+ # end
68
+ #
69
+ # In this example, we are using the +url+ method to map a subdomain of +foobar.com+ to the
70
+ # console method of the Admin view. In this case, we did not need a controller method, so
71
+ # we simply didn't call one.
72
+ #
73
+ # = Mapping Modules
74
+ #
75
+ # You may encapsulate sets of related rules into modules and simply include them into your
76
+ # mapping module. Some rule sets come packaged with Waves, such as PrettyUrls (rules for
77
+ # matching resources using names instead of ids). The simplest way to define such modules for
78
+ # reuse is by defining the +included+ class method for the rules module, and then define
79
+ # the rules using +module_eval+. This will likely be made simpler in a future release, but
80
+ # see the PrettyUrls module for an example of how to do this.
81
+ #
82
+ # *Important:* Using pre-packaged mapping rules does not prevent you from adding to or
83
+ # overriding these rules. However, order does matter, so you should put your own rules
84
+ # ahead of those your may be importing. Also, place rules with constraints (for example,
85
+ # rules that require a POST) ahead of those with no constraints, otherwise the constrainted
86
+ # rules may never be called.
87
+
88
+ module Mapping
89
+
90
+ # If the pattern matches and constraints given by the options hash are satisfied, run the
91
+ # block before running any +path+ or +url+ actions. You can have as many +before+ matches
92
+ # as you want - they will all run, unless one of them calls redirect, generates an
93
+ # unhandled exception, etc.
94
+ def before( pattern, options=nil, &block )
95
+ filters[:before] << [ pattern, options, block ]
96
+ end
97
+
98
+ # Similar to before, except it runs its actions after any matching +url+ or +path+ actions.
99
+ def after( pattern, options=nil, &block )
100
+ filters[:after] << [ pattern, options, block ]
101
+ end
102
+
103
+ # Run the action before and after the matching +url+ or +path+ action.
104
+ def wrap( pattern, options=nil, &block )
105
+ filters[:before] << [ pattern, options, block ]
106
+ filters[:after] << [ pattern, options, block ]
107
+ end
108
+
109
+ # Maps a request to a block. Don't use this method directly unless you know what
110
+ # you're doing. Use +path+ or +url+ instead.
111
+ def map( options, &block )
112
+ pattern = options[:path] || options[:url]
113
+ mapping << [ pattern, options, block ]
114
+ end
115
+
116
+ # Match pattern against the +request.path+, along with satisfying any constraints
117
+ # specified by the options hash. If the pattern matches and the constraints are satisfied,
118
+ # run the block. Only one +path+ or +url+ match will be run (the first one).
119
+ def path( pat, options = {}, &block )
120
+ options[:path] = pat; map( options, &block )
121
+ end
122
+
123
+ # Match pattern against the +request.url+, along with satisfying any constraints
124
+ # specified by the options hash. If the pattern matches and the constraints are satisfied,
125
+ # run the block. Only one +path+ or +url+ match will be run (the first one).
126
+ def url( pat, options = {}, &block )
127
+ options[:url] = pat; map( options, block )
128
+ end
129
+
130
+ # Match the given request against the defined rules. This is typically only called
131
+ # by a dispatcher object, so you shouldn't typically use it directly.
132
+ def []( request )
133
+
134
+ rx = { :before => [], :after => [], :action => nil }
135
+
136
+ ( filters[:before] + filters[:wrap] ).each do | pattern, options, function |
137
+ matches = pattern.match(request.path)
138
+ rx[:before] << [ function, matches[1..-1] ] if matches &&
139
+ ( ! options || satisfy( request, options ))
140
+ end
141
+
142
+ mapping.find do |pattern, options, function|
143
+ matches = pattern.match(request.path)
144
+ rx[:action] = [ function, matches[1..-1] ] if matches &&
145
+ ( ! options || satisfy( request, options ) )
146
+ end
147
+
148
+ ( filters[:after] + filters[:wrap] ).each do | pattern, options, function |
149
+ matches = pattern.match(request.path)
150
+ rx[:after] << [ function, matches[1..-1] ] if matches &&
151
+ ( ! options || satisfy( request, options ))
152
+ end
153
+
154
+ not_found(request) unless rx[:action]
155
+
156
+ return rx
157
+
158
+ end
159
+
160
+ private
161
+
162
+ def mapping; @mapping ||= []; end
163
+
164
+ def filters; @filters ||= { :before => [], :after => [], :wrap => [] }; end
165
+
166
+ def not_found(request)
167
+ raise Waves::Dispatchers::NotFoundError.new( request.url + ' not found.')
168
+ end
169
+
170
+ def satisfy( request, options )
171
+ options.each do |method, param|
172
+ return false unless self.send( method, param, request )
173
+ end
174
+ return true
175
+ end
176
+
177
+ def method( method, request )
178
+ request.method == method
179
+ end
180
+
181
+ # TODO: Add constraint for request accept header
182
+ def accept( format, request )
183
+ end
184
+
185
+ end
186
+
187
+ end
@@ -0,0 +1,95 @@
1
+ module Waves
2
+ module Mapping
3
+
4
+ # A set of pre-packed mapping rules for dealing with pretty URLs (that use names instead
5
+ # of numbers to identify resources). There are two modules.
6
+ # - GetRules, which defines all the GET methods for dealing with named resources
7
+ # - RestRules, which defines add, update, and delete rules using a REST style interface
8
+ #
9
+ module PrettyUrls
10
+
11
+ #
12
+ # GetRules defines the following URL conventions:
13
+ #
14
+ # /resources # => get a list of all instances of resource
15
+ # /resource/name # => get a specific instance of resource with the given name
16
+ # /resource/name/editor # => display an edit page for the given resource
17
+ #
18
+ module GetRules
19
+
20
+ def self.included(target)
21
+
22
+ target.module_eval do
23
+
24
+ extend Waves::Mapping
25
+
26
+ name = '([\w\-\_\.\+\@]+)'; model = '([\w\-]+)'
27
+
28
+ # get all resources for the given model
29
+ path %r{^/#{model}/?$} do | model |
30
+ use( model.singular ) | controller { all } | view { |data| list( model => data ) }
31
+ end
32
+
33
+ # get the given resource for the given model
34
+ path %r{^/#{model}/#{name}/?$} do | model, name |
35
+ use(model) | controller { find( name ) } |
36
+ view { |data| show( model => data ) }
37
+ end
38
+
39
+ # display an editor for the given resource / model
40
+ path %r{^/#{model}/#{name}/editor/?$} do | model, name |
41
+ use(model) | controller { find( name ) } |
42
+ view { |data| editor( model => data ) }
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ #
52
+ # RestRules defines the following URL conventions:
53
+ #
54
+ # POST /resources # => add a new resource using the POST variables
55
+ # POST /resource/name # => update the given resource using the POST variables
56
+ # DELETE /resource/name # => delete the given resource
57
+ #
58
+ module RestRules
59
+
60
+ def self.included(target)
61
+
62
+ target.module_eval do
63
+
64
+ extend Waves::Mapping
65
+
66
+ name = '([\w\-\_\.\+\@]+)'; model = '([\w\-]+)'
67
+
68
+ # create a new resource for the given model
69
+ path %r{^/#{model}/?$}, :method => :post do | model |
70
+ use( model.singular )
71
+ instance = controller { create }
72
+ redirect( "/#{model.singular}/#{instance.name}/editor" )
73
+ end
74
+
75
+ # add / update the given resource for the given model
76
+ path %r{^/#{model}/#{name}/?$}, :method => :post do | model, name |
77
+ use(model) | controller { update( name ) }; redirect( url )
78
+ end
79
+
80
+ # delete the given resource for the given model
81
+ path %r{^/#{model}/#{name}/?$}, :method => :delete do | model, name |
82
+ use( model ) | controller { delete( name ) }
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+
95
+ end