maveric 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +56 -0
- data/lib/maveric.rb +223 -627
- metadata +53 -38
- data/lib/maveric/extensions.rb +0 -111
- data/lib/maveric/fastcgi.rb +0 -18
- data/lib/maveric/mongrel.rb +0 -24
- data/lib/maveric/sessions.rb +0 -112
- data/lib/maveric/webrick.rb +0 -30
data/README
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
- Maveric
|
2
|
+
|
3
|
+
Maveric is a MVC overlay for Rack.
|
4
|
+
|
5
|
+
The dominant idea behind Maveric is as little magic as possible, being as
|
6
|
+
flexible as possible.
|
7
|
+
|
8
|
+
The use of a Maveric is simple.
|
9
|
+
|
10
|
+
class MyApp < Maveric
|
11
|
+
def get
|
12
|
+
render {'<p>Hello World!</p>'}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
env['PATH_INFO'] # => '/'
|
17
|
+
MyApp.call(env)[0] # => 200
|
18
|
+
|
19
|
+
env['PATH_INFO'] # => '/index'
|
20
|
+
MyApp.call(env)[0] # => 200
|
21
|
+
|
22
|
+
By default the method used to generate a response is the lower case form of the
|
23
|
+
http method. GET requests call #get, POST requests call #post, and so on. A 404
|
24
|
+
is returned if a the appropriate method is not defined.
|
25
|
+
|
26
|
+
-- Actions
|
27
|
+
|
28
|
+
To override this you may redefine #action= as needed.
|
29
|
+
|
30
|
+
class MyApp < Maveric
|
31
|
+
def action= method
|
32
|
+
if method == :get and @request.path_info == '/'
|
33
|
+
@action = :index
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
def index
|
39
|
+
render {'<p>Hello World!</p>'}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
-- The Render Method
|
44
|
+
|
45
|
+
Provides a simple way to format data.
|
46
|
+
|
47
|
+
class MyApp < Maveric
|
48
|
+
module Views
|
49
|
+
def html data
|
50
|
+
'<p>'+data.to_s+'</p>'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
def get
|
54
|
+
render :html { 'Hello World!' }
|
55
|
+
end
|
56
|
+
end
|
data/lib/maveric.rb
CHANGED
@@ -1,698 +1,294 @@
|
|
1
|
-
|
2
|
-
# = Maveric: A simple, non-magical, framework
|
3
|
-
#
|
4
|
-
# == Resources
|
5
|
-
#
|
6
|
-
# Maveric releases can normally be found on
|
7
|
-
# {rubyforge}[http://rubyforge.org/projects/maveric/].
|
8
|
-
# Documentation sits at {rubyforge}[http://maveric.rubyforge.org/] as well.
|
9
|
-
# Maveric's code base resides at rubyforge as well, but will eventually be
|
10
|
-
# located at {devjavu}[http://maveric.devjavu.com/].
|
11
|
-
#
|
12
|
-
# Maveric is also listed at the RAA as
|
13
|
-
# {maveric}[http://raa.ruby-lang.org/project/maveric/]. This page lags a bit
|
14
|
-
# behind rubyforge in terms of updates.
|
15
|
-
#
|
16
|
-
# == Version
|
17
|
-
#
|
18
|
-
# We are at version 0.4.0 and approaching a solid, stable, and full release.
|
19
|
-
# It's not often I complete a project to a public release, often relegating the
|
20
|
-
# the half completed code to my code vault and it never seeing the light of day
|
21
|
-
# again. So this is definately a yey!
|
22
|
-
#
|
23
|
-
# Version 0.4.0 refines the Route class and interactions by utilizing a Struct.
|
24
|
-
# Maveric no longer depends on any lib outside of core or stdlibs allowing you
|
25
|
-
# to only install Maveric for functionality. We are now using -w and -d switches
|
26
|
-
# during development and striving for concise *and* correct code.
|
27
|
-
#
|
28
|
-
# Maveric addons have been revised, giving the current functionality of Maveric
|
29
|
-
# to FastCGI and Mongrel. A plugin to WEBrick has been provided, but is untested
|
30
|
-
# and not supported.
|
31
|
-
#
|
32
|
-
# Check other documentation for a full list of changes.
|
33
|
-
#
|
34
|
-
# == Authors
|
35
|
-
#
|
36
|
-
# Maveric is designed and coded by {blink}[mailto:blinketje@gmail.com]
|
37
|
-
# of #ruby-lang on irc.freenode.net
|
38
|
-
#
|
39
|
-
# == Licence
|
40
|
-
#
|
41
|
-
# This software is licensed under the
|
42
|
-
# {CC-GNU LGPL}[http://creativecommons.org/licenses/LGPL/2.1/]
|
43
|
-
#
|
44
|
-
# == Disclaimer
|
45
|
-
#
|
46
|
-
# This software is provided "as is" and without any express or
|
47
|
-
# implied warranties, including, without limitation, the implied
|
48
|
-
# warranties of merchantability and fitness for a particular purpose.
|
49
|
-
# Authors are not responsible for any damages, direct or indirect.
|
50
|
-
#
|
51
|
-
# = Maveric: History
|
52
|
-
#
|
53
|
-
# Maveric was initially designed as a replacement for Camping in the style of a
|
54
|
-
# Model-View-Controller framework. Early implementations aimed to reduce the
|
55
|
-
# amount of "magic" to 0. Outstanding magic of Camping is that nothing is
|
56
|
-
# related due to the reading, gsub-ing, and eval-ing of the Camping source file
|
57
|
-
# Also, even the unobfuscated/unabridged version of Camping is a bit hard to
|
58
|
-
# follow at times.
|
59
|
-
#
|
60
|
-
# However, the result of this initial attempt was a spaghetti of winding code,
|
61
|
-
# empty template modules, and rediculous inheritance paths between the template
|
62
|
-
# modules and a plethora of dynamically generated classes and modules. I got
|
63
|
-
# more complaints with that particular jumble of code than any other, evar.
|
64
|
-
#
|
65
|
-
# The next iteration of Maveric was a seeking of simplification and departure
|
66
|
-
# from the concept of cloning Camping and its functionality and settling for
|
67
|
-
# Camping-esqe. At this point, I started talking to ezmobius on #ruby-lang and
|
68
|
-
# their Merb project. We talked a bit about merging and brainstorming but my
|
69
|
-
# lone wolf tendencies stirred me away from such an endeavour, but I came away
|
70
|
-
# a compatriot and inspiration for a new routing methodology inspired by Merb's.
|
71
|
-
# The result is the Route that's been stable since, only varying in method
|
72
|
-
# placement and data management.
|
73
|
-
#
|
74
|
-
# After building to a version that stood on it's owned and was able to run a
|
75
|
-
# variance of my website as functional as the Camping version. I noticed a few
|
76
|
-
# tendncies of my coding and refactored. I also redesigned the concept from a
|
77
|
-
# modules that included the Maveric module to that of subclassing Maveric, which
|
78
|
-
# is a Mongrel::HttpHandlerPlugin. This allowed the running of Maveric apps
|
79
|
-
# without using Mongrel::CampingHandler as well as not worrying about namespace
|
80
|
-
# clobbering.
|
81
|
-
#
|
82
|
-
# Because ehird from #ruby-lang was whining about it so much I removed its
|
83
|
-
# dependancy on Mongrel sooner than later. Maveric should now be very maveric.
|
84
|
-
#
|
85
|
-
# Because Maveric 0.1.0 worked, but still didn't do things the way I wanted
|
86
|
-
# I revised and refactored codes and algorithms into future versions which will
|
87
|
-
# hopefully be all that I want. Everything beyond 1.0.0 will be improvements and
|
88
|
-
# extensions.
|
89
|
-
#
|
90
|
-
# == Not so Maveric
|
91
|
-
#
|
92
|
-
# Maveric has additional libs to allow you to run Maveric behind different
|
93
|
-
# http server implementations. CGI, FastCGI, and Mongrel are supported. If
|
94
|
-
# another service exists that you'd like to have supported please submit a
|
95
|
-
# patch of a good reason why.
|
96
|
-
#
|
97
|
-
# For these examples we will utilize the simplest functional Maveric possible
|
98
|
-
# require 'maveric'
|
99
|
-
# class Maveric::Index < Maveric::Controller
|
100
|
-
# def get
|
101
|
-
# 'Hello, world!'
|
102
|
-
# end
|
103
|
-
# end
|
104
|
-
#
|
105
|
-
# To use CGI: (not cgi.rb)
|
106
|
-
# puts Maveric.new.dispatch.to_s
|
107
|
-
#
|
108
|
-
# To use FastCGI:
|
109
|
-
# require 'maveric/fastcgi'
|
110
|
-
# ::Maveric::FCGI(MyMaveric.new)
|
111
|
-
#
|
112
|
-
# To use Mongrel:
|
113
|
-
# require 'maveric/mongrel'
|
114
|
-
# h = ::Mongrel::HttpHandler ip, port
|
115
|
-
# h.register mountpoint, MyMaveric.new
|
116
|
-
# h.join.run
|
117
|
-
#
|
118
|
-
# However, if your script is mounted at a point other than /, use the
|
119
|
-
# :path_prefix option to adjust your routes. This affects all routes generated
|
120
|
-
# from MyMaveric.
|
121
|
-
# mav = MyMaveric.new :path_prefix => mountpoint
|
122
|
-
# ::Maveric::FCGI(mav)
|
123
|
-
# h.register mountpoint, mav
|
124
|
-
#
|
125
|
-
# --------------------------------
|
1
|
+
# Author: scytrin@stadik.net
|
126
2
|
require 'uri'
|
127
|
-
require '
|
128
|
-
require 'maveric/extensions'
|
129
|
-
|
130
|
-
##
|
131
|
-
# = The Maveric: Yeargh.
|
132
|
-
# The Maveric may be used alone or may be used in a cadre of loosely aligned
|
133
|
-
# Maveric instances. The Maveric stands tall and proud, relying on it's fine
|
134
|
-
# family and it's inventory of goods to get things done with little magic or
|
135
|
-
# trickery.
|
136
|
-
class Maveric
|
137
|
-
## Implementation details.
|
138
|
-
VERSION='0.4.0'
|
139
|
-
## Standard end of line for HTTP
|
140
|
-
EOL="\r\n"
|
141
|
-
## Group 1 wil contain a boundary for multipart/form-data bodies.
|
142
|
-
MP_BOUND_REGEX = /\Amultipart\/form-data.*boundary=\"?([^\";, ]+)\"?/n
|
143
|
-
## Contains a list of environment preprocessors.
|
144
|
-
@prepare_env = []
|
145
|
-
|
146
|
-
# I hate putting utility methods here, rather than in some module, but
|
147
|
-
# I don't see how to do it without getting messy.
|
148
|
-
class << self
|
149
|
-
##
|
150
|
-
# Parses a query string by breaking it up around the
|
151
|
-
# delimiting characters. You can also use this to parse
|
152
|
-
# cookies by changing the characters used in the second
|
153
|
-
# parameter (which defaults to ';,').
|
154
|
-
#
|
155
|
-
# This will return a hash of parameters, values being contained in an
|
156
|
-
# array. As a warning, the default value is an empty array, not nil.
|
157
|
-
def query_parse(qs, delim = '&;')
|
158
|
-
(qs||'').split(/[#{delim}] */n).inject({}) { |h,p|
|
159
|
-
k, v = URI.decode(p).split('=',2)
|
160
|
-
(h[k]||=[]) << v
|
161
|
-
h
|
162
|
-
}
|
163
|
-
end
|
3
|
+
require 'rack'
|
164
4
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
#
|
169
|
-
# Might need to be rehauled to match query_parse's behaviour.
|
170
|
-
def parse_multipart boundary, body
|
171
|
-
values = {}
|
172
|
-
bound = /(?:\r?\n|\A)#{Regexp::quote('--'+boundary)}(?:--)?\r$/
|
173
|
-
until body.eof?
|
174
|
-
fv = {}
|
175
|
-
until body.eof? or /^#{EOL}$/ =~ l
|
176
|
-
case l = body.readline
|
177
|
-
when /^Content-Type: (.+?)(\r$|\Z)/m
|
178
|
-
fv[:type] = $1
|
179
|
-
when /^Content-Disposition: form-data;/
|
180
|
-
$'.scan(/(?:\s(\w+)="([^"]+)")/) {|w| fv[w[0].intern] = w[1] }
|
181
|
-
end
|
182
|
-
end
|
5
|
+
module Maveric
|
6
|
+
# Should contain methods to be shared amongst various maverics
|
7
|
+
module Helpers; end
|
183
8
|
|
184
|
-
|
185
|
-
|
186
|
-
o << buf.chomp and break if bound =~ line
|
187
|
-
o << buf
|
188
|
-
line
|
189
|
-
end # Merb and Camping do it faster.
|
9
|
+
# Should contain various views to be utilized by various maverics
|
10
|
+
module Views; attr_reader :maveric; TEMPLATES = {}; end
|
190
11
|
|
191
|
-
fv[:tempfile].rewind if fv.key? :tempfile
|
192
|
-
values[fv[:name]] = fv.key?(:filename) ? fv : o
|
193
|
-
end
|
194
|
-
body.rewind rescue nil # FCGI::Stream fun.
|
195
|
-
values
|
196
|
-
end
|
197
12
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
a << c if c < ::Maveric::Controller
|
210
|
-
a += nested_controllers c, stk unless c < ::Maveric
|
211
|
-
a
|
212
|
-
end.compact.flatten
|
213
|
-
end
|
13
|
+
# The current instance is stored in the passed environment under the key
|
14
|
+
# 'maveric'. A Rack::Request of the current request is provided at @request.
|
15
|
+
def initialize env
|
16
|
+
env['maveric'] = self
|
17
|
+
@request = Rack::Request.new env
|
18
|
+
@route, @action = self.class.routes.empty? ?
|
19
|
+
[nil, :index] : self.class.routes.find{|(r,d)| r === env['PATH_INFO'] }
|
20
|
+
extend self.class::Helpers
|
21
|
+
self.init_hook if self.respond_to? :init_hook
|
22
|
+
end
|
23
|
+
attr_reader :request, :route, :action
|
214
24
|
|
215
|
-
##
|
216
|
-
# Sets up a new Maveric with everything it needs for normal
|
217
|
-
# operation. Many things are either copied or included from it's
|
218
|
-
# parent. Models and Views are included by their respective modules.
|
219
|
-
def inherited klass
|
220
|
-
super
|
221
|
-
parent = self
|
222
|
-
klass.class_eval do
|
223
|
-
const_set(:Models, Module.new).
|
224
|
-
module_eval { include parent::Models }
|
225
|
-
const_set(:Views, Module.new).
|
226
|
-
module_eval { include parent::Views }
|
227
|
-
@prepare_env = parent.prepare_env.dup
|
228
|
-
end
|
229
|
-
end
|
230
25
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
26
|
+
# Will return a 404 type Response if the set action is invalid. Will
|
27
|
+
# generate a new response if a previous one is not cached or +renew+ is true.
|
28
|
+
#
|
29
|
+
# If the response responds to :finish, it will return the result, otherwise
|
30
|
+
# the response itself will be returned.
|
31
|
+
def response renew=false
|
32
|
+
raise Response, 404 unless @action and respond_to? @action
|
33
|
+
@response = __send__ @action if not @response or renew
|
34
|
+
rescue Response
|
35
|
+
warn('Response: '+$!.inspect)
|
36
|
+
@response = $!.to_a
|
37
|
+
rescue
|
38
|
+
warn('Rescue: '+$!.inspect)
|
39
|
+
body = $!.message + "\n\t" + $@[0..10]*"\n\t"
|
40
|
+
@request.env['rack.errors'].puts body
|
41
|
+
body << "\n\t" << $@[11..-1]*"\n\t"
|
42
|
+
@request.env.sort.each{|(k,v)| body << "\n#{k}\n\t#{v.inspect}" }
|
43
|
+
@response = [ 500, {
|
44
|
+
'Content-Type' => 'text/plain',
|
45
|
+
'Content-Length' => body.length.to_s
|
46
|
+
}, body.to_a ]
|
252
47
|
end
|
253
48
|
|
254
|
-
## Alias to Maveric.prepare_env
|
255
|
-
def prepare_env(*a, &b)
|
256
|
-
self.class.prepare_env(*a, &b)
|
257
|
-
end
|
258
49
|
|
259
|
-
|
260
|
-
# Sets @options from +opts+ and initializes @routes.
|
261
|
-
# Adds routes from nested Controllers.
|
262
|
-
def initialize opts={}
|
263
|
-
@options = {:path_prefix => self.class.to_path(true)}
|
264
|
-
@options.update opts
|
265
|
-
@routes = Set.new
|
266
|
-
self.class.nested_controllers.each do |cont|
|
267
|
-
routes = cont.routes
|
268
|
-
routes ||= [[cont.to_path(0).sub(/^#{self.class.to_path(0)}\/?/,'/'),{}]]
|
269
|
-
routes.each {|route,ropts| add_route cont, route, ropts }
|
270
|
-
end
|
271
|
-
end
|
50
|
+
private
|
272
51
|
|
273
|
-
## Passes arguments to ::Maveric::Route.new and adds the result to @routes.
|
274
|
-
def add_route controller, path, opts={}
|
275
|
-
@routes << ::Maveric::Route.new(controller, path, opts)
|
276
|
-
end
|
277
52
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
53
|
+
# Powerhouse method to build a response. TODO: doc
|
54
|
+
def respond *args, &blk
|
55
|
+
headers = Hash === args.last ? args.pop : {}
|
56
|
+
seed = (Symbol === args.last && args.last) ? '' : args.pop
|
57
|
+
views, args = args.reverse.partition{|e| Symbol === e }
|
58
|
+
|
59
|
+
bad = views.map{|e|e.to_s} - self.class::Views.instance_methods
|
60
|
+
raise ArgumentError, 'Invalid views: '+bad.inspect unless bad.empty?
|
61
|
+
warn 'Invalid arguments: '+args.inspect unless args.empty?
|
62
|
+
|
63
|
+
response = Rack::Response.new
|
64
|
+
response.headers.update self.class.headers
|
65
|
+
response.headers.update headers.
|
66
|
+
reject{|(k,v)| !k.is_a? String or !v.is_a? String }
|
67
|
+
response.extend self.class::Views
|
68
|
+
response.instance_variable_set '@maveric', self
|
69
|
+
|
70
|
+
catch :response do
|
71
|
+
yield response if block_given?
|
72
|
+
body = views.inject(seed){|b,v| response.__send__(v,b) }.to_s
|
73
|
+
response.length = Rack::Utils.bytesize body
|
74
|
+
response.body = body.to_a
|
75
|
+
return response.finish
|
76
|
+
end
|
285
77
|
end
|
286
78
|
|
287
|
-
|
288
|
-
#
|
289
|
-
#
|
290
|
-
def
|
291
|
-
|
79
|
+
|
80
|
+
# Provides a simple way of generating a redirection response, relative to
|
81
|
+
# the current url.
|
82
|
+
def redirect uri
|
83
|
+
uri = URI(uri).normalize
|
84
|
+
uri = URI(@request.url).merge uri
|
85
|
+
[ 303, { 'Location'=>uri.to_s,
|
86
|
+
'Content-Type'=>'text/plain',
|
87
|
+
'Content-Length'=>'0'
|
88
|
+
}, [] ]
|
292
89
|
end
|
293
90
|
|
294
|
-
attr_reader :options, :routes
|
295
|
-
|
296
|
-
##
|
297
|
-
# Maveric's cue to start doing the heavy lifting. +env+ should be
|
298
|
-
# a hash with the typical assignments from an HTTP environment or crying
|
299
|
-
# and whining will ensue. +req_body+ should be an IO compatible instance.
|
300
|
-
def dispatch req_body=$stdin, env=ENV, opts={}
|
301
|
-
begin
|
302
|
-
prepare_environment env unless env.key? :maveric
|
303
|
-
raise RuntimeError, [404, "Page not found."] unless env[:route]
|
304
|
-
|
305
|
-
# If this is a POST request we need to load data from the body
|
306
|
-
if env['REQUEST_METHOD'] =~ /^post$/i
|
307
|
-
params = env.key?(:multipart_boundary) ?
|
308
|
-
parse_multipart(env[:multipart_boundary], req_body) :
|
309
|
-
::Maveric.query_parse(req_body.read)
|
310
|
-
env[:params].update params
|
311
|
-
end
|
312
91
|
|
313
|
-
|
314
|
-
|
315
|
-
error(e)
|
316
|
-
end
|
92
|
+
def routes_to dest
|
93
|
+
self.class.routes.select{|(r,d)| d === dest }
|
317
94
|
end
|
318
95
|
|
319
|
-
## Override for custom error handling and/or styling.
|
320
|
-
def error err=$!
|
321
|
-
err
|
322
|
-
end
|
323
96
|
|
324
|
-
|
325
|
-
|
326
|
-
# Run through actions specified by Maveric.prepare_env in the order stated,
|
327
|
-
# testing env against act[:test] if present to determine if it will be used,
|
328
|
-
# and runs the action on env.
|
329
|
-
def prepare_environment env
|
330
|
-
# DEV-NOTE: This method is seperated from prepare_env, in contrast to
|
331
|
-
# #before and #after in Controller, due to the fact one Maveric instance
|
332
|
-
# exists for each service, rather than for each occurance of env per
|
333
|
-
# request.
|
334
|
-
env.update :maveric => self
|
335
|
-
prepare_env.each do |act|
|
336
|
-
next unless act[:test].nil? or act[:test][env]
|
337
|
-
(act[:run] || method(act[:name]))[env]
|
338
|
-
end
|
97
|
+
def route_to dest
|
98
|
+
routes_to(dest).find{|(r,d)| String === d }
|
339
99
|
end
|
340
100
|
|
341
|
-
### Does this count as magic? No, just defaults.
|
342
|
-
prepare_env :route do |env|
|
343
|
-
env[:route] = env[:maveric].match env['REQUEST_URI']
|
344
|
-
end
|
345
101
|
|
346
|
-
prepare_env :cookies do |env|
|
347
|
-
env[:cookies] = ::Maveric.query_parse env['HTTP_COOKIE'], ';,'
|
348
|
-
end
|
349
102
|
|
350
|
-
|
351
|
-
|
352
|
-
|
103
|
+
# Methods provided to construct a maveric
|
104
|
+
module Class
|
105
|
+
# Default headers for Maveric instantiation.
|
106
|
+
attr_reader :routes, :headers
|
107
|
+
attr_writer :mount
|
108
|
+
|
109
|
+
# Scours through './templates', unless provided an alternate directory,
|
110
|
+
# and adds templates and a method to maveric::Views. Currently handles
|
111
|
+
# haml and erb files.
|
112
|
+
def add_templates directory='templates/*'
|
113
|
+
Dir.glob directory do |file| p file
|
114
|
+
next unless File.readable? file and tmpl = File.read(file)
|
115
|
+
next unless extn = File.extname(file) and not extn.empty?
|
116
|
+
next unless name = File.basename(file, extn) and not name.empty?
|
117
|
+
view = case extn
|
118
|
+
when '.haml'
|
119
|
+
require 'haml' rescue next
|
120
|
+
puts "Defining HAML template view " + name.inspect
|
121
|
+
haml = Haml::Engine.new tmpl
|
122
|
+
proc{|content| haml.render self, :content => content }
|
123
|
+
when '.erb'
|
124
|
+
require 'erb' rescue next
|
125
|
+
puts "Defining ERB template view " + name.inspect
|
126
|
+
erb = ERB.new tmpl
|
127
|
+
erb.filename = file
|
128
|
+
proc{|content| erb.result(binding) }
|
129
|
+
else
|
130
|
+
puts "Defining sprintf template view " + name.inspect
|
131
|
+
proc{|content| sprintf self, content }
|
132
|
+
end
|
353
133
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
and env['CONTENT_TYPE'] =~ MP_BOUND_REGEX
|
358
|
-
env[:multipart_boundary] = $1
|
134
|
+
self::Views::TEMPLATES[name] = view
|
135
|
+
self::Views.__send__ :define_method, name, &view
|
136
|
+
end
|
359
137
|
end
|
360
|
-
end
|
361
138
|
|
362
|
-
|
363
|
-
|
364
|
-
def
|
139
|
+
# Instantiates a maveric with the provided environment, then calls
|
140
|
+
# maveric#response
|
141
|
+
def call env
|
142
|
+
new(env).response
|
143
|
+
end
|
365
144
|
|
366
|
-
def
|
367
|
-
|
145
|
+
def routing mountpoint, routes
|
146
|
+
@mount = mountpoint
|
147
|
+
routes.each{|d,a| route d, *a }
|
368
148
|
end
|
369
149
|
|
370
|
-
def
|
371
|
-
|
150
|
+
def route d, *a
|
151
|
+
a.flatten.each{|r| @routes << [r, d] }
|
372
152
|
end
|
373
153
|
|
374
|
-
def
|
375
|
-
|
376
|
-
::Erubis::Eruby.new(raw_file(filename)).result(binding)
|
154
|
+
def mount
|
155
|
+
@mount || self.name.downcase.gsub(/^|::/, '/')
|
377
156
|
end
|
378
157
|
|
379
|
-
def
|
380
|
-
@
|
158
|
+
def self.extend_object obj
|
159
|
+
obj.instance_variable_set '@routes', []
|
160
|
+
obj.instance_variable_set '@headers', Rack::Utils::HeaderHash.new
|
161
|
+
super
|
381
162
|
end
|
382
|
-
end
|
383
163
|
|
384
|
-
|
385
|
-
|
164
|
+
def inspect
|
165
|
+
"#{self.name}:'#{mount}':#{routes.inspect}"
|
166
|
+
end
|
386
167
|
end
|
387
|
-
end
|
388
168
|
|
389
|
-
##
|
390
|
-
# Controllers are the classes that do the actual handling and processing of
|
391
|
-
# requests. The number of normal methods is kept low to prevent clobbering by
|
392
|
-
# user defined methods.
|
393
|
-
#
|
394
|
-
# === Placing a Controller
|
395
|
-
#
|
396
|
-
# They may be defined in any location, but if they are defined in a nested
|
397
|
-
# location within the Maveric class being used to serve at a particular
|
398
|
-
# location, on instantialization of the Maveric they will automatically
|
399
|
-
# be added at either the routes specified in their class definition or
|
400
|
-
# at the default route which is derived from their name and their nesting.
|
401
|
-
#
|
402
|
-
# === Working in Controller
|
403
|
-
#
|
404
|
-
# Within an instance of Controller: @env contains the environment hash; @in
|
405
|
-
# contains the request body, and @cookies contains a hash of cookie values.
|
406
|
-
#
|
407
|
-
# In addition @status, @header, and @body may be set to appropriate values
|
408
|
-
# for the response. Note that @headers should be a Hash, @status should be an
|
409
|
-
# Integer corresponding to a real HTTP status code, and @body should contain
|
410
|
-
# a String.
|
411
|
-
#
|
412
|
-
# The instance variable @action contains a Symbol corresponding to the
|
413
|
-
# Controller method being called. The result of this call is assigned to
|
414
|
-
# @body.
|
415
|
-
#
|
416
|
-
# Those are the only instance variables you should be warned against playing
|
417
|
-
# with frivously. All instance variables are initially assigned before the
|
418
|
-
# before actions have been run, with the exception of @body which is set
|
419
|
-
# after @action is called.
|
420
|
-
#
|
421
|
-
# It's recommended all work be done within methods of Controller, rather than
|
422
|
-
# overriding the inherited methods of Controller, but really it's up to you.
|
423
|
-
#
|
424
|
-
# NOTE: Due to the extension of the Controller instance by the View and Models
|
425
|
-
# modules of the operating Maveric, one must take care not to overlap method
|
426
|
-
# names. I hope to remove this 'feature' in a future version.
|
427
|
-
class Maveric::Controller
|
428
|
-
REQUEST_METHODS = [:post, :get, :put, :delete, :head] # CRUDY
|
429
|
-
@before = []
|
430
|
-
@after = []
|
431
169
|
|
170
|
+
|
171
|
+
@maverics = [] unless defined? @maverics
|
172
|
+
@last_hash = nil
|
173
|
+
@map = nil
|
432
174
|
class << self
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
@before = parent.before.dup
|
438
|
-
@after = parent.after.dup
|
439
|
-
@routes = []
|
440
|
-
end
|
175
|
+
attr_reader :maverics
|
176
|
+
|
177
|
+
def inspect
|
178
|
+
"Maveric#{maverics.inspect}"
|
441
179
|
end
|
442
180
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
181
|
+
# Generates a Rack::URLMap compatible hash composed of all maverics and
|
182
|
+
# their mount points. Will be regenerated if the list of maverics have
|
183
|
+
# changed.
|
184
|
+
def map
|
185
|
+
return @map if @map and @last_hash == @maverics.hash
|
186
|
+
@last_hash = @maverics.hash
|
187
|
+
@map = @maverics.inject({}) do |h,m|
|
188
|
+
h.store m.mount, m
|
189
|
+
h
|
190
|
+
end
|
447
191
|
end
|
448
192
|
|
449
|
-
|
450
|
-
def
|
451
|
-
|
452
|
-
|
453
|
-
@routes << [r, o]
|
193
|
+
# Generates a Rack::URLMap from the result of Maveric.map
|
194
|
+
def urlmap
|
195
|
+
return @urlmap if @urlmap and @last_hash == @maverics.hash
|
196
|
+
@urlmap = Rack::URLMap.new self.map
|
454
197
|
end
|
455
198
|
|
456
|
-
|
457
|
-
def
|
458
|
-
|
199
|
+
# Maps to a call to the result of Maveric.urlmap
|
200
|
+
def call env
|
201
|
+
self.urlmap.call env
|
459
202
|
end
|
460
203
|
|
461
|
-
|
462
|
-
def
|
463
|
-
return
|
464
|
-
|
204
|
+
# Returns itself, or a Rack::Cascade when provided an app
|
205
|
+
def new app=nil
|
206
|
+
return self unless app
|
207
|
+
Rack::Cascade.new [app, self]
|
465
208
|
end
|
466
209
|
|
467
|
-
|
468
|
-
#
|
469
|
-
#
|
470
|
-
#
|
471
|
-
# If the second argument is omitted or a collection of hashed options,
|
472
|
-
# then an action is added to the array of actions with +action+ as its
|
473
|
-
# name. If a block is provided it will be run, else the controller instance
|
474
|
-
# method of the same name of the action will be run.
|
475
|
-
# For conditional execution of the action you may provide a list of what
|
476
|
-
# actions it should exclusively run on or not run on, via :only and
|
477
|
-
# :exclude respectively.
|
210
|
+
# When a maveric is subclassed, the superclass' headers inherited. Views
|
211
|
+
# and Helpers modules are also included into the subclass' corresponding
|
212
|
+
# modules.
|
478
213
|
#
|
479
|
-
#
|
480
|
-
#
|
481
|
-
# controller.
|
214
|
+
# By default, the first class to inherit from Maveric is assigned the mount
|
215
|
+
# point of '/'.
|
482
216
|
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
def
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
opts[:exclude] = [*opts[:exclude]] if opts.key? :exclude
|
497
|
-
@before << opts.update(:name => action, :run => block)
|
498
|
-
end
|
499
|
-
else @before end
|
217
|
+
# A maveric's default mount point is by default derived from it's full
|
218
|
+
# name. For example: Object would be '/object', Module would be '/module',
|
219
|
+
# and Rack::Auth::OpenID would be '/rack/auth/openid'.
|
220
|
+
def included obj
|
221
|
+
super
|
222
|
+
obj.extend Maveric::Class
|
223
|
+
%w'Helpers Views'.each do |name|
|
224
|
+
next if obj.const_defined?(name)
|
225
|
+
obj.const_set(name, Module.new).
|
226
|
+
instance_eval{ include Maveric.const_get(name) }
|
227
|
+
end
|
228
|
+
obj.mount = '/' if @maverics.empty?
|
229
|
+
@maverics << obj
|
500
230
|
end
|
501
231
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
# If a Symbol or String is given with no block, during Controller
|
506
|
-
# initialization after the action is called, the corresponding
|
507
|
-
# method is called with the Controller instance as an argument.
|
508
|
-
# If a block is given, the block is called with the controller
|
509
|
-
# instance as an argument instead.
|
510
|
-
#
|
511
|
-
# Additional arguments include :only and :exclude, whose values should
|
512
|
-
# be a Symbol or an Array of such that correspond with actions that
|
513
|
-
# they should only run on or not run on.
|
514
|
-
#
|
515
|
-
# NOTE: If you are referencing instance variables within the action,
|
516
|
-
# it is recommended that you create a method rather than a block.
|
517
|
-
def after action=nil, opts={}, &block
|
518
|
-
if action.is_a? Symbol
|
519
|
-
if opts.is_a? ::Maveric::Controller
|
520
|
-
@after.each do |act|
|
521
|
-
next unless (act[:only].nil? or act[:only].include? action) \
|
522
|
-
and (act[:exclude].nil? or not act[:exclude].include? action)
|
523
|
-
(act[:run] || controller.method(act[:name]))[opts]
|
524
|
-
end
|
525
|
-
else # opts should be a Hash
|
526
|
-
opts[:only] = [*opts[:only]] if opts.key? :only
|
527
|
-
opts[:exclude] = [*opts[:exclude]] if opts.key? :exclude
|
528
|
-
@after << opts.update(:name => action, :run => block)
|
529
|
-
end
|
530
|
-
else @after end
|
232
|
+
def extend_object obj
|
233
|
+
warn 'Maveric should only be included.'
|
234
|
+
return obj
|
531
235
|
end
|
532
236
|
end
|
533
237
|
|
534
|
-
## Alias to Controller.before
|
535
|
-
def before(*a, &b)
|
536
|
-
self.class.before(*a, &b)
|
537
|
-
end
|
538
238
|
|
539
|
-
## Alias to Controller.after
|
540
|
-
def after(*a, &b)
|
541
|
-
self.class.before(*a, &b)
|
542
|
-
end
|
543
239
|
|
544
|
-
|
545
|
-
#
|
546
|
-
#
|
547
|
-
#
|
548
|
-
#
|
240
|
+
# Response is an Exception class that can be raised from within a call to
|
241
|
+
# maveric#response, with 401, 403, and 404 errors provided.
|
242
|
+
#
|
243
|
+
# <tt>raise Maveric::Response, 404</tt> will look up 404 via Response#[] and
|
244
|
+
# if a value is found, it is utilized to set the status, headers, and body.
|
549
245
|
#
|
550
|
-
#
|
551
|
-
#
|
552
|
-
# * :route, which contains a struct instance which was returned by
|
553
|
-
# Route#match that helped us get here.
|
554
|
-
# * :params, containing a hash of QUERY_STRING data. If REQUEST_METHOD
|
555
|
-
# is 'post' then parameter data from the request bod is overlayed.
|
556
|
-
# * :cookies, which contains hashed data parsed from HTTP_COOKIE.
|
557
|
-
# * plugins or extensions to Maveric may add other special hash pairs
|
558
|
-
# to @env, such as :session, by 'maveric/sessions'.
|
559
|
-
# * @cookies contains a hash
|
560
|
-
# header. If you plan on adding or altering cookie data, do it here.
|
561
|
-
# * @in contains a reference to the input stream of the http request. Note that
|
562
|
-
# if REQUEST_METHOD is 'post' then it has probably already been read.
|
563
|
-
# * @action is the controller instance method to be called. The returned
|
564
|
-
# value of the call should be a string, which will be assigned to @body.
|
565
|
-
# * @status, by default is 200. And @headers, which by default only sets
|
566
|
-
# Content-Type to 'text/html'.
|
246
|
+
# <tt>raise Maveric::Response, 'Bad stuff'</tt> will generate a plain-text
|
247
|
+
# 500 response with the message as the body of the response.
|
567
248
|
#
|
568
|
-
#
|
569
|
-
#
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
249
|
+
# Additional or replacement responses should be set via Response#[]= and
|
250
|
+
# should be valid rack responses.
|
251
|
+
class Response < Exception
|
252
|
+
DEFAULT, @r = [500, 'text/plain'], {}
|
253
|
+
def self.[] e; @r[e]; end
|
254
|
+
def self.[]= e, v; @r[e]=v; end
|
255
|
+
def initialize err
|
256
|
+
resp = Response[err] || [ DEFAULT[0],
|
257
|
+
{ 'Content-Type' => DEFAULT[1], 'Content-Length' => err.length.to_s },
|
258
|
+
err]
|
259
|
+
@status, @headers, @body = resp
|
260
|
+
super @body
|
580
261
|
end
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
action ||= @env['REQUEST_METHOD'].downcase rescue nil
|
585
|
-
action ||= 'get'
|
586
|
-
@action = action.to_sym
|
587
|
-
|
588
|
-
before @action, self
|
589
|
-
@body = __send__ @action
|
590
|
-
after @action, self
|
591
|
-
end
|
592
|
-
|
593
|
-
attr_reader :status, :headers, :body
|
594
|
-
|
595
|
-
## A simple way to retrive pertinant data
|
596
|
-
def to_a # to_splat in 1.9
|
597
|
-
[@status, @headers, @body]
|
598
|
-
end
|
599
|
-
|
600
|
-
## For quick and raw response outputting!
|
601
|
-
def to_http raw=true
|
602
|
-
response = if not raw then 'Status:'
|
603
|
-
elsif @env and @env.key? 'HTTP_VERSION' then @env['HTTP_VERSION']
|
604
|
-
else 'HTTP/1.1' end
|
605
|
-
response += " #{self.status}" + ::Maveric::EOL # Status message? :/
|
606
|
-
response << self.headers.map{|k,v| "#{k}: #{v}" }*::Maveric::EOL
|
607
|
-
response << ::Maveric::EOL*2 + self.body
|
262
|
+
def each; @body.each{|line| yield line }; end
|
263
|
+
def to_a; [@status, @headers, self]; end # to_splat in 1.9
|
264
|
+
alias_method :finish, :to_a
|
608
265
|
end
|
266
|
+
end
|
609
267
|
|
610
|
-
private
|
611
268
|
|
612
|
-
##
|
613
|
-
# This is a simple method, really. The only confusing part is the expressions
|
614
|
-
# just above the return. Controller#render calls itself. That is all.
|
615
|
-
def render view, s=''
|
616
|
-
s = yield s if block_given?
|
617
|
-
raise ArgumentError, "The View #{view.inspect} not defined." unless \
|
618
|
-
respond_to? view
|
619
|
-
s = __send__ view, s
|
620
|
-
s = render :layout, s unless view.to_s[/^(_|layout$)/] \
|
621
|
-
or caller.any?{|l| l=~/`render'/ } # i don't like this but it works
|
622
|
-
return s
|
623
|
-
end
|
624
269
|
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
270
|
+
class Rack::Request
|
271
|
+
# Allows the request to provide the current maveric.
|
272
|
+
def maveric; @env['maveric']; end
|
273
|
+
# Provides the compliment to #fullpath.
|
274
|
+
def site_root
|
275
|
+
url = scheme + "://" + host
|
276
|
+
url << ":#{port}" if \
|
277
|
+
scheme == "https" && port != 443 || \
|
278
|
+
scheme == "http" && port != 80
|
279
|
+
url
|
630
280
|
end
|
631
|
-
|
632
|
-
##
|
633
|
-
# Folds the datasets of @cookie to @headers if they have been altered or
|
634
|
-
# are new.
|
635
|
-
def cookies_fold controller
|
636
|
-
@cookies.each do |k,v|
|
637
|
-
next unless v != @env[:cookies][k]
|
638
|
-
@headers['Set-Cookie'] << "#{k}=#{URI.decode(v)}"
|
639
|
-
end
|
640
|
-
end
|
641
|
-
|
642
|
-
after :cookies_fold
|
281
|
+
def url; site_root + fullpath; end
|
643
282
|
end
|
644
283
|
|
645
|
-
##
|
646
|
-
# Encapsulating route functionality into a single class.
|
647
|
-
#
|
648
|
-
# Route instances are typically stored in a Maveric instance and used
|
649
|
-
# for matching incoming paths to controllers. After a #match is made
|
650
|
-
# the resulting Struct instance has members consisting of the parameters
|
651
|
-
# of the seed path, as well as struct[:controller] pointing to the
|
652
|
-
# destination Controller and struct[:route] pointing to the Route
|
653
|
-
# instance itself.
|
654
|
-
class Maveric::Route
|
655
|
-
## A negative class of URI delimiting and reserved characters.
|
656
|
-
URI_CHAR = '[^/?:,&#]'
|
657
|
-
## Standard pattern for finding parameters in provided paths.
|
658
|
-
PARAM_MATCH= %r~:(#{URI_CHAR}+):?~
|
659
|
-
|
660
|
-
##
|
661
|
-
# Builds a regex and respective param list from path by parsing out
|
662
|
-
# fragments matching PARAM_MATCH and replacing them with either a
|
663
|
-
# corresponding symbol from the opts hash or the default regex.
|
664
|
-
#
|
665
|
-
# The final Route instance is utilized to build paths and match
|
666
|
-
# absolute paths for routing.
|
667
|
-
def initialize controller, path, opts={}
|
668
|
-
@controller = controller
|
669
|
-
@path = path.dup.freeze
|
670
|
-
params = []
|
671
|
-
regex = @path.gsub PARAM_MATCH do
|
672
|
-
params << param = $1.to_sym
|
673
|
-
"(#{opts[param] || URI_CHAR+'+'})"
|
674
|
-
end
|
675
|
-
raise ArgumentError, "Duplicated parameters in path."+
|
676
|
-
" <#{params.inspect}>" if params.size != params.uniq.size
|
677
|
-
@struct = Struct.new(:route, :controller, *params)
|
678
|
-
@regex = /\A#{regex}\z/.freeze
|
679
|
-
@opts = opts.reject{|k,v| !params.include? k }.freeze
|
680
|
-
end
|
681
284
|
|
682
|
-
attr_reader :path, :regex, :opts, :struct, :controller
|
683
285
|
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
# creation of incomplete paths, otherwise returns a path string.
|
694
|
-
def path_to args
|
695
|
-
return nil unless @opts.keys.sort == args.keys.sort
|
696
|
-
args.inject(@path){|u,(k,v)| u.sub(/#{k.inspect}:?/, v.to_s) }
|
697
|
-
end
|
698
|
-
end
|
286
|
+
Maveric::Response[401] = [401,
|
287
|
+
{'Content-Type'=>'text/plain','Content-Length'=>'13'},
|
288
|
+
['Unauthorized.']]
|
289
|
+
Maveric::Response[403] = [403,
|
290
|
+
{'Content-Type'=>'text/plain','Content-Length'=>'15'},
|
291
|
+
['Not authorized.']]
|
292
|
+
Maveric::Response[404] = [404,
|
293
|
+
{'Content-Type'=>'text/plain','Content-Length'=>'10'},
|
294
|
+
['Not Found.']]
|