scytrin-maveric 1.0.0 → 2
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 +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
|