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