orange 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.markdown +154 -0
  2. data/lib/orange.rb +7 -0
  3. data/lib/orange/application.rb +125 -0
  4. data/lib/orange/carton.rb +114 -0
  5. data/lib/orange/cartons/site_carton.rb +9 -0
  6. data/lib/orange/core.rb +197 -0
  7. data/lib/orange/magick.rb +91 -0
  8. data/lib/orange/middleware/access_control.rb +100 -0
  9. data/lib/orange/middleware/base.rb +41 -0
  10. data/lib/orange/middleware/database.rb +22 -0
  11. data/lib/orange/middleware/globals.rb +18 -0
  12. data/lib/orange/middleware/recapture.rb +19 -0
  13. data/lib/orange/middleware/rerouter.rb +13 -0
  14. data/lib/orange/middleware/restful_router.rb +59 -0
  15. data/lib/orange/middleware/route_context.rb +39 -0
  16. data/lib/orange/middleware/route_site.rb +51 -0
  17. data/lib/orange/middleware/show_exceptions.rb +78 -0
  18. data/lib/orange/middleware/site_load.rb +33 -0
  19. data/lib/orange/middleware/static.rb +81 -0
  20. data/lib/orange/middleware/static_file.rb +32 -0
  21. data/lib/orange/middleware/template.rb +61 -0
  22. data/lib/orange/model_resource.rb +170 -0
  23. data/lib/orange/packet.rb +88 -0
  24. data/lib/orange/resource.rb +37 -0
  25. data/lib/orange/resources/flex_router.rb +13 -0
  26. data/lib/orange/resources/mapper.rb +55 -0
  27. data/lib/orange/resources/page_parts.rb +54 -0
  28. data/lib/orange/resources/parser.rb +60 -0
  29. data/lib/orange/routable_resource.rb +16 -0
  30. data/lib/orange/stack.rb +201 -0
  31. data/spec/application_spec.rb +146 -0
  32. data/spec/carton_spec.rb +5 -0
  33. data/spec/core_spec.rb +231 -0
  34. data/spec/magick_spec.rb +89 -0
  35. data/spec/mock/mock_app.rb +17 -0
  36. data/spec/mock/mock_core.rb +2 -0
  37. data/spec/mock/mock_middleware.rb +13 -0
  38. data/spec/mock/mock_mixins.rb +19 -0
  39. data/spec/mock/mock_pulp.rb +24 -0
  40. data/spec/mock/mock_resource.rb +5 -0
  41. data/spec/mock/mock_router.rb +10 -0
  42. data/spec/model_resource_spec.rb +5 -0
  43. data/spec/orange_spec.rb +19 -0
  44. data/spec/packet_spec.rb +134 -0
  45. data/spec/resource_spec.rb +5 -0
  46. data/spec/resources/flex_router_spec.rb +5 -0
  47. data/spec/resources/mapper_spec.rb +5 -0
  48. data/spec/resources/parser_spec.rb +5 -0
  49. data/spec/routable_resource_spec.rb +5 -0
  50. data/spec/spec_helper.rb +16 -0
  51. data/spec/stack_spec.rb +202 -0
  52. data/spec/stats.rb +182 -0
  53. 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,13 @@
1
+ require 'orange/middleware/base'
2
+ module Orange::Middleware
3
+
4
+ class Rerouter < Base
5
+ def packet_call(packet)
6
+ begin
7
+ pass packet
8
+ rescue Orange::Reroute
9
+ packet.finish
10
+ end
11
+ end
12
+ end
13
+ 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