orange-core 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +145 -0
- data/lib/orange-core.rb +8 -0
- data/lib/orange-core/application.rb +132 -0
- data/lib/orange-core/assets/css/exceptions.css +50 -0
- data/lib/orange-core/assets/js/exceptions.js +44 -0
- data/lib/orange-core/carton.rb +178 -0
- data/lib/orange-core/core.rb +266 -0
- data/lib/orange-core/magick.rb +270 -0
- data/lib/orange-core/middleware/base.rb +96 -0
- data/lib/orange-core/middleware/database.rb +45 -0
- data/lib/orange-core/middleware/four_oh_four.rb +45 -0
- data/lib/orange-core/middleware/globals.rb +17 -0
- data/lib/orange-core/middleware/loader.rb +13 -0
- data/lib/orange-core/middleware/rerouter.rb +53 -0
- data/lib/orange-core/middleware/restful_router.rb +99 -0
- data/lib/orange-core/middleware/route_context.rb +39 -0
- data/lib/orange-core/middleware/route_site.rb +51 -0
- data/lib/orange-core/middleware/show_exceptions.rb +80 -0
- data/lib/orange-core/middleware/static.rb +67 -0
- data/lib/orange-core/middleware/static_file.rb +32 -0
- data/lib/orange-core/middleware/template.rb +60 -0
- data/lib/orange-core/packet.rb +232 -0
- data/lib/orange-core/plugin.rb +172 -0
- data/lib/orange-core/resource.rb +96 -0
- data/lib/orange-core/resources/mapper.rb +36 -0
- data/lib/orange-core/resources/model_resource.rb +228 -0
- data/lib/orange-core/resources/not_found.rb +10 -0
- data/lib/orange-core/resources/page_parts.rb +68 -0
- data/lib/orange-core/resources/parser.rb +113 -0
- data/lib/orange-core/resources/routable_resource.rb +16 -0
- data/lib/orange-core/resources/scaffold.rb +106 -0
- data/lib/orange-core/stack.rb +226 -0
- data/lib/orange-core/templates/exceptions.haml +111 -0
- data/lib/orange-core/views/default_resource/create.haml +4 -0
- data/lib/orange-core/views/default_resource/edit.haml +9 -0
- data/lib/orange-core/views/default_resource/list.haml +10 -0
- data/lib/orange-core/views/default_resource/show.haml +4 -0
- data/lib/orange-core/views/default_resource/table_row.haml +7 -0
- data/lib/orange-core/views/not_found/404.haml +2 -0
- data/spec/orange-core/application_spec.rb +183 -0
- data/spec/orange-core/carton_spec.rb +136 -0
- data/spec/orange-core/core_spec.rb +248 -0
- data/spec/orange-core/magick_spec.rb +96 -0
- data/spec/orange-core/middleware/base_spec.rb +38 -0
- data/spec/orange-core/middleware/globals_spec.rb +3 -0
- data/spec/orange-core/middleware/rerouter_spec.rb +3 -0
- data/spec/orange-core/middleware/restful_router_spec.rb +3 -0
- data/spec/orange-core/middleware/route_context_spec.rb +3 -0
- data/spec/orange-core/middleware/route_site_spec.rb +3 -0
- data/spec/orange-core/middleware/show_exceptions_spec.rb +3 -0
- data/spec/orange-core/middleware/static_file_spec.rb +3 -0
- data/spec/orange-core/middleware/static_spec.rb +3 -0
- data/spec/orange-core/mock/mock_app.rb +16 -0
- data/spec/orange-core/mock/mock_carton.rb +43 -0
- data/spec/orange-core/mock/mock_core.rb +2 -0
- data/spec/orange-core/mock/mock_middleware.rb +25 -0
- data/spec/orange-core/mock/mock_mixins.rb +19 -0
- data/spec/orange-core/mock/mock_model_resource.rb +47 -0
- data/spec/orange-core/mock/mock_pulp.rb +24 -0
- data/spec/orange-core/mock/mock_resource.rb +26 -0
- data/spec/orange-core/mock/mock_router.rb +10 -0
- data/spec/orange-core/orange_spec.rb +19 -0
- data/spec/orange-core/packet_spec.rb +203 -0
- data/spec/orange-core/resource_spec.rb +96 -0
- data/spec/orange-core/resources/mapper_spec.rb +5 -0
- data/spec/orange-core/resources/model_resource_spec.rb +246 -0
- data/spec/orange-core/resources/parser_spec.rb +5 -0
- data/spec/orange-core/resources/routable_resource_spec.rb +5 -0
- data/spec/orange-core/spec_helper.rb +53 -0
- data/spec/orange-core/stack_spec.rb +232 -0
- data/spec/stats.rb +182 -0
- metadata +227 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'rack/request'
|
3
|
+
require 'rack/utils'
|
4
|
+
require 'orange-core/middleware/base'
|
5
|
+
|
6
|
+
module Orange::Middleware
|
7
|
+
# Rack::ShowExceptions catches all exceptions raised from the app it
|
8
|
+
# wraps. It shows a useful backtrace with the sourcefile and
|
9
|
+
# clickable context, the whole Rack environment and the request
|
10
|
+
# data.
|
11
|
+
#
|
12
|
+
# Be careful when you use this on public-facing sites as it could
|
13
|
+
# reveal information helpful to attackers.
|
14
|
+
#
|
15
|
+
# Orange::Middleware::ShowExceptions is a slightly modified
|
16
|
+
# version of Rack::ShowExceptions
|
17
|
+
class ShowExceptions < Base
|
18
|
+
CONTEXT = 7
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
@app.call(env)
|
22
|
+
rescue Exception => e
|
23
|
+
backtrace = pretty(env, e)
|
24
|
+
|
25
|
+
[500,
|
26
|
+
{"Content-Type" => "text/html",
|
27
|
+
"Content-Length" => backtrace.join.size.to_s},
|
28
|
+
backtrace]
|
29
|
+
end
|
30
|
+
|
31
|
+
def packet_call(packet)
|
32
|
+
backtrace = pretty()
|
33
|
+
end
|
34
|
+
|
35
|
+
def pretty(env, exception)
|
36
|
+
req = Rack::Request.new(env)
|
37
|
+
path = (req.script_name + req.path_info).squeeze("/")
|
38
|
+
|
39
|
+
frames = exception.backtrace.map { |line|
|
40
|
+
frame = OpenStruct.new
|
41
|
+
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
42
|
+
frame.filename = $1
|
43
|
+
frame.lineno = $2.to_i
|
44
|
+
frame.function = $4
|
45
|
+
|
46
|
+
begin
|
47
|
+
lineno = frame.lineno-1
|
48
|
+
lines = ::File.readlines(frame.filename)
|
49
|
+
frame.pre_context_lineno = [lineno-CONTEXT, 0].max
|
50
|
+
frame.pre_context = lines[frame.pre_context_lineno...lineno]
|
51
|
+
frame.context_line = lines[lineno].chomp
|
52
|
+
frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
|
53
|
+
frame.post_context = lines[lineno+1..frame.post_context_lineno]
|
54
|
+
rescue
|
55
|
+
end
|
56
|
+
|
57
|
+
frame
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
}.compact
|
62
|
+
|
63
|
+
env["rack.errors"].puts "#{exception.class}: #{exception.message}"
|
64
|
+
env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
|
65
|
+
env["rack.errors"].flush
|
66
|
+
orange_env = env["orange.env"]
|
67
|
+
parse = orange[:parser].haml("exceptions.haml", binding, :template => true)
|
68
|
+
[parse]
|
69
|
+
end
|
70
|
+
|
71
|
+
def h(obj) # :nodoc:
|
72
|
+
case obj
|
73
|
+
when String
|
74
|
+
Rack::Utils.escape_html(obj)
|
75
|
+
else
|
76
|
+
Rack::Utils.escape_html(obj.inspect)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'orange-core/middleware/base'
|
2
|
+
require 'orange-core/middleware/static_file'
|
3
|
+
|
4
|
+
module Orange::Middleware
|
5
|
+
# The Orange::Middleware::Static middleware intercepts requests for static files
|
6
|
+
# (javascript files, images, stylesheets, etc) based on the url prefixes
|
7
|
+
# passed in the options, and serves them using a Rack::File object.
|
8
|
+
#
|
9
|
+
# This differs from Rack::Static in that it can serve from multiple roots
|
10
|
+
# to accommodate both Orange static files and site specific ones.
|
11
|
+
# urls and root act the same as they do for Rack::Static. Only :libs option acts
|
12
|
+
# specially.
|
13
|
+
#
|
14
|
+
# Each lib is responsible for responding to static_url and static_dir
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
# use Orange::Middleware::Static :libs => [Orange::Core, AwesomeMod]
|
18
|
+
# use Orange::Middleware::Static :libs => [Orange::Core, AwesomeMod],
|
19
|
+
# :urls => ["/favicon.ico"]
|
20
|
+
#
|
21
|
+
# => Example 1 would load a file root for Orange::Core and Awesome Mod
|
22
|
+
# Orange::Core static_url is _orange_, and dir is the
|
23
|
+
# orange lib/assets folder
|
24
|
+
# => Example 2 would also redirect favicon.ico to the assets dir
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# Note that as a general rule, Orange will assume everything static to be in an
|
28
|
+
# /assets/ subfolder, therefore, '/assets' will be prepended to the url given
|
29
|
+
# by static_url
|
30
|
+
# Also note, that since this is the case - setting up a match for general '/assets'
|
31
|
+
# could yield unpredictable results
|
32
|
+
#
|
33
|
+
# a static_url corresponds to the :module => in the add_css and add_js helpers
|
34
|
+
class Static < Base
|
35
|
+
|
36
|
+
def initialize(app, core, options={})
|
37
|
+
@lib_urls = {'_orange_' => File.join(core.core_dir, 'assets') }
|
38
|
+
Orange.plugins.each{|p| @lib_urls[p.assets_name] = p.assets if p.has_assets?}
|
39
|
+
|
40
|
+
@app = app
|
41
|
+
@core = core
|
42
|
+
@urls = options[:urls] || ["/favicon.ico", "/assets/public", "/assets/uploaded"]
|
43
|
+
@root = options[:root] || File.join(orange.app_dir, 'assets')
|
44
|
+
@file_server = Orange::Middleware::StaticFile.new(@root)
|
45
|
+
end
|
46
|
+
|
47
|
+
def packet_call(packet)
|
48
|
+
path = packet.env["PATH_INFO"]
|
49
|
+
can_serve_lib = @lib_urls.select { |url, server|
|
50
|
+
path.index(File.join('', 'assets', url)) == 0
|
51
|
+
}.first
|
52
|
+
can_serve = @urls.any?{|url| path.index(url) == 0 }
|
53
|
+
if can_serve_lib
|
54
|
+
lib_url = can_serve_lib.first
|
55
|
+
packet['file.root'] = can_serve_lib.last
|
56
|
+
packet['route.path'] = path.split(lib_url, 2).last
|
57
|
+
@file_server.call(packet.env)
|
58
|
+
elsif can_serve
|
59
|
+
packet['route.path'] = path.gsub(/^\/assets/, '')
|
60
|
+
@file_server.call(packet.env)
|
61
|
+
else
|
62
|
+
pass packet
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/utils'
|
3
|
+
|
4
|
+
module Orange::Middleware
|
5
|
+
# Rack::File serves files below the +root+ given, according to the
|
6
|
+
# path info of the Rack request.
|
7
|
+
# Orange::Middleware::StaticFile acts the same as Rack::File, but acts on
|
8
|
+
# the orange specific path if available. (So site url would be ignored, etc.)
|
9
|
+
#
|
10
|
+
# Handlers can detect if bodies are a Rack::File, and use mechanisms
|
11
|
+
# like sendfile on the +path+.
|
12
|
+
|
13
|
+
class StaticFile < Rack::File
|
14
|
+
def _call(env)
|
15
|
+
@path_info = Rack::Utils.unescape(env['orange.env']["route.path"]) || Rack::Utils.unescape(env["PATH_INFO"])
|
16
|
+
@root = env['orange.env']['file.root'] || @root
|
17
|
+
return forbidden if @path_info.include? ".."
|
18
|
+
|
19
|
+
@path = F.join(@root, @path_info)
|
20
|
+
|
21
|
+
begin
|
22
|
+
if F.file?(@path) && F.readable?(@path)
|
23
|
+
serving
|
24
|
+
else
|
25
|
+
raise Errno::EPERM
|
26
|
+
end
|
27
|
+
rescue SystemCallError
|
28
|
+
not_found
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'orange-core/core'
|
2
|
+
require 'orange-core/middleware/base'
|
3
|
+
|
4
|
+
module Orange::Middleware
|
5
|
+
|
6
|
+
class Template < Base
|
7
|
+
def init(*args)
|
8
|
+
@core.add_pulp(Orange::Pulp::Template)
|
9
|
+
@core.mixin(Orange::Mixins::Template)
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def packet_call(packet)
|
14
|
+
packet['template.file'] = orange.template_for packet
|
15
|
+
status, headers, content = pass packet
|
16
|
+
if needs_wrapped?(packet)
|
17
|
+
content = wrap(packet, content)
|
18
|
+
packet[:content] = content.first
|
19
|
+
orange.fire(:wrapped, packet)
|
20
|
+
end
|
21
|
+
orange.fire(:after_wrap, packet)
|
22
|
+
packet.finish
|
23
|
+
end
|
24
|
+
|
25
|
+
def needs_wrapped?(packet)
|
26
|
+
return false if packet.request.xhr? && !packet['template.enable'] # don't wrap xhr unless specifically asked to
|
27
|
+
packet['template.file'] && !packet['template.disable']
|
28
|
+
end
|
29
|
+
|
30
|
+
def wrap(packet, content = false)
|
31
|
+
content = packet.content unless content
|
32
|
+
content = content.join
|
33
|
+
content = orange[:parser].haml(packet['template.file'], packet, :wrapped_content => content, :template => true) do
|
34
|
+
content
|
35
|
+
end
|
36
|
+
[content]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Orange::Pulp::Template
|
42
|
+
def wrap
|
43
|
+
packet[:content] = orange[:parser].haml(packet['template.file'], packet, :wrapped_content => packet[:content], :template => true) do
|
44
|
+
content
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Orange::Mixins::Template
|
50
|
+
def template_for(packet)
|
51
|
+
template_chooser.call(packet)
|
52
|
+
end
|
53
|
+
def template_chooser(&block)
|
54
|
+
if block_given?
|
55
|
+
@template_chooser = Proc.new
|
56
|
+
else
|
57
|
+
@template_chooser ||= Proc.new {|packet| false}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module Orange
|
2
|
+
# Orange::Packet is a wrapper for Rack's basic env variable.
|
3
|
+
# It acts somewhat like Rack::Request, except with more functionality.
|
4
|
+
# For each request a unique Packet is generated, and this packet is
|
5
|
+
# used to by middleware to create the response.
|
6
|
+
#
|
7
|
+
# All orange enhanced middleware has a packet_call method that
|
8
|
+
# automatically turns the generic rack call(env) into a call
|
9
|
+
# that has a packet instead, so all functions for the packet should
|
10
|
+
# be available for the request.
|
11
|
+
#
|
12
|
+
# Pulps are modules that are mixed into the Packet, allowing
|
13
|
+
# additional functionality to be used by the packet.
|
14
|
+
#
|
15
|
+
# By default, haml files are parsed in the context of their
|
16
|
+
# packet. This means all of instance variables and functions should
|
17
|
+
# be available to the haml parser.
|
18
|
+
class Packet
|
19
|
+
# By default, header will be content-type
|
20
|
+
DEFAULT_HEADERS = {"Content-Type" => 'text/html'} unless defined?(DEFAULT_HEADERS)
|
21
|
+
|
22
|
+
# We override the instantiation to only create one packet per env
|
23
|
+
# @param [Orange::Core] orange a pointer to the orange core
|
24
|
+
# @param [Hash] env a standard Rack hash
|
25
|
+
def self.new(orange, env)
|
26
|
+
return env['orange.packet'] if env['orange.packet']
|
27
|
+
super(orange, env)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Allows tying in to the method_missing method without redefining it
|
31
|
+
# elsewhere. This lets dynamic methods be defined on the packet.
|
32
|
+
# Regexes are defined to match method names. #method_missing will
|
33
|
+
# loop through and try to find a match, executing the proc defined in
|
34
|
+
# the block.
|
35
|
+
# @param [Regexp] regex the regex to match
|
36
|
+
# @yield [Orange::Packet, MatchData, args] the block to execute if matched
|
37
|
+
# (passed instance, match data and args)
|
38
|
+
def self.meta_methods(regex, &block)
|
39
|
+
return unless block_given?
|
40
|
+
proc = block
|
41
|
+
@@matchers ||= {}
|
42
|
+
@@matchers[regex] = proc
|
43
|
+
end
|
44
|
+
|
45
|
+
# Allows access to the matchers added via the #meta_methods method
|
46
|
+
# @return [Hash] the matchers hash
|
47
|
+
def matchers
|
48
|
+
@@matchers || {}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Initialize is only called if a packet hasn't already been called for
|
52
|
+
# this env. Sets up the basic env, creating a pointer back to self
|
53
|
+
# and a Rack::Request object.
|
54
|
+
# @param [Orange::Core] orange a pointer to the orange core
|
55
|
+
# @param [Hash] env a standard Rack hash
|
56
|
+
def initialize(orange, env)
|
57
|
+
@orange = orange
|
58
|
+
@env = env
|
59
|
+
@env['orange.packet'] = self
|
60
|
+
@env['orange.env'] = {} unless @env['orange.env']
|
61
|
+
@env['orange.env'][:request] = Rack::Request.new(env)
|
62
|
+
@env['orange.env'][:headers] = {}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Gives access to the orange.env key in the env, optionally
|
66
|
+
# including a default if key isn't involved.
|
67
|
+
# @param [Symbol, String] key the key to be found
|
68
|
+
# @param [optional, Object] default the return value if key doesn't exist (default is false)
|
69
|
+
# @return [Object] any value stored in the env
|
70
|
+
def [](key, default = false)
|
71
|
+
@env['orange.env'].has_key?(key) ? @env['orange.env'][key] : default
|
72
|
+
end
|
73
|
+
|
74
|
+
# Lets user set a value in the orange.env
|
75
|
+
# @param [Symbol, String] key the key to be set
|
76
|
+
# @param [Object] val the value to be set
|
77
|
+
def []=(key, val)
|
78
|
+
@env['orange.env'][key] = val
|
79
|
+
end
|
80
|
+
|
81
|
+
# Access to the main env (orange env is stored within the main env)
|
82
|
+
# @return [Hash] the request's env hash
|
83
|
+
def env
|
84
|
+
@env
|
85
|
+
end
|
86
|
+
|
87
|
+
# Access to the rack session
|
88
|
+
# @return [Hash] the session information made available by Rack
|
89
|
+
def session
|
90
|
+
env['rack.session']["flash"] ||= {}
|
91
|
+
env['rack.session']
|
92
|
+
end
|
93
|
+
|
94
|
+
# Access to the rack session flash
|
95
|
+
# @return [String] the string stored in the flash
|
96
|
+
def flash(key = nil, val = nil)
|
97
|
+
env['rack.session']["flash"] ||= {}
|
98
|
+
if key.nil? && val.nil?
|
99
|
+
env['rack.session']["flash"]
|
100
|
+
elsif val.nil?
|
101
|
+
env['rack.session']["flash"].delete(key)
|
102
|
+
else
|
103
|
+
env['rack.session']["flash"][key] = val
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Generate headers for finalization
|
108
|
+
# @return [Hash] the header information stored in the orange.env, combined with the defaults
|
109
|
+
# set as DEFAULT_HEADERS
|
110
|
+
def headers
|
111
|
+
packet[:headers, {}].with_defaults(DEFAULT_HEADERS)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set a header
|
115
|
+
# @param [String] key the key to be set
|
116
|
+
# @param [Object] val the value to be set
|
117
|
+
def header(key, val)
|
118
|
+
@env['orange.env'][:headers][key] = val
|
119
|
+
end
|
120
|
+
|
121
|
+
# Set a header (same as #header)
|
122
|
+
# @param [String] key the key to be set
|
123
|
+
# @param [Object] val the value to be set
|
124
|
+
def add_header(key, val)
|
125
|
+
header key, val
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the content ready to be used by Rack (wrapped in an array)
|
129
|
+
# @return [Array] array of strings to be rendered
|
130
|
+
def content
|
131
|
+
# Stringify content if it isn't a string for some weird reason.
|
132
|
+
packet[:content] = packet[:content].to_s unless packet[:content].is_a? String
|
133
|
+
return [packet[:content]] if packet[:content]
|
134
|
+
return ['']
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns the request object generated by Rack::Request(packet.env)
|
138
|
+
# @return [Rack::Request] the request object
|
139
|
+
def request
|
140
|
+
packet[:request]
|
141
|
+
end
|
142
|
+
|
143
|
+
# A pointer to the Orange::Core instance
|
144
|
+
# @return [Orange::Core] the orange core run by the application
|
145
|
+
def orange
|
146
|
+
@orange
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns the array of [status, headers, content] Rack expects
|
150
|
+
# @return [Array] the triple array expected by Rack at the end
|
151
|
+
# of a call
|
152
|
+
def finish
|
153
|
+
headers = packet.headers
|
154
|
+
status = packet[:status, 200]
|
155
|
+
content = packet.content
|
156
|
+
if content.respond_to?(:to_ary)
|
157
|
+
headers["Content-Length"] = content.to_ary.
|
158
|
+
inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
|
159
|
+
end
|
160
|
+
[status, headers, content]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns self
|
164
|
+
# @return [Orange::Packet] self
|
165
|
+
def packet
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
# Includes the module passed
|
170
|
+
# @param [Module] inc the module to be mixed into the class
|
171
|
+
def self.mixin(inc)
|
172
|
+
include inc
|
173
|
+
end
|
174
|
+
|
175
|
+
# Route calls the router object set in the packet
|
176
|
+
# @return [void] route doesn't return anything directly, the
|
177
|
+
# main application calls packet.route then returns packet.finish.
|
178
|
+
# Routers set content, headers and status if necessary.
|
179
|
+
# They can also raise redirect errors to circumvent the process.
|
180
|
+
def route
|
181
|
+
router = packet['route.router']
|
182
|
+
raise 'Router not found' unless router
|
183
|
+
router.route(self)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Pulls options set out of the packet and places them into
|
187
|
+
# a hash. Options are retrieved from POST, GET, and route.resource_path
|
188
|
+
# and take that order of precedence (POST overrides GET, etc)
|
189
|
+
# @param [Array] key_list an array of keys to retrieve and merge together
|
190
|
+
# in order of precedence. :GET and :POST for request vars, the rest
|
191
|
+
# are keys directly available on packet
|
192
|
+
# @return [Hash] A hash of options set in the packet
|
193
|
+
def extract_opts(key_list = [:POST, :GET, 'route.resource_path'])
|
194
|
+
opts = {}
|
195
|
+
key_list.reverse.each do |key|
|
196
|
+
case key
|
197
|
+
when :GET then opts.merge! packet.request.GET
|
198
|
+
when :POST then opts.merge! packet.request.POST
|
199
|
+
else
|
200
|
+
opts.merge!(packet[key, {}].kind_of?(String) ? url_to_hash(packet[key]) : packet[key, {}])
|
201
|
+
end
|
202
|
+
end
|
203
|
+
opts
|
204
|
+
end
|
205
|
+
|
206
|
+
# Converts a url path to a hash with symbol keys. URL must
|
207
|
+
# be in /key/val/key/val/etc format
|
208
|
+
def url_to_hash(url)
|
209
|
+
parts = url.split('/')
|
210
|
+
hash = {}
|
211
|
+
while !parts.blank?
|
212
|
+
key = parts.shift
|
213
|
+
key = parts.shift if key.blank?
|
214
|
+
val = parts.shift
|
215
|
+
hash[key.to_sym] = val unless key.blank? or val.blank?
|
216
|
+
end
|
217
|
+
hash
|
218
|
+
end
|
219
|
+
|
220
|
+
# Method Missing allows defining custom methods
|
221
|
+
def method_missing(id, *args)
|
222
|
+
matched = false
|
223
|
+
id = id.to_s
|
224
|
+
@@matchers.each_key do |k|
|
225
|
+
matched = k if id =~ k
|
226
|
+
break if matched
|
227
|
+
end
|
228
|
+
return @@matchers[matched].call(packet, matched.match(id), args) if matched
|
229
|
+
raise NoMethodError.new("No method ##{id} found", id)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|