strelka 0.0.1pre4
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/.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
|
+
|