orange 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +154 -0
- data/lib/orange.rb +7 -0
- data/lib/orange/application.rb +125 -0
- data/lib/orange/carton.rb +114 -0
- data/lib/orange/cartons/site_carton.rb +9 -0
- data/lib/orange/core.rb +197 -0
- data/lib/orange/magick.rb +91 -0
- data/lib/orange/middleware/access_control.rb +100 -0
- data/lib/orange/middleware/base.rb +41 -0
- data/lib/orange/middleware/database.rb +22 -0
- data/lib/orange/middleware/globals.rb +18 -0
- data/lib/orange/middleware/recapture.rb +19 -0
- data/lib/orange/middleware/rerouter.rb +13 -0
- data/lib/orange/middleware/restful_router.rb +59 -0
- data/lib/orange/middleware/route_context.rb +39 -0
- data/lib/orange/middleware/route_site.rb +51 -0
- data/lib/orange/middleware/show_exceptions.rb +78 -0
- data/lib/orange/middleware/site_load.rb +33 -0
- data/lib/orange/middleware/static.rb +81 -0
- data/lib/orange/middleware/static_file.rb +32 -0
- data/lib/orange/middleware/template.rb +61 -0
- data/lib/orange/model_resource.rb +170 -0
- data/lib/orange/packet.rb +88 -0
- data/lib/orange/resource.rb +37 -0
- data/lib/orange/resources/flex_router.rb +13 -0
- data/lib/orange/resources/mapper.rb +55 -0
- data/lib/orange/resources/page_parts.rb +54 -0
- data/lib/orange/resources/parser.rb +60 -0
- data/lib/orange/routable_resource.rb +16 -0
- data/lib/orange/stack.rb +201 -0
- data/spec/application_spec.rb +146 -0
- data/spec/carton_spec.rb +5 -0
- data/spec/core_spec.rb +231 -0
- data/spec/magick_spec.rb +89 -0
- data/spec/mock/mock_app.rb +17 -0
- data/spec/mock/mock_core.rb +2 -0
- data/spec/mock/mock_middleware.rb +13 -0
- data/spec/mock/mock_mixins.rb +19 -0
- data/spec/mock/mock_pulp.rb +24 -0
- data/spec/mock/mock_resource.rb +5 -0
- data/spec/mock/mock_router.rb +10 -0
- data/spec/model_resource_spec.rb +5 -0
- data/spec/orange_spec.rb +19 -0
- data/spec/packet_spec.rb +134 -0
- data/spec/resource_spec.rb +5 -0
- data/spec/resources/flex_router_spec.rb +5 -0
- data/spec/resources/mapper_spec.rb +5 -0
- data/spec/resources/parser_spec.rb +5 -0
- data/spec/routable_resource_spec.rb +5 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/stack_spec.rb +202 -0
- data/spec/stats.rb +182 -0
- metadata +194 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
# Monkey Patch the extract_options! stolen from ActiveSupport
|
2
|
+
class ::Array
|
3
|
+
def extract_options!
|
4
|
+
last.is_a?(::Hash) ? pop : {}
|
5
|
+
end
|
6
|
+
def extract_with_defaults(defaults)
|
7
|
+
extract_options!.with_defaults(defaults)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Monkey Patch for merging defaults into a hash
|
12
|
+
class ::Hash
|
13
|
+
def with_defaults(defaults)
|
14
|
+
self.merge(defaults){ |key, old, new| old.nil? ? new : old }
|
15
|
+
end
|
16
|
+
def with_defaults!(defaults)
|
17
|
+
self.merge!(defaults){ |key, old, new| old.nil? ? new : old }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
# Monkey patch for awesome array -> hash conversions
|
24
|
+
# use:
|
25
|
+
#
|
26
|
+
# [:x, :y, :z].inject_hash do |results, letter|
|
27
|
+
# results[letter] = rand(100)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# => {:x => 32, :y => 63, :z => 91}
|
31
|
+
module Enumerable
|
32
|
+
def inject_hash(hash = {})
|
33
|
+
inject(hash) {|(h,item)| yield(h,item); h}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassInheritableAttributes
|
38
|
+
def cattr_inheritable(*args)
|
39
|
+
@cattr_inheritable_attrs ||= [:cattr_inheritable_attrs]
|
40
|
+
@cattr_inheritable_attrs += args
|
41
|
+
args.each do |arg|
|
42
|
+
class_eval %(
|
43
|
+
class << self; attr_accessor :#{arg} end
|
44
|
+
)
|
45
|
+
end
|
46
|
+
@cattr_inheritable_attrs
|
47
|
+
end
|
48
|
+
|
49
|
+
def inherited(subclass)
|
50
|
+
@cattr_inheritable_attrs.each do |inheritable_attribute|
|
51
|
+
instance_var = "@#{inheritable_attribute}"
|
52
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module Orange
|
58
|
+
|
59
|
+
# Class that extends hash so that [] can have an optional second attribute
|
60
|
+
class DefaultHash < ::Hash
|
61
|
+
def [](key, my_default = nil)
|
62
|
+
my_default = self.default if my_default.nil?
|
63
|
+
self.has_key?(key) ? super(key) : my_default
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Simple class for evaluating options and allowing us to access them.
|
68
|
+
class Options
|
69
|
+
|
70
|
+
def initialize(*options, &block)
|
71
|
+
@options = options.extract_options!
|
72
|
+
@options ||= {}
|
73
|
+
instance_eval(&block) if block_given?
|
74
|
+
end
|
75
|
+
|
76
|
+
def hash
|
77
|
+
@options
|
78
|
+
end
|
79
|
+
|
80
|
+
def method_missing(key, *args)
|
81
|
+
return (@options[key.to_s.gsub(/\?$/, '').to_sym].eql?(true)) if key.to_s.match(/\?$/)
|
82
|
+
if args.empty?
|
83
|
+
@options[key.to_sym]
|
84
|
+
elsif(key.to_s.match(/\=$/))
|
85
|
+
@options[key.to_s.gsub(/\=$/, '').to_sym] = (args.size == 1 ? args.first : args)
|
86
|
+
else
|
87
|
+
@options[key.to_sym] = (args.size == 1 ? args.first : args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
module Orange::Middleware
|
6
|
+
|
7
|
+
class AccessControl < Base
|
8
|
+
def init(*args)
|
9
|
+
defs = {:locked => [:admin, :orange], :login => '/login',
|
10
|
+
:handle_login => true, :openid => true, :config_id => true}
|
11
|
+
opts = args.extract_with_defaults(defs)
|
12
|
+
@openid = opts.has_key?(:openid) ? opts[:openid] : false
|
13
|
+
@locked = opts[:locked]
|
14
|
+
@login = opts[:login]
|
15
|
+
@handle = opts[:handle_login]
|
16
|
+
@single = opts[:config_id]
|
17
|
+
end
|
18
|
+
|
19
|
+
def packet_call(packet)
|
20
|
+
packet['user.id'] ||= (packet.session['user.id'] || false)
|
21
|
+
if @openid && need_to_handle?(packet)
|
22
|
+
ret = handle_openid(packet)
|
23
|
+
return ret if ret # unless handle_openid returns false, exit immediately
|
24
|
+
end
|
25
|
+
unless access_allowed?(packet)
|
26
|
+
packet.session['user.after_login'] = packet.request.path
|
27
|
+
packet.reroute(@login)
|
28
|
+
end
|
29
|
+
|
30
|
+
pass packet
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_allowed?(packet)
|
34
|
+
return true unless @locked.include?(packet['route.context'])
|
35
|
+
if packet['user.id']
|
36
|
+
if @single && (packet['user.id'] == packet['orange.globals']['main_user'])
|
37
|
+
true
|
38
|
+
elsif @single
|
39
|
+
# Current id no good.
|
40
|
+
packet['user.id'] = false
|
41
|
+
packet.session['user.id'] = false
|
42
|
+
false
|
43
|
+
else
|
44
|
+
true
|
45
|
+
end
|
46
|
+
else
|
47
|
+
false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def need_to_handle?(packet)
|
52
|
+
@handle && (packet.env['REQUEST_PATH'] == @login)
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_openid(packet)
|
56
|
+
packet.reroute('/') if packet['user.id'] # Reroute to index if we're logged in.
|
57
|
+
# If login set
|
58
|
+
if packet.request.post?
|
59
|
+
packet['template.disable'] = true
|
60
|
+
# Check for openid response
|
61
|
+
if resp = packet.env["rack.openid.response"]
|
62
|
+
if resp.status == :success
|
63
|
+
packet['user.id'] = resp.identity_url
|
64
|
+
packet['user.openid.url'] = resp.identity_url
|
65
|
+
packet['user.openid.response'] = resp
|
66
|
+
|
67
|
+
after = packet.session.has_key?('user.after_login') ?
|
68
|
+
packet.session['user.after_login'] : false
|
69
|
+
packet.session['user.after_login'] = false
|
70
|
+
|
71
|
+
# Save id into session if we have one.
|
72
|
+
packet.session['user.id'] = packet['user.id']
|
73
|
+
|
74
|
+
# If the user was supposed to be going somewhere, redirect there
|
75
|
+
packet.reroute(after) if after
|
76
|
+
packet.reroute('/')
|
77
|
+
false
|
78
|
+
else
|
79
|
+
packet.session['flash.error'] = resp.status
|
80
|
+
packet.reroute(@login)
|
81
|
+
false
|
82
|
+
end
|
83
|
+
# Set WWW-Authenticate header if awaiting openid.response
|
84
|
+
else
|
85
|
+
packet[:status] = 401
|
86
|
+
packet[:headers] = {}
|
87
|
+
packet.add_header('WWW-Authenticate', Rack::OpenID.build_header(
|
88
|
+
:identifier => packet.request.params["openid_identifier"]
|
89
|
+
))
|
90
|
+
packet[:content] = 'Got openID?'
|
91
|
+
packet.finish
|
92
|
+
end
|
93
|
+
# Show login form, if necessary
|
94
|
+
else
|
95
|
+
packet[:content] = orange[:parser].haml('openid_login.haml', packet)
|
96
|
+
packet.finish
|
97
|
+
end
|
98
|
+
end # end handle_openid
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'orange/packet'
|
2
|
+
|
3
|
+
# Orange Middleware is a bit more complex than Rack middleware.
|
4
|
+
# Initializing it requires both a link to the downstream app and
|
5
|
+
# the core, and calling the app often requires an Orange::Packet
|
6
|
+
#
|
7
|
+
# Orange::Middleware::Base takes care of these basic tasks.
|
8
|
+
# Subclasses override the init method for extra initialization
|
9
|
+
# and the packet_call for a call with a packet, rather than
|
10
|
+
# a basic call
|
11
|
+
module Orange::Middleware
|
12
|
+
class Base
|
13
|
+
def initialize(app, core, *args)
|
14
|
+
@app = app
|
15
|
+
@core = core
|
16
|
+
init(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def init(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
packet = Orange::Packet.new(@core, env)
|
24
|
+
packet_call(packet)
|
25
|
+
end
|
26
|
+
|
27
|
+
def packet_call(packet)
|
28
|
+
pass packet
|
29
|
+
end
|
30
|
+
|
31
|
+
def pass(packet)
|
32
|
+
@app.call(packet.env)
|
33
|
+
end
|
34
|
+
|
35
|
+
def orange; @core; end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
self.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
module Orange::Middleware
|
3
|
+
|
4
|
+
class Database < Base
|
5
|
+
def init(*args)
|
6
|
+
orange.mixin Orange::Mixins::DBLoader
|
7
|
+
end
|
8
|
+
def packet_call(packet)
|
9
|
+
db = packet['orange.globals']['database'] || 'sqlite3::memory:'
|
10
|
+
orange.load_db!(db)
|
11
|
+
pass packet
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
module Orange::Mixins::DBLoader
|
18
|
+
def load_db!(url)
|
19
|
+
DataMapper.setup(:default, url)
|
20
|
+
DataMapper.auto_upgrade!
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
module Orange::Middleware
|
3
|
+
|
4
|
+
class Globals < Base
|
5
|
+
def init(*args)
|
6
|
+
opts = args.extract_options!.with_defaults(:file => "__ORANGE__/config.yml")
|
7
|
+
@file = opts[:file].gsub('__ORANGE__', orange.app_dir)
|
8
|
+
@globals = orange[:parser].yaml(@file)
|
9
|
+
end
|
10
|
+
def packet_call(packet)
|
11
|
+
globs = packet['orange.globals'] || {}
|
12
|
+
globs.merge! orange.options
|
13
|
+
packet['orange.globals'] = globs.merge @globals
|
14
|
+
pass packet
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
module Orange::Middleware
|
3
|
+
|
4
|
+
# Middleware to recapture return info and put it back into the
|
5
|
+
# packet. Since the Orange::Stack is all middleware, this is
|
6
|
+
# important for adding after filters into the orange stack
|
7
|
+
# that can interact with the returns of external apps
|
8
|
+
class Recapture < Base
|
9
|
+
|
10
|
+
def packet_call(packet)
|
11
|
+
ret = pass packet
|
12
|
+
packet[:status] = ret[0]
|
13
|
+
packet[:headers] = ret[1]
|
14
|
+
packet[:content] = ret[2].first
|
15
|
+
ret
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
|
3
|
+
module Orange::Middleware
|
4
|
+
class RestfulRouter < Base
|
5
|
+
def init(*args)
|
6
|
+
opts = args.extract_options!.with_defaults(:contexts => [:admin, :orange], :root_resource => :not_found)
|
7
|
+
@contexts = opts[:contexts]
|
8
|
+
@root_resource = opts[:root_resource]
|
9
|
+
end
|
10
|
+
|
11
|
+
# sets resource, resource_id, resource_action and resource_path
|
12
|
+
# /resource/id/action/[resource/path/if/any]
|
13
|
+
# /resource/action/[resource/path/if/any]
|
14
|
+
#
|
15
|
+
# In future - support for nested resources
|
16
|
+
def packet_call(packet)
|
17
|
+
return (pass packet) if packet['route.router'] # Don't route if other middleware
|
18
|
+
# already has
|
19
|
+
if(@contexts.include?(packet['route.context']))
|
20
|
+
path = packet['route.path'] || packet.request.path_info
|
21
|
+
parts = path.split('/')
|
22
|
+
pad = parts.shift
|
23
|
+
if !parts.empty?
|
24
|
+
resource = parts.shift
|
25
|
+
if orange.loaded?(resource.to_sym)
|
26
|
+
packet['route.resource'] = resource.to_sym
|
27
|
+
if !parts.empty?
|
28
|
+
second = parts.shift
|
29
|
+
if second =~ /^\d+$/
|
30
|
+
packet['route.resource_id'] = second
|
31
|
+
if !parts.empty?
|
32
|
+
packet['route.resource_action'] = parts.shift.to_sym
|
33
|
+
end
|
34
|
+
else
|
35
|
+
packet['route.resource_action'] = second.to_sym
|
36
|
+
end
|
37
|
+
end # end check for second part
|
38
|
+
else
|
39
|
+
parts.unshift(resource)
|
40
|
+
end # end check for loaded resource
|
41
|
+
end # end check for nonempty route
|
42
|
+
|
43
|
+
packet['route.resource'] ||= @root_resource
|
44
|
+
packet['route.resource_path'] = parts.unshift(pad).join('/')
|
45
|
+
packet['route.router'] = self
|
46
|
+
end # End context match if
|
47
|
+
|
48
|
+
pass packet
|
49
|
+
end
|
50
|
+
|
51
|
+
def route(packet)
|
52
|
+
resource = packet['route.resource']
|
53
|
+
raise 'resource not found' unless orange.loaded? resource
|
54
|
+
mode = packet['route.resource_action'] ||
|
55
|
+
(packet['route.resource_id'] ? :show : :list)
|
56
|
+
packet[:content] = orange[resource].view packet
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
|
3
|
+
module Orange::Middleware
|
4
|
+
# This middleware handles setting orange.env[:context]
|
5
|
+
# to a value based on the route, if any. The route is then
|
6
|
+
# trimmed before continuing on.
|
7
|
+
class RouteContext < Base
|
8
|
+
def initialize(app, core, *args)
|
9
|
+
opts = args.extract_options!
|
10
|
+
opts.with_defaults!(:contexts => [:live, :admin, :orange],
|
11
|
+
:default => :live,
|
12
|
+
:urls => {})
|
13
|
+
@app = app
|
14
|
+
@core = core
|
15
|
+
@contexts = opts[:contexts]
|
16
|
+
@default = opts[:default]
|
17
|
+
@urls = opts[:urls]
|
18
|
+
end
|
19
|
+
def packet_call(packet)
|
20
|
+
path_info = packet['route.path'] || packet.env['PATH_INFO']
|
21
|
+
path = path_info.split('/')
|
22
|
+
pad = path.shift # Shift off empty first part
|
23
|
+
if @urls[packet.request.host]
|
24
|
+
packet['route.context'] = urls[packet.request.host]
|
25
|
+
elsif path.empty?
|
26
|
+
packet['route.context'] = @default
|
27
|
+
else
|
28
|
+
if(@contexts.include?(path.first.to_sym))
|
29
|
+
packet['route.context'] = path.shift.to_sym
|
30
|
+
path.unshift(pad)
|
31
|
+
packet['route.path'] = path.join('/')
|
32
|
+
else
|
33
|
+
packet['route.context'] = @default
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@app.call(packet.env)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'orange/middleware/base'
|
2
|
+
|
3
|
+
module Orange::Middleware
|
4
|
+
# This middleware handles setting orange.env['route.site_url']
|
5
|
+
# to a value based on the route, if any. The route is then
|
6
|
+
# trimmed before continuing on.
|
7
|
+
#
|
8
|
+
# Options -
|
9
|
+
# :multi - does Orange need to handle multiple urls
|
10
|
+
# :fake_it - host url(s) that Orange will fake requests on
|
11
|
+
# ex: :multi => true, :fake_it => 'localhost'
|
12
|
+
# will fake hostnames as first component of url
|
13
|
+
# only on localhost
|
14
|
+
class RouteSite < Base
|
15
|
+
def initialize(app, core, *args)
|
16
|
+
opts = args.extract_options!
|
17
|
+
opts.with_defaults!(:multi => false, :fake_it => ['localhost'])
|
18
|
+
@app = app
|
19
|
+
@core = core
|
20
|
+
@multi = opts[:multi]
|
21
|
+
# Put fake_it into an array, if necessary
|
22
|
+
@fake_it = opts[:fake_it].respond_to?(:include?) ?
|
23
|
+
opts[:fake_it] : [opts[:fake_it]]
|
24
|
+
end
|
25
|
+
|
26
|
+
def packet_call(packet)
|
27
|
+
request = packet.request
|
28
|
+
path_info = packet['route.path'] || packet.env['PATH_INFO']
|
29
|
+
path = path_info.split('/')
|
30
|
+
pad = path.shift # Shift off empty first part
|
31
|
+
packet['route.faked_site'] = false
|
32
|
+
if @multi
|
33
|
+
if path.empty?
|
34
|
+
packet['route.site_url'] = request.host
|
35
|
+
else
|
36
|
+
if @fake_it.include?(request.host)
|
37
|
+
packet['route.site_url'] = path.shift
|
38
|
+
packet['route.faked_site'] = true
|
39
|
+
else
|
40
|
+
packet['route.site_url'] = request.host
|
41
|
+
end
|
42
|
+
path.unshift(pad)
|
43
|
+
packet['route.path'] = path.join('/')
|
44
|
+
end
|
45
|
+
else
|
46
|
+
packet['route.site_url'] = request.host
|
47
|
+
end
|
48
|
+
@app.call(packet.env)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|