scytrin-maveric 1.0.0 → 2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +27 -15
- data/lib/maveric.rb +215 -204
- metadata +8 -7
data/README
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
- Maveric
|
2
|
+
|
1
3
|
Maveric is a MVC overlay for Rack.
|
2
4
|
|
3
5
|
The dominant idea behind Maveric is as little magic as possible, being as
|
@@ -7,7 +9,7 @@ The use of a Maveric is simple.
|
|
7
9
|
|
8
10
|
class MyApp < Maveric
|
9
11
|
def get
|
10
|
-
|
12
|
+
render {'<p>Hello World!</p>'}
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
@@ -21,24 +23,34 @@ By default the method used to generate a response is the lower case form of the
|
|
21
23
|
http method. GET requests call #get, POST requests call #post, and so on. A 404
|
22
24
|
is returned if a the appropriate method is not defined.
|
23
25
|
|
26
|
+
-- Actions
|
24
27
|
|
25
|
-
To
|
26
|
-
internally by the application. Routes are defined in the manner of
|
27
|
-
uri-templates.
|
28
|
-
|
29
|
-
The first route matching the PATH_INFO of the request is used, and if there is a
|
30
|
-
value labeled "action" and a method by that name is defined, that method will be
|
31
|
-
used in preference to the default. If no method by that name is found, or no
|
32
|
-
matching route is found, default behaviour takes place.
|
28
|
+
To override this you may redefine #action= as needed.
|
33
29
|
|
34
30
|
class MyApp < Maveric
|
35
|
-
|
31
|
+
def action= method
|
32
|
+
if method == :get and @request.path_info == '/'
|
33
|
+
@action = :index
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
36
38
|
def index
|
37
|
-
|
39
|
+
render {'<p>Hello World!</p>'}
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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,245 +1,256 @@
|
|
1
|
+
# Author: scytrin@stadik.net
|
2
|
+
require 'uri'
|
1
3
|
require 'rack'
|
2
|
-
require 'addressable/uri'
|
3
|
-
|
4
|
-
class Maveric < Rack::Response
|
5
|
-
# Simple logger. Defaults all output to STDERR.
|
6
|
-
class << LOG = Object.new
|
7
|
-
def method_missing(m, *a, &b)
|
8
|
-
l = a.first.is_a?(String) ? ': '+a.shift : ''
|
9
|
-
STDERR.puts m.to_s.upcase+':'+caller[0].gsub(/(.*):(.*):.*/,'\1:\2')+l
|
10
|
-
STDERR.puts ' '+a.map{|e|e.inspect}*', '
|
11
|
-
end
|
12
|
-
end
|
13
|
-
LOG.debug 'Building Maveric'
|
14
4
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
Controllers.each do |a|
|
27
|
-
m = a.mount || a.name.downcase.gsub(/^|::/,'/')
|
28
|
-
v = map[m]
|
29
|
-
if v.is_a? Rack::Cascade
|
30
|
-
v << a
|
31
|
-
else
|
32
|
-
v = !v ? a : Rack::Cascade.new([map[m],a]))
|
33
|
-
map[m] = v
|
5
|
+
module Maveric
|
6
|
+
class Response < Exception
|
7
|
+
DEFAULT, @r = [500, 'text/plain'], {}
|
8
|
+
def self.[] e; @r[e]; end
|
9
|
+
def self.[]= e, v; @r[e]=v; end
|
10
|
+
def initialize err
|
11
|
+
@status, @headers, @body = if resp = Response[err] then resp else
|
12
|
+
[ DEFAULT[0],
|
13
|
+
{ 'Content-Type' => DEFAULT[1],
|
14
|
+
'Content-Length' => err.length.to_s },
|
15
|
+
err]
|
34
16
|
end
|
17
|
+
super @body
|
35
18
|
end
|
36
|
-
|
19
|
+
def each; @body.each{|line| yield line }; end
|
20
|
+
def to_a; [@status, @headers, self]; end # to_splat in 1.9
|
21
|
+
alias_method :finish, :to_a
|
37
22
|
end
|
38
23
|
|
39
|
-
|
24
|
+
module Views
|
25
|
+
attr_reader :maveric
|
26
|
+
TEMPLATES = {}
|
27
|
+
end
|
40
28
|
|
41
|
-
|
42
|
-
|
43
|
-
# Helpers is included into Maverics on instatiation as well as the sandbox
|
44
|
-
# used during rendering.
|
45
|
-
module Helpers; end
|
46
|
-
# A utility modules to hold references to model related actions and objects.
|
47
|
-
module Models; end
|
48
|
-
# Views is included into the sandbox utilized during rendering.
|
49
|
-
module Views; end
|
29
|
+
module Helpers
|
30
|
+
protected
|
50
31
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
32
|
+
# Powerhouse method to build a response. TODO: doc
|
33
|
+
def respond *args, &blk
|
34
|
+
headers = Hash === args.last ? args.pop : {}
|
35
|
+
seed = (Symbol === args.last && args.last) ? '' : args.pop
|
36
|
+
views, args = args.reverse.partition{|e| Symbol === e }
|
55
37
|
|
56
|
-
|
57
|
-
|
58
|
-
|
38
|
+
bad = views.map{|e|e.to_s} - self.class::Views.instance_methods
|
39
|
+
raise ArgumentError, 'Invalid views: '+bad.inspect unless bad.empty?
|
40
|
+
warn 'Invalid arguments: '+args.inspect unless args.empty?
|
59
41
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
klass.const_set(c, Module.new).module_eval{ include s.const_get(c) }
|
74
|
-
end
|
75
|
-
%w'@header @preparations'.each do |v|
|
76
|
-
klass.instance_variable_set v, s.instance_variable_get(v).dup
|
42
|
+
response = Rack::Response.new
|
43
|
+
response.headers.update self.class.headers
|
44
|
+
response.headers.update headers.
|
45
|
+
reject{|(k,v)| !k.is_a? String or !v.is_a? String }
|
46
|
+
response.extend self.class::Views
|
47
|
+
response.instance_variable_set '@maveric', self
|
48
|
+
|
49
|
+
catch :response do
|
50
|
+
yield response if block_given?
|
51
|
+
body = views.inject(seed){|b,v| response.__send__(v,b) }.to_s
|
52
|
+
response.length = Rack::Utils.bytesize body
|
53
|
+
response.body = body.to_a
|
54
|
+
return response.finish
|
77
55
|
end
|
78
|
-
klass.instance_variable_set :@mount, klass.to_s.downcase.gsub(/^|:+/, '/')
|
79
|
-
super
|
80
56
|
end
|
81
57
|
|
82
|
-
|
83
|
-
# the method calls of .new, #prepare, and #finish. The calls are
|
84
|
-
# encapsulated in a catch block with the key of :response.
|
85
|
-
def call env
|
86
|
-
LOG.debug 'MVC:Args', *env.values_at('HTTP_HOST','REQUEST_URI','SCRIPT_NAME','PATH_INFO')
|
87
|
-
# either throw(:response, [status,header,body]) or don't, pls
|
88
|
-
response = catch(:response){ new(env).prepare.finish }
|
89
|
-
LOG.debug 'MVC:Response', response
|
90
|
-
response
|
91
|
-
end
|
58
|
+
private
|
92
59
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
# A preparation is a hash with the keys +:name+ and +:run+ set at a
|
104
|
-
# minimum. +:name+ is set to the label provided as the first argument.
|
105
|
-
# +:run+ is set to the block provided, or nil.
|
106
|
-
#
|
107
|
-
# When preparations are run, if +:run: is set, the block is evaluated in
|
108
|
-
# the instances scope, otherwise the method named by +:name+ is called if
|
109
|
-
# it exists.
|
110
|
-
def preparation name, opts={}, &blk
|
111
|
-
unless name.is_a? String
|
112
|
-
raise ArgumentError, "name #{name.inspect} is not a string"
|
113
|
-
end
|
114
|
-
prep = @preparations.find{|e| e[:name] == name }
|
115
|
-
if opts or blk
|
116
|
-
if prep
|
117
|
-
raise "Preparation with the name #{name.inspect} already exists"+
|
118
|
-
" for #{self.inspect}."
|
119
|
-
else
|
120
|
-
@preparations << prep = opts.merge(:name => name, :run => blk)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
prep
|
60
|
+
# Provides a simple way of generating a redirection response, relative to
|
61
|
+
# the current url.
|
62
|
+
def redirect uri
|
63
|
+
uri = URI(uri).normalize
|
64
|
+
uri = URI(@request.url).merge uri
|
65
|
+
[ 303, { 'Location'=>uri.to_s,
|
66
|
+
'Content-Type'=>'text/plain',
|
67
|
+
'Content-Length'=>'0'
|
68
|
+
}, [] ]
|
124
69
|
end
|
125
70
|
end
|
126
71
|
|
127
|
-
attr_reader :request, :action
|
128
72
|
|
129
|
-
|
73
|
+
|
74
|
+
# The current instance is stored in the passed environment under the key
|
75
|
+
# 'maveric'. A Rack::Request of the current request is provided at @request.
|
76
|
+
#
|
77
|
+
# If no action is provided, @action is set as :index.
|
78
|
+
def initialize env, action=nil, route=nil
|
79
|
+
env['maveric'] = self
|
130
80
|
@request = Rack::Request.new env
|
131
|
-
@action =
|
81
|
+
@route, @action = route, action || :index
|
132
82
|
extend self.class::Helpers
|
133
|
-
|
134
|
-
|
83
|
+
self.init_hook if self.respond_to? :init_hook
|
84
|
+
p [ @request.script_name, @route, @action ]
|
135
85
|
end
|
86
|
+
attr_reader :request, :route, :action
|
87
|
+
|
88
|
+
|
89
|
+
# Will return a 404 type Response if the set action is invalid. Will
|
90
|
+
# generate a new response if a previous one is not cached or +renew+ is true.
|
91
|
+
#
|
92
|
+
# If the response responds to :finish, it will return the result, otherwise
|
93
|
+
# the response itself will be returned.
|
94
|
+
def response renew=false
|
95
|
+
raise Response, 404 unless @action and respond_to? @action
|
96
|
+
@response = __send__ @action if not @response or renew
|
97
|
+
rescue Response
|
98
|
+
warn('Response: '+$!.inspect)
|
99
|
+
@response = $!.to_a
|
100
|
+
rescue
|
101
|
+
warn('Rescue: '+$!.inspect)
|
102
|
+
body = $!.message + "\n\t" + $@[0..10]*"\n\t"
|
103
|
+
@request.env['rack.errors'].puts body
|
104
|
+
body << "\n\t" << $@[11..-1]*"\n\t"
|
105
|
+
@request.env.sort.each{|(k,v)| body << "\n#{k}\n\t#{v.inspect}" }
|
106
|
+
@response = [ 500, {
|
107
|
+
'Content-Type' => 'text/plain',
|
108
|
+
'Content-Length' => body.length.to_s
|
109
|
+
}, body.to_a ]
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# Methods provided to construct a maveric
|
114
|
+
module Class
|
115
|
+
# Default headers for Maveric instantiation.
|
116
|
+
attr_reader :routes, :headers
|
117
|
+
attr_writer :mount
|
118
|
+
|
119
|
+
def add_templates directory='templates/*'
|
120
|
+
Dir.glob directory do |file| p file
|
121
|
+
next unless File.readable? file and tmpl = File.read(file)
|
122
|
+
next unless extn = File.extname(file) and not extn.empty?
|
123
|
+
next unless name = File.basename(file, extn) and not name.empty?
|
124
|
+
view = case extn
|
125
|
+
when '.haml'
|
126
|
+
require 'haml' rescue next
|
127
|
+
puts "Defining HAML template view " + name.inspect
|
128
|
+
haml = Haml::Engine.new tmpl
|
129
|
+
proc{|content| haml.render self, :content => content }
|
130
|
+
when '.erb'
|
131
|
+
require 'erb' rescue next
|
132
|
+
puts "Defining ERB template view " + name.inspect
|
133
|
+
erb = ERB.new tmpl
|
134
|
+
erb.filename = file
|
135
|
+
proc{|content| erb.result(binding) }
|
136
|
+
else
|
137
|
+
puts "Defining sprintf template view " + name.inspect
|
138
|
+
proc{|content| sprintf self, content }
|
139
|
+
end
|
136
140
|
|
137
|
-
|
138
|
-
|
139
|
-
next if prep.key? :except and !prep[:except].include?(@action)
|
140
|
-
next if prep.key? :accept and prep[:accept].include?(@action)
|
141
|
-
if prep[:run]
|
142
|
-
instance_eval &prep[:run]
|
143
|
-
elsif respond_to? prep[:name]
|
144
|
-
__send__ prep[:name]
|
145
|
-
else
|
146
|
-
warn "Bad preparation for #{prep.inspect} in #{self}."
|
141
|
+
self::Views::TEMPLATES[name] = view
|
142
|
+
self::Views.__send__ :define_method, name, &view
|
147
143
|
end
|
148
144
|
end
|
149
|
-
LOG.debug 'MVC:Prep', @action, self
|
150
|
-
return self
|
151
|
-
end
|
152
145
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
super()
|
158
|
-
end
|
146
|
+
def call env
|
147
|
+
r, a = @routes.find{|(r,d)| r === env['PATH_INFO'] }
|
148
|
+
new(env, a, r).response
|
149
|
+
end
|
159
150
|
|
160
|
-
|
161
|
-
|
162
|
-
# for starting content: use first arg unless a symbol, else i_eval given block
|
163
|
-
# iterate through rest of args, calling matching method with arg of content
|
164
|
-
# set @response.body to result... change?
|
165
|
-
def render *args, &blk
|
166
|
-
o = Object.new
|
167
|
-
o.extend self.class::Views
|
168
|
-
o.extend self.class::Helpers
|
169
|
-
instance_variables.each do |i|
|
170
|
-
o.instance_variable_set i, instance_variable_get(i)
|
151
|
+
def route d, *a
|
152
|
+
a.flatten.each{|r| @routes << [r, d] }
|
171
153
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
next o.__send__(view, memo) if o.respond_to? view
|
176
|
-
warn "No view <#{view}> in #{self.inspect}"
|
177
|
-
memo
|
154
|
+
|
155
|
+
def mount
|
156
|
+
@mount || self.name.downcase.gsub(/^|::/, '/')
|
178
157
|
end
|
179
|
-
@body = result || seed
|
180
|
-
end
|
181
158
|
|
182
|
-
|
183
|
-
|
184
|
-
DEF_MATCH = '.*'
|
185
|
-
def initialize
|
186
|
-
#uri.extract_mapping(String pattern, URIProcessor proc)
|
187
|
-
@match = Hash.new(DEF_MATCH) #String of regex to match pattern
|
188
|
-
@restore = {} #Transform to usable
|
189
|
-
#URI.expand_template(String template, Hash mapping, URIProcessor proc)
|
190
|
-
@validate = {} #Boolean if acceptable
|
191
|
-
@transform = {} #Transform to uri encoding
|
192
|
-
yield self if block_given?
|
159
|
+
def inspect
|
160
|
+
"#<Maveric:#{self.name} '#{mount}'>"
|
193
161
|
end
|
194
|
-
|
195
|
-
|
196
|
-
@
|
162
|
+
|
163
|
+
def self.extend_object obj
|
164
|
+
obj.instance_variable_set '@routes', []
|
165
|
+
obj.instance_variable_set '@headers', Rack::Utils::HeaderHash.new
|
166
|
+
super
|
197
167
|
end
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
168
|
+
end
|
169
|
+
|
170
|
+
@maverics = [] unless defined? @maverics
|
171
|
+
@last_hash = nil
|
172
|
+
@map = nil
|
173
|
+
class << self
|
174
|
+
attr_reader :maverics
|
175
|
+
|
176
|
+
# Generates a Rack::URLMap compatible hash composed of all maverics and
|
177
|
+
# their mount points.
|
178
|
+
def map
|
179
|
+
return @map if @map and @last_hash == @maverics.hash
|
180
|
+
@last_hash = @maverics.hash
|
181
|
+
@map = @maverics.inject({}) do |h,m|
|
182
|
+
h.store m.mount, m
|
183
|
+
h
|
184
|
+
end
|
202
185
|
end
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
186
|
+
|
187
|
+
# Generates a Rack::URLMap from the result of Maveric.map
|
188
|
+
def urlmap
|
189
|
+
return @urlmap if @urlmap and @last_hash == @maverics.hash
|
190
|
+
@urlmap = Rack::URLMap.new self.map
|
207
191
|
end
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
192
|
+
|
193
|
+
# Maps to a call to the result of Maveric.urlmap
|
194
|
+
def call env
|
195
|
+
self.urlmap.call env
|
212
196
|
end
|
213
|
-
end
|
214
197
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
198
|
+
def new app
|
199
|
+
Rack::Cascade.new [app, self]
|
200
|
+
end
|
201
|
+
|
202
|
+
# When a maveric is subclassed, the superclass' headers inherited. Views
|
203
|
+
# and Helpers modules are also included into the subclass' corresponding
|
204
|
+
# modules.
|
205
|
+
#
|
206
|
+
# By default, the first class to inherit from Maveric is assigned the mount
|
207
|
+
# point of '/'.
|
208
|
+
#
|
209
|
+
# A maveric's default mount point is by default derived from it's full
|
210
|
+
# name. For example: Object would be '/object', Module would be '/module',
|
211
|
+
# and Rack::Auth::OpenID would be '/rack/auth/openid'.
|
212
|
+
def included obj
|
213
|
+
super
|
214
|
+
obj.extend Maveric::Class
|
215
|
+
%w'Helpers Views'.each do |name|
|
216
|
+
next if obj.const_defined?(name)
|
217
|
+
obj.const_set(name, Module.new).
|
218
|
+
instance_eval{ include Maveric.const_get(name) }
|
219
|
+
end
|
220
|
+
obj.mount = '/' if @maverics.empty?
|
221
|
+
@maverics << obj
|
221
222
|
end
|
222
|
-
raise unless maveric.is_a? ::Maveric
|
223
|
-
self[maveric.class].find do |route, processor|
|
224
|
-
uri = Addressable::URI.parse(maveric.request.path_info)
|
225
|
-
map = uri.extract_mapping(route, processor)
|
226
|
-
break map if map and map.all?{|k,v| !v.empty? }
|
227
|
-
end || {}
|
228
|
-
end
|
229
223
|
|
230
|
-
|
231
|
-
|
232
|
-
|
224
|
+
def extend_object obj
|
225
|
+
warn 'Maveric should only be included.'
|
226
|
+
return obj
|
227
|
+
end
|
233
228
|
end
|
229
|
+
end
|
234
230
|
|
235
|
-
|
236
|
-
#
|
237
|
-
|
238
|
-
#
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
231
|
+
class Rack::Request
|
232
|
+
# Allows the request to provide the current maveric.
|
233
|
+
def maveric; @env['maveric']; end
|
234
|
+
# Allows the request to provide the current session.
|
235
|
+
def session; @env['rack.session']; end
|
236
|
+
# Same as the standard Rack::Request method.
|
237
|
+
def url; site_root + fullpath; end
|
238
|
+
# Provides the compliment to #fullpath.
|
239
|
+
def site_root
|
240
|
+
url = scheme + "://" + host
|
241
|
+
url << ":#{port}" if \
|
242
|
+
scheme == "https" && port != 443 || \
|
243
|
+
scheme == "http" && port != 80
|
244
|
+
url
|
244
245
|
end
|
245
246
|
end
|
247
|
+
|
248
|
+
Maveric::Response[401] = [401,
|
249
|
+
{'Content-Type'=>'text/plain','Content-Length'=>'13'},
|
250
|
+
['Unauthorized.']]
|
251
|
+
Maveric::Response[403] = [403,
|
252
|
+
{'Content-Type'=>'text/plain','Content-Length'=>'15'},
|
253
|
+
['Not authorized.']]
|
254
|
+
Maveric::Response[404] = [404,
|
255
|
+
{'Content-Type'=>'text/plain','Content-Length'=>'10'},
|
256
|
+
['Not Found.']]
|
metadata
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scytrin-maveric
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: "2"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- scytrin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-05-24 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -22,7 +23,7 @@ dependencies:
|
|
22
23
|
version: "0"
|
23
24
|
version:
|
24
25
|
description:
|
25
|
-
email:
|
26
|
+
email: scytrin@gmail.com
|
26
27
|
executables: []
|
27
28
|
|
28
29
|
extensions: []
|
@@ -32,12 +33,12 @@ extra_rdoc_files:
|
|
32
33
|
files:
|
33
34
|
- README
|
34
35
|
- lib/maveric.rb
|
35
|
-
has_rdoc:
|
36
|
+
has_rdoc: true
|
36
37
|
homepage: http://maveric.rubyforge.org/
|
37
38
|
post_install_message:
|
38
39
|
rdoc_options:
|
39
40
|
- --main
|
40
|
-
- README
|
41
|
+
- README
|
41
42
|
require_paths:
|
42
43
|
- lib
|
43
44
|
required_ruby_version: !ruby/object:Gem::Requirement
|