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