orange-core 0.5.3
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.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
|