strelka 0.0.1pre4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/History.rdoc +4 -0
- data/IDEAS.textile +174 -0
- data/Manifest.txt +38 -0
- data/README.rdoc +66 -0
- data/Rakefile +64 -0
- data/bin/leash +403 -0
- data/data/strelka/apps/strelka-admin +65 -0
- data/data/strelka/apps/strelka-setup +26 -0
- data/data/strelka/bootstrap-config.rb +34 -0
- data/data/strelka/templates/admin/console.tmpl +21 -0
- data/data/strelka/templates/layout.tmpl +30 -0
- data/lib/strelka/app/defaultrouter.rb +85 -0
- data/lib/strelka/app/filters.rb +70 -0
- data/lib/strelka/app/parameters.rb +64 -0
- data/lib/strelka/app/plugins.rb +205 -0
- data/lib/strelka/app/routing.rb +140 -0
- data/lib/strelka/app/templating.rb +157 -0
- data/lib/strelka/app.rb +175 -0
- data/lib/strelka/behavior/plugin.rb +36 -0
- data/lib/strelka/constants.rb +53 -0
- data/lib/strelka/httprequest.rb +52 -0
- data/lib/strelka/logging.rb +241 -0
- data/lib/strelka/mixins.rb +143 -0
- data/lib/strelka/process.rb +19 -0
- data/lib/strelka.rb +40 -0
- data/spec/data/layout.tmpl +3 -0
- data/spec/data/main.tmpl +1 -0
- data/spec/lib/constants.rb +32 -0
- data/spec/lib/helpers.rb +134 -0
- data/spec/strelka/app/defaultrouter_spec.rb +215 -0
- data/spec/strelka/app/parameters_spec.rb +74 -0
- data/spec/strelka/app/plugins_spec.rb +167 -0
- data/spec/strelka/app/routing_spec.rb +139 -0
- data/spec/strelka/app/templating_spec.rb +169 -0
- data/spec/strelka/app_spec.rb +160 -0
- data/spec/strelka/httprequest_spec.rb +54 -0
- data/spec/strelka/logging_spec.rb +72 -0
- data/spec/strelka_spec.rb +27 -0
- metadata +226 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'strelka' unless defined?( Strelka )
|
4
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
5
|
+
|
6
|
+
|
7
|
+
# Request/response filters plugin for Strelka::App.
|
8
|
+
module Strelka::App::Filters
|
9
|
+
extend Strelka::App::Plugin
|
10
|
+
|
11
|
+
run_before :routing
|
12
|
+
|
13
|
+
### Class methods to add to classes with routing.
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
# Default filters hash
|
17
|
+
@filters = { :request => [], :response => [], :both => [] }
|
18
|
+
|
19
|
+
# The list of filters
|
20
|
+
attr_reader :filters
|
21
|
+
|
22
|
+
|
23
|
+
### Get/set the router class to use for mapping requests to handlers to +newclass.
|
24
|
+
def filter( which=:both, &block )
|
25
|
+
which = which.to_sym
|
26
|
+
raise ArgumentError, "invalid filter stage %p; expected one of: %p" %
|
27
|
+
[ which, self.filters.keys ] if !self.filters.key?( which )
|
28
|
+
self.filters[ which ] << block
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
### Return filters which should be applied to requests, i.e., those with a +which+ of
|
33
|
+
### :request or :both.
|
34
|
+
def request_filters
|
35
|
+
return self.filters[ :request ] + self.filters[ :both ]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
### Return filters which should be applied to responses, i.e., those with a +which+ of
|
40
|
+
### :response or :both.
|
41
|
+
def request_filters
|
42
|
+
return self.filters[ :both ] + self.filters[ :response ]
|
43
|
+
end
|
44
|
+
|
45
|
+
end # module ClassMethods
|
46
|
+
|
47
|
+
|
48
|
+
### Apply filters to the given +request+ before yielding back to the App, then apply
|
49
|
+
### filters to the response that comes back.
|
50
|
+
def handler( request )
|
51
|
+
self.apply_request_filters( request )
|
52
|
+
response = super
|
53
|
+
self.apply_response_filters( response )
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
### Apply :request and :both filters to +request+.
|
58
|
+
def apply_request_filters( request )
|
59
|
+
self.class.request_filters.each {|filter| filter.call(request) }
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
### Apply :both and :response filters to +response+.
|
64
|
+
def apply_response_filters( response )
|
65
|
+
self.class.response_filters.each {|filter| filter.call(response) }
|
66
|
+
end
|
67
|
+
|
68
|
+
end # module Strelka::App::Filters
|
69
|
+
|
70
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'strelka' unless defined?( Strelka )
|
4
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
5
|
+
|
6
|
+
# Parameter declaration for Strelka::Apps
|
7
|
+
module Strelka::App::Parameters
|
8
|
+
extend Strelka::App::Plugin
|
9
|
+
|
10
|
+
### Class methods to add to classes with routing.
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# Pattern for matching route parameters
|
14
|
+
PARAMETER_PATTERN = %r{/:(?<paramname>[a-z]\w*)}i
|
15
|
+
|
16
|
+
# Param defaults
|
17
|
+
PARAMETER_DEFAULT_OPTIONS = {
|
18
|
+
:constraint => //,
|
19
|
+
:required => false,
|
20
|
+
:untaint => false,
|
21
|
+
:description => nil,
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
# Default parameters hash
|
26
|
+
@parameters = {}
|
27
|
+
|
28
|
+
# The hash of declared parameters
|
29
|
+
attr_reader :parameters
|
30
|
+
|
31
|
+
|
32
|
+
### Declare a parameter with the specified +name+ that will be validated using the given
|
33
|
+
### +regexp+.
|
34
|
+
def param( name, regexp=nil, *flags )
|
35
|
+
Strelka.log.debug "New param %p" % [ name ]
|
36
|
+
name = name.to_sym
|
37
|
+
|
38
|
+
regexp = Regexp.compile( "(?<#{name}>" + regexp.to_s + ")" ) unless
|
39
|
+
regexp.names.include?( name.to_s )
|
40
|
+
Strelka.log.debug " param constraint is: %p" % [ regexp ]
|
41
|
+
|
42
|
+
options = PARAMETER_DEFAULT_OPTIONS.dup
|
43
|
+
options[ :constraint ] = regexp
|
44
|
+
options[ :required ] = true if flags.include?( :required )
|
45
|
+
options[ :untaint ] = true if flags.include?( :untaint )
|
46
|
+
Strelka.log.debug " param options are: %p" % [ options ]
|
47
|
+
|
48
|
+
self.parameters[ name ] = options
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
### Inheritance hook -- inheriting classes inherit their parents' parameter
|
53
|
+
### declarations, too.
|
54
|
+
def inherited( subclass )
|
55
|
+
super
|
56
|
+
subclass.instance_variable_set( :@parameters, self.parameters.dup )
|
57
|
+
end
|
58
|
+
|
59
|
+
end # module ClassMethods
|
60
|
+
|
61
|
+
|
62
|
+
end # module Strelka::App::Parameters
|
63
|
+
|
64
|
+
|
@@ -0,0 +1,205 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'strelka' unless defined?( Strelka )
|
4
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
5
|
+
|
6
|
+
# Pluggable functionality mixin for Strelka::App.
|
7
|
+
class Strelka::App
|
8
|
+
|
9
|
+
# The Hash of loaded plugin modules, keyed by their downcased and symbolified
|
10
|
+
# name (e.g., Strelka::App::Templating => :templating)
|
11
|
+
class << self; attr_reader :loaded_plugins; end
|
12
|
+
@loaded_plugins = {}
|
13
|
+
|
14
|
+
|
15
|
+
# Plugin Module extension -- adds registration, sorting, etc.
|
16
|
+
module Plugin
|
17
|
+
include Comparable
|
18
|
+
|
19
|
+
### Mixin hook -- extend including objects instead.
|
20
|
+
def self::included( mod )
|
21
|
+
mod.extend( self )
|
22
|
+
end
|
23
|
+
|
24
|
+
### Extension hook -- Extend the given object with methods for setting it
|
25
|
+
### up as a plugin for Strelka::Apps.
|
26
|
+
def self::extend_object( object )
|
27
|
+
Strelka.log.debug "Extending %p as a Strelka::App::Plugin" % [ object ]
|
28
|
+
|
29
|
+
super
|
30
|
+
name = object.plugin_name
|
31
|
+
object.instance_variable_set( :@load_order, {:before => [], :after => []} )
|
32
|
+
|
33
|
+
Strelka.log.debug " adding %p (%p) to the plugin registry" % [ name, object ]
|
34
|
+
Strelka::App.loaded_plugins[ name ] = object
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
#############################################################
|
39
|
+
### A P P E N D E D M E T H O D S
|
40
|
+
#############################################################
|
41
|
+
|
42
|
+
# An Array of Arrays that tracks which plugins should be installed before and after
|
43
|
+
# itself, in that order.
|
44
|
+
attr_reader :load_order
|
45
|
+
|
46
|
+
|
47
|
+
### Comparable operator -- use the plugin load_order to compare Plugin modules.
|
48
|
+
def <=>( other_plugin )
|
49
|
+
if self.before?( other_plugin ) || other_plugin.after?( self )
|
50
|
+
return -1
|
51
|
+
elsif self.after?( other_plugin ) || other_plugin.before?( self )
|
52
|
+
return 1
|
53
|
+
else
|
54
|
+
return 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
### Returns true if the receiver has specified that it should run before +other_plugin+.
|
60
|
+
def before?( other_plugin )
|
61
|
+
return self.load_order[ :before ].include?( other_plugin.plugin_name )
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
### Returns true if the receiver has specified that it should run after +other_plugin+.
|
66
|
+
def after?( other_plugin )
|
67
|
+
return self.load_order[ :after ].include?( other_plugin.plugin_name )
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
### Return the name of the receiving plugin
|
72
|
+
def plugin_name
|
73
|
+
name = self.name || "anonymous#{self.object_id}"
|
74
|
+
name.sub!( /.*::/, '' )
|
75
|
+
return name.downcase.to_sym
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
### Register the receiver as needing to be run before +other_plugins+ for requests, and
|
80
|
+
### *after* them for responses.
|
81
|
+
def run_before( *other_plugins )
|
82
|
+
self.load_order[:before] += other_plugins
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
### Register the receiver as needing to be run after +other_plugins+ for requests, and
|
87
|
+
### *before* them for responses.
|
88
|
+
def run_after( *other_plugins )
|
89
|
+
self.load_order[:after] += other_plugins
|
90
|
+
end
|
91
|
+
|
92
|
+
end # module Plugin
|
93
|
+
|
94
|
+
|
95
|
+
# Plugin system
|
96
|
+
module Plugins
|
97
|
+
|
98
|
+
### Inclusion callback -- add class methods and instance variables without
|
99
|
+
### needing a separate call to #extend.
|
100
|
+
def self::included( klass )
|
101
|
+
klass.extend( ClassMethods )
|
102
|
+
super
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
### Extension callback -- add instance variables to extending objects.
|
107
|
+
def self::extended( object )
|
108
|
+
super
|
109
|
+
object.instance_variable_set( :@plugins, {} )
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
### Class methods to add to classes with plugins.
|
114
|
+
module ClassMethods
|
115
|
+
|
116
|
+
### Load the plugin with the given +name+, or nil if
|
117
|
+
def load_plugin( name )
|
118
|
+
|
119
|
+
# Just return Modules as-is
|
120
|
+
return name if name.is_a?( Strelka::App::Plugin )
|
121
|
+
|
122
|
+
unless mod = Strelka::App.loaded_plugins[ name.to_sym ]
|
123
|
+
Strelka.log.debug "Loading plugin from strelka/app/#{name}"
|
124
|
+
require "strelka/app/#{name}"
|
125
|
+
mod = Strelka::App.loaded_plugins[ name.to_sym ] or
|
126
|
+
raise "#{name} plugin didn't load correctly."
|
127
|
+
end
|
128
|
+
|
129
|
+
return mod
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
### Install the plugin +mod+ in the receiving class.
|
134
|
+
def install_plugin( mod )
|
135
|
+
Strelka.log.debug " adding %p to %p" % [ mod, self ]
|
136
|
+
include( mod )
|
137
|
+
|
138
|
+
if mod.const_defined?( :ClassMethods )
|
139
|
+
cm_mod = mod.const_get(:ClassMethods)
|
140
|
+
Strelka.log.debug " adding class methods from %p" % [ cm_mod ]
|
141
|
+
|
142
|
+
extend( cm_mod )
|
143
|
+
cm_mod.instance_variables.each do |ivar|
|
144
|
+
Strelka.log.debug " copying class instance variable %s" % [ ivar ]
|
145
|
+
ival = cm_mod.instance_variable_get( ivar )
|
146
|
+
|
147
|
+
# Don't duplicate modules/classes or immediates
|
148
|
+
case ival
|
149
|
+
when Module, TrueClass, FalseClass, Symbol, Numeric, NilClass
|
150
|
+
instance_variable_set( ivar, ival )
|
151
|
+
else
|
152
|
+
instance_variable_set( ivar, ival.dup )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
### Load the plugins with the given +names+ and install them.
|
160
|
+
def plugins( *names )
|
161
|
+
# Load the associated Plugin Module objects
|
162
|
+
mods = names.flatten.collect {|name| self.load_plugin(name) }.sort
|
163
|
+
|
164
|
+
# Now install them in reverse order, as the ancestry array should have them
|
165
|
+
# in LIFO order
|
166
|
+
mods.reverse.each {|mod| self.install_plugin(mod) }
|
167
|
+
end
|
168
|
+
alias_method :plugin, :plugins
|
169
|
+
|
170
|
+
end # module ClassMethods
|
171
|
+
|
172
|
+
|
173
|
+
#
|
174
|
+
# :section: Extension Points
|
175
|
+
#
|
176
|
+
|
177
|
+
### The main extension-point for the plugin system. Strelka::App supers to this method
|
178
|
+
### with a block that processes the actual request, and the plugins implement this
|
179
|
+
### method to add their own functionality.
|
180
|
+
def handle_request( request, &block )
|
181
|
+
raise LocalJumpError,
|
182
|
+
"no block given; plugin supering without preserving arguments?" unless block
|
183
|
+
return block.call( request )
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
### An alternate extension-point for the plugin system. Plugins can implement this method
|
188
|
+
### to alter or replace the +request+ before the regular request/response cycle begins.
|
189
|
+
def fixup_request( request )
|
190
|
+
return request
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
### An alternate extension-point for the plugin system. Plugins can implement this method
|
195
|
+
### to alter or replace the +response+ to the specified +request+ after the regular
|
196
|
+
### request/response cycle is finished.
|
197
|
+
def fixup_response( request, response )
|
198
|
+
return response
|
199
|
+
end
|
200
|
+
|
201
|
+
end # module Plugins
|
202
|
+
|
203
|
+
end # class Strelka::App
|
204
|
+
|
205
|
+
|
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'strelka' unless defined?( Strelka )
|
4
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
5
|
+
require 'strelka/app/defaultrouter' unless defined?( Strelka::App::DefaultRouter )
|
6
|
+
|
7
|
+
require 'strelka/app/plugins'
|
8
|
+
|
9
|
+
# Default routing logic for Strelka::Apps
|
10
|
+
module Strelka::App::Routing
|
11
|
+
extend Strelka::App::Plugin
|
12
|
+
include Strelka::Loggable,
|
13
|
+
Strelka::Constants
|
14
|
+
|
15
|
+
run_after :templating, :filters, :parameters
|
16
|
+
|
17
|
+
|
18
|
+
# Class methods to add to classes with routing.
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# The list of routes to pass to the Router when the application is created
|
22
|
+
attr_reader :routes
|
23
|
+
@routes = []
|
24
|
+
|
25
|
+
# The class of object to instantiate for routing
|
26
|
+
attr_accessor :routerclass
|
27
|
+
@routerclass = Strelka::App::DefaultRouter
|
28
|
+
|
29
|
+
|
30
|
+
### Return a Hash of the methods defined by routes.
|
31
|
+
def route_methods
|
32
|
+
return self.instance_methods.grep( /^#{HTTP::RFC2616_VERB_REGEX}\b/ )
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
### Define a route for the GET verb and the given +pattern+.
|
37
|
+
def get( pattern='', options={}, &block )
|
38
|
+
self.add_route( :GET, pattern, options, &block )
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
### Define a route for the POST verb and the given +pattern+.
|
43
|
+
def post( pattern='', options={}, &block )
|
44
|
+
self.add_route( :POST, pattern, options, &block )
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
### Get/set the router class to use for mapping requests to handlers to +newclass.
|
49
|
+
def router( newclass=nil )
|
50
|
+
if newclass
|
51
|
+
Strelka.log.info "%p will use the %p router" % [ self, newclass ]
|
52
|
+
self.routerclass = newclass
|
53
|
+
end
|
54
|
+
|
55
|
+
return self.routerclass
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
### Define a route method for the specified +verb+ and +pattern+ with the
|
60
|
+
### specified +options+, and the +block+ as its body.
|
61
|
+
def add_route( verb, pattern, options={}, &block )
|
62
|
+
|
63
|
+
# Start the name of the route method with the HTTP verb, then split the
|
64
|
+
# route pattern into its components
|
65
|
+
methodparts = [ verb.upcase ]
|
66
|
+
patternparts = self.split_route_pattern( pattern )
|
67
|
+
Strelka.log.debug "Split pattern %p into parts: %p" % [ pattern, patternparts ]
|
68
|
+
|
69
|
+
# Make a method name from the directories and the named captures of the patterns
|
70
|
+
# in the route
|
71
|
+
patternparts.each do |part|
|
72
|
+
if part.is_a?( Regexp )
|
73
|
+
methodparts << '_' + part.names.join( '_' )
|
74
|
+
else
|
75
|
+
methodparts << part
|
76
|
+
end
|
77
|
+
end
|
78
|
+
Strelka.log.debug " route methodname parts are: %p" % [ methodparts ]
|
79
|
+
methodname = methodparts.join( '_' )
|
80
|
+
|
81
|
+
# Define the method using the block from the route as its body
|
82
|
+
Strelka.log.debug " adding route method %p for %p route: %p" % [ methodname, verb, block ]
|
83
|
+
define_method( methodname, &block )
|
84
|
+
|
85
|
+
# Now add all the parts to the routes array for the router created by
|
86
|
+
# instances
|
87
|
+
self.routes << [ verb, patternparts, self.instance_method(methodname), options ]
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
### Split the given +pattern+ into its path components and
|
92
|
+
def split_route_pattern( pattern )
|
93
|
+
pattern.slice!( 0, 1 ) if pattern.start_with?( '/' )
|
94
|
+
|
95
|
+
return pattern.split( '/' ).collect do |component|
|
96
|
+
# Map patterns to their parameter constraint regex
|
97
|
+
if component.start_with?( ':' )
|
98
|
+
Strelka.log.debug " searching for a param for %p" % [ component ]
|
99
|
+
param = self.parameters[ component[1..-1].to_sym ] or
|
100
|
+
raise ScriptError, "no parameter %p defined" % [ component ]
|
101
|
+
param[ :constraint ]
|
102
|
+
else
|
103
|
+
component
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
### Inheritance hook -- inheriting classes inherit their parents' routes table.
|
110
|
+
def inherited( subclass )
|
111
|
+
super
|
112
|
+
subclass.instance_variable_set( :@routes, self.routes.dup )
|
113
|
+
end
|
114
|
+
|
115
|
+
end # module ClassMethods
|
116
|
+
|
117
|
+
|
118
|
+
### Create a new router object for each class with Routing.
|
119
|
+
def initialize( * )
|
120
|
+
super
|
121
|
+
@router ||= self.class.routerclass.new( self.class.routes )
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# The App's router object
|
126
|
+
attr_reader :router
|
127
|
+
|
128
|
+
|
129
|
+
### Dispatch the request using the Router.
|
130
|
+
def handle_request( request, &block )
|
131
|
+
if handler = self.router.route_request( request )
|
132
|
+
return handler.bind( self ).call( request )
|
133
|
+
else
|
134
|
+
finish_with HTTP::NOT_FOUND, "The requested resource was not found on this server."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end # module Strelka::App::Routing
|
139
|
+
|
140
|
+
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'inversion'
|
4
|
+
|
5
|
+
require 'strelka' unless defined?( Strelka )
|
6
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
7
|
+
|
8
|
+
require 'strelka/app/plugins'
|
9
|
+
|
10
|
+
|
11
|
+
# Templating plugin for Strelka::Apps.
|
12
|
+
module Strelka::App::Templating
|
13
|
+
include Strelka::Constants
|
14
|
+
extend Strelka::App::Plugin
|
15
|
+
|
16
|
+
run_before :routing, :filters
|
17
|
+
|
18
|
+
|
19
|
+
# Class methods to add to classes with templating.
|
20
|
+
module ClassMethods
|
21
|
+
|
22
|
+
# The map of template names to template file paths.
|
23
|
+
@template_map = {}
|
24
|
+
attr_reader :template_map
|
25
|
+
|
26
|
+
@layout_template = nil
|
27
|
+
attr_accessor :layout_template
|
28
|
+
|
29
|
+
|
30
|
+
### Get/set the templates declared for the App.
|
31
|
+
def templates( newhash=nil )
|
32
|
+
if newhash
|
33
|
+
self.template_map.merge!( newhash )
|
34
|
+
end
|
35
|
+
|
36
|
+
return self.template_map
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
### Declare a template that will act as a wrapper for all other templates
|
41
|
+
def layout( tmplpath=nil )
|
42
|
+
self.layout_template = tmplpath if tmplpath
|
43
|
+
return self.layout_template
|
44
|
+
end
|
45
|
+
|
46
|
+
end # module ClassMethods
|
47
|
+
|
48
|
+
|
49
|
+
### Preload any templates registered with the template map.
|
50
|
+
def initialize( * )
|
51
|
+
super
|
52
|
+
@template_map = self.load_template_map
|
53
|
+
@layout = self.load_layout_template
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
######
|
58
|
+
public
|
59
|
+
######
|
60
|
+
|
61
|
+
# The map of template names to Inversion::Template instances.
|
62
|
+
attr_reader :template_map
|
63
|
+
|
64
|
+
# The layout template (an Inversion::Template), if one was declarted
|
65
|
+
attr_reader :layout
|
66
|
+
|
67
|
+
|
68
|
+
### Return the template keyed by the given +name+.
|
69
|
+
### :TODO: Add auto-reloading,
|
70
|
+
def template( name )
|
71
|
+
template = self.template_map[ name ] or
|
72
|
+
raise ArgumentError, "no %p template registered!" % [ name ]
|
73
|
+
template.reload if template.changed?
|
74
|
+
return template.dup
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
### Load instances for all the template paths specified in the App's class
|
79
|
+
### and return them in a hash keyed by name (Symbol).
|
80
|
+
def load_template_map
|
81
|
+
return self.class.template_map.inject( {} ) do |map, (name, path)|
|
82
|
+
map[ name ] = Inversion::Template.load( path )
|
83
|
+
map
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
### Load an Inversion::Template for the layout template and return it if one was declared.
|
89
|
+
### If none was declared, returns +nil+.
|
90
|
+
def load_layout_template
|
91
|
+
return nil unless ( lt_path = self.class.layout_template )
|
92
|
+
return Inversion::Template.load( lt_path )
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
### Intercept responses on the way back out and turn them into a Mongrel2::HTTPResponse
|
97
|
+
### with a String for its entity body. It will take action if the response is one of:
|
98
|
+
###
|
99
|
+
### 1. A Mongrel2::Response with an Inversion::Template as its body.
|
100
|
+
### 2. An Inversion::Template by itself.
|
101
|
+
### 3. A Symbol that matches one of the keys of the registered templates.
|
102
|
+
###
|
103
|
+
### In all three of these cases, the return value will be a Mongrel2::Request with a
|
104
|
+
### body set to the rendered value of the template in question, and with its status
|
105
|
+
### set to '200 OK' unless it is already set to something else.
|
106
|
+
###
|
107
|
+
### If there is a registered layout template, and any of the three cases is true, the
|
108
|
+
### layout template is loaded, its #body attributes set to the content template,
|
109
|
+
### and its rendered output set as the body of the response instead.
|
110
|
+
###
|
111
|
+
### Every other response is returned without modification.
|
112
|
+
def handle_request( request, &block )
|
113
|
+
response = super
|
114
|
+
self.log.debug "Templating: examining %p response." % [ response.class ]
|
115
|
+
template = nil
|
116
|
+
|
117
|
+
# Response is a template name
|
118
|
+
if response.is_a?( Symbol ) && self.template_map.key?( response )
|
119
|
+
self.log.debug " response is a template name (Symbol); using the %p template" % [ response ]
|
120
|
+
template = self.template( response )
|
121
|
+
response = request.response
|
122
|
+
|
123
|
+
# Template object
|
124
|
+
elsif response.is_a?( Inversion::Template )
|
125
|
+
self.log.debug " response is an %p; wrapping it in a Response object" % [ response.class ]
|
126
|
+
template = response
|
127
|
+
response = request.response
|
128
|
+
|
129
|
+
# Template object already in a Response
|
130
|
+
elsif response.is_a?( Mongrel2::Response ) && response.body.is_a?( Inversion::Template )
|
131
|
+
template = response.body
|
132
|
+
self.log.debug " response is a %p in the body of a %p" % [ template.class, response.class ]
|
133
|
+
|
134
|
+
# Not templated; returned as-is
|
135
|
+
else
|
136
|
+
self.log.debug " response isn't templated; returning it as-is"
|
137
|
+
return response
|
138
|
+
end
|
139
|
+
|
140
|
+
# Wrap the template in a layout if there is one
|
141
|
+
if self.layout
|
142
|
+
l_template = self.layout.dup
|
143
|
+
self.log.debug " wrapping response in layout %p" % [ l_template ]
|
144
|
+
l_template.body = template
|
145
|
+
template = l_template
|
146
|
+
end
|
147
|
+
|
148
|
+
self.log.debug " rendering the template into the response body"
|
149
|
+
response.body = template.render
|
150
|
+
response.status ||= HTTP::OK
|
151
|
+
|
152
|
+
return response
|
153
|
+
end
|
154
|
+
|
155
|
+
end # module Strelka::App::Templating
|
156
|
+
|
157
|
+
|