maveric 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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.']]
|