orange-core 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/README.markdown +145 -0
  2. data/lib/orange-core.rb +8 -0
  3. data/lib/orange-core/application.rb +132 -0
  4. data/lib/orange-core/assets/css/exceptions.css +50 -0
  5. data/lib/orange-core/assets/js/exceptions.js +44 -0
  6. data/lib/orange-core/carton.rb +178 -0
  7. data/lib/orange-core/core.rb +266 -0
  8. data/lib/orange-core/magick.rb +270 -0
  9. data/lib/orange-core/middleware/base.rb +96 -0
  10. data/lib/orange-core/middleware/database.rb +45 -0
  11. data/lib/orange-core/middleware/four_oh_four.rb +45 -0
  12. data/lib/orange-core/middleware/globals.rb +17 -0
  13. data/lib/orange-core/middleware/loader.rb +13 -0
  14. data/lib/orange-core/middleware/rerouter.rb +53 -0
  15. data/lib/orange-core/middleware/restful_router.rb +99 -0
  16. data/lib/orange-core/middleware/route_context.rb +39 -0
  17. data/lib/orange-core/middleware/route_site.rb +51 -0
  18. data/lib/orange-core/middleware/show_exceptions.rb +80 -0
  19. data/lib/orange-core/middleware/static.rb +67 -0
  20. data/lib/orange-core/middleware/static_file.rb +32 -0
  21. data/lib/orange-core/middleware/template.rb +60 -0
  22. data/lib/orange-core/packet.rb +232 -0
  23. data/lib/orange-core/plugin.rb +172 -0
  24. data/lib/orange-core/resource.rb +96 -0
  25. data/lib/orange-core/resources/mapper.rb +36 -0
  26. data/lib/orange-core/resources/model_resource.rb +228 -0
  27. data/lib/orange-core/resources/not_found.rb +10 -0
  28. data/lib/orange-core/resources/page_parts.rb +68 -0
  29. data/lib/orange-core/resources/parser.rb +113 -0
  30. data/lib/orange-core/resources/routable_resource.rb +16 -0
  31. data/lib/orange-core/resources/scaffold.rb +106 -0
  32. data/lib/orange-core/stack.rb +226 -0
  33. data/lib/orange-core/templates/exceptions.haml +111 -0
  34. data/lib/orange-core/views/default_resource/create.haml +4 -0
  35. data/lib/orange-core/views/default_resource/edit.haml +9 -0
  36. data/lib/orange-core/views/default_resource/list.haml +10 -0
  37. data/lib/orange-core/views/default_resource/show.haml +4 -0
  38. data/lib/orange-core/views/default_resource/table_row.haml +7 -0
  39. data/lib/orange-core/views/not_found/404.haml +2 -0
  40. data/spec/orange-core/application_spec.rb +183 -0
  41. data/spec/orange-core/carton_spec.rb +136 -0
  42. data/spec/orange-core/core_spec.rb +248 -0
  43. data/spec/orange-core/magick_spec.rb +96 -0
  44. data/spec/orange-core/middleware/base_spec.rb +38 -0
  45. data/spec/orange-core/middleware/globals_spec.rb +3 -0
  46. data/spec/orange-core/middleware/rerouter_spec.rb +3 -0
  47. data/spec/orange-core/middleware/restful_router_spec.rb +3 -0
  48. data/spec/orange-core/middleware/route_context_spec.rb +3 -0
  49. data/spec/orange-core/middleware/route_site_spec.rb +3 -0
  50. data/spec/orange-core/middleware/show_exceptions_spec.rb +3 -0
  51. data/spec/orange-core/middleware/static_file_spec.rb +3 -0
  52. data/spec/orange-core/middleware/static_spec.rb +3 -0
  53. data/spec/orange-core/mock/mock_app.rb +16 -0
  54. data/spec/orange-core/mock/mock_carton.rb +43 -0
  55. data/spec/orange-core/mock/mock_core.rb +2 -0
  56. data/spec/orange-core/mock/mock_middleware.rb +25 -0
  57. data/spec/orange-core/mock/mock_mixins.rb +19 -0
  58. data/spec/orange-core/mock/mock_model_resource.rb +47 -0
  59. data/spec/orange-core/mock/mock_pulp.rb +24 -0
  60. data/spec/orange-core/mock/mock_resource.rb +26 -0
  61. data/spec/orange-core/mock/mock_router.rb +10 -0
  62. data/spec/orange-core/orange_spec.rb +19 -0
  63. data/spec/orange-core/packet_spec.rb +203 -0
  64. data/spec/orange-core/resource_spec.rb +96 -0
  65. data/spec/orange-core/resources/mapper_spec.rb +5 -0
  66. data/spec/orange-core/resources/model_resource_spec.rb +246 -0
  67. data/spec/orange-core/resources/parser_spec.rb +5 -0
  68. data/spec/orange-core/resources/routable_resource_spec.rb +5 -0
  69. data/spec/orange-core/spec_helper.rb +53 -0
  70. data/spec/orange-core/stack_spec.rb +232 -0
  71. data/spec/stats.rb +182 -0
  72. metadata +227 -0
@@ -0,0 +1,96 @@
1
+ require 'orange-core/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
+ # Initialize will set the core and downstream app, then call init
14
+ # subclasses should override init instead of initialize.
15
+ # If the subclass defines a stack_init, then it will be registered
16
+ # as a stack_loaded event.
17
+ # @param [Object] app a downstream app
18
+ # @param [Orange::Core] core the orange core
19
+ # @param [optional, Array] args any arguments
20
+ def initialize(app, core, *args)
21
+ @app = app
22
+ @core = core
23
+ init(*args)
24
+ core.middleware(self)
25
+ end
26
+
27
+ # A stub method that subclasses can override to handle initialization
28
+ # For initialization
29
+ # @return [void]
30
+ def init(*args)
31
+ end
32
+
33
+ # The standard Rack "call". By default, Orange Middleware wraps the env into
34
+ # an Orange::Packet and passes it on to #packet_call. Subclasses will typically
35
+ # override packet_call rather than overriding call directly.
36
+ #
37
+ # Orange Middleware
38
+ # should expect to have this method ignored by upstream Orange-aware apps in
39
+ # favor of calling packet_call directly.
40
+ # @param [Hash] env the hash of environment variables given by the rack interface.
41
+ # @return [Array] the standard Rack striplet of status, headers and content
42
+ def call(env)
43
+ packet = Orange::Packet.new(@core, env)
44
+ packet_call(packet)
45
+ end
46
+
47
+ # Like the standard call, but with the env hash already wrapped into a Packet
48
+ # This is called automatically as part of #call, so subclasses can have a packet
49
+ # without having to initialize it. It will be called directly by Orange-aware
50
+ # upstream middleware, skipping the step of initializing the packet during #call.
51
+ #
52
+ # Passing the packet downstream should be done with #pass rather than the Rack
53
+ # standard @app.call, since #pass will take the packet and do a #packet_call
54
+ # if possible.
55
+ # @param [Orange::Packet] packet the packet corresponding to this env
56
+ # @return [Array] the standard Rack striplet of status, headers and content
57
+ def packet_call(packet)
58
+ pass packet
59
+ end
60
+
61
+ # Pass will sent the packet to the downstream app by calling call or packet call.
62
+ # Calling pass on a packet is the preferred way to call downstream apps, as it
63
+ # will call packet_call directly if possible (to avoid reinitializing the packet)
64
+ # @param [Orange::Packet] packet the packet to pass to downstream apps
65
+ # @return [Array] the standard Rack striplet of status, headers and content
66
+ def pass(packet)
67
+ if @app.respond_to?(:packet_call)
68
+ @app.packet_call(packet)
69
+ else
70
+ recapture(@app.call(packet.env), packet)
71
+ end
72
+ end
73
+
74
+ # After the pass has been completed, we should recapture the contents and make
75
+ # sure they are placed in the packet, in case the downstream app is not Orange aware.
76
+ # @param [Array] the standard Rack striplet of status, headers and content
77
+ # @param [Orange::Packet] packet the packet to pass to downstream apps
78
+ # @return [Array] the standard Rack striplet of status, headers and content
79
+ def recapture(response, packet)
80
+ packet[:status] = response[0]
81
+ packet[:headers] = response[1]
82
+ packet[:content] = response[2].first
83
+ response
84
+ end
85
+
86
+ # Accessor for @core, which is the stack's instance of Orange::Core
87
+ # @return [Orange::Core] the stack's instance of Orange::Core
88
+ def orange; @core; end
89
+
90
+ # Help stack traces
91
+ # @return [String] string representing this middleware (#to_s)
92
+ def inspect
93
+ self.to_s
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,45 @@
1
+ require 'orange-core/middleware/base'
2
+ module Orange::Middleware
3
+
4
+ class Database < Base
5
+ def init(opts = {})
6
+ opts = opts.with_defaults(:migration_url => (orange.options[:development_mode] ? '/__ORANGE_DB__/migrate' : false), :no_auto_upgrade => false)
7
+ orange.mixin Orange::Mixins::DBLoader
8
+ @options = opts
9
+ end
10
+
11
+ def stack_init
12
+ unless orange.options.has_key?('database') && orange.options['database'] == false
13
+ db = orange.options['database'] || 'sqlite3::memory:'
14
+ orange.load_db!(db)
15
+ orange.upgrade_db! unless @options[:no_auto_upgrade] || orange.options['db_no_auto_upgrade']
16
+ end
17
+ end
18
+
19
+ def packet_call(packet)
20
+ path = packet['route.path'] || packet.request.path_info
21
+ if @options[:migration_url] && @options[:migration_url] == path
22
+ orange.migrate_db!
23
+ after = packet.flash('redirect_to') || '/'
24
+ packet.reroute(after)
25
+ end
26
+ pass packet
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ module Orange::Mixins::DBLoader
33
+ def load_db!(url)
34
+ DataMapper.setup(:default, url)
35
+ end
36
+
37
+ def migrate_db!
38
+ DataMapper.auto_migrate!
39
+ end
40
+
41
+ def upgrade_db!
42
+ DataMapper.auto_upgrade!
43
+ end
44
+
45
+ end
@@ -0,0 +1,45 @@
1
+ require 'orange-core/middleware/base'
2
+
3
+ module Orange::Middleware
4
+ # The FlexRouter middleware takes a resource that can route paths and
5
+ # then intercepts routes for that resource. By default,
6
+ # it uses the Orange::SitemapResource.
7
+ #
8
+ # The resource is automatically loaded into the core as
9
+ # :sitemap. The resource must respond to "route?(path)"
10
+ # and "route(packet)".
11
+ #
12
+ # Pass a different routing resource using the :resource arg
13
+ class FourOhFour < Base
14
+ def init(opts = {})
15
+ @resource = opts[:resource] || Orange::NotFound
16
+ orange.load @resource.new, :not_found
17
+ orange.add_pulp Orange::Pulp::NotFoundHelper
18
+ end
19
+
20
+ # Sets the sitemap resource as the router if the resource can accept
21
+ # the path.
22
+ def packet_call(packet)
23
+ packet['route.router'] = orange[:not_found] unless packet['route.router']
24
+ begin
25
+ pass packet
26
+ rescue Orange::NotFoundException
27
+ orange[:not_found].route(packet)
28
+ packet.finish
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+
35
+ module Orange
36
+ class NotFoundException < Exception
37
+
38
+ end
39
+
40
+ module Pulp::NotFoundHelper
41
+ def not_found
42
+ raise Orange::NotFoundException.new
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ require 'orange-core/middleware/base'
2
+ module Orange::Middleware
3
+
4
+ class Globals < Base
5
+ def init(opts = {})
6
+ opts = opts.with_defaults(:file => "__ORANGE__/config.yml")
7
+ @file = opts[:file].gsub('__ORANGE__', orange.app_dir)
8
+ @globals = orange[:parser].yaml(@file) || {}
9
+ @globals.each{|k,v| orange.options[k] = v}
10
+ end
11
+ def packet_call(packet)
12
+ packet['orange.globals'] ||= orange.options
13
+ pass packet
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require 'orange-core/middleware/base'
2
+ module Orange::Middleware
3
+ class Loader < Base
4
+ def init(*args)
5
+ Dir.glob(File.join(orange.app_dir, 'resources', '*.rb')).each do |f|
6
+ require f
7
+ orange.load Orange::Inflector.constantize(Orange::Inflector.camelize(File.basename(f, '.rb'))).new
8
+ end
9
+ Dir.glob(File.join(orange.app_dir, 'cartons', '*.rb')).each { |f| require f }
10
+ Dir.glob(File.join(orange.app_dir, 'middleware', '*.rb')).each { |f| require f }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ require 'orange-core/middleware/base'
2
+ module Orange::Middleware
3
+
4
+ class Rerouter < Base
5
+ def init(*args)
6
+ orange.add_pulp Orange::Pulp::Packet_Reroute
7
+ end
8
+
9
+ def packet_call(packet)
10
+ begin
11
+ pass packet
12
+ rescue Orange::Reroute
13
+ packet.finish
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ module Orange
20
+
21
+ module Pulp::Packet_Reroute
22
+ def reroute(url, type = :real, *args)
23
+ packet['reroute.to'] = url
24
+ packet['reroute.type'] = type
25
+ packet['reroute.args'] = *args if args
26
+ raise Reroute.new(self), 'Unhandled reroute'
27
+ end
28
+ end
29
+
30
+ class Reroute < Exception
31
+ def initialize(packet)
32
+ @packet = packet
33
+ @packet[:headers] = {"Content-Type" => 'text/html', "Location" => self.url}
34
+ @packet[:status] = 302
35
+ end
36
+
37
+ def url
38
+ case packet['reroute.type']
39
+ when :real
40
+ packet['reroute.to']
41
+ # Parsing for orange urls or something
42
+ when :orange
43
+ packet.route_to(packet['reroute.to'], *packet['reroute.args', []])
44
+ else
45
+ packet['reroute.to']
46
+ end
47
+ end
48
+
49
+ def packet
50
+ @packet
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,99 @@
1
+ require 'orange-core/middleware/base'
2
+
3
+ module Orange::Middleware
4
+ class RestfulRouter < Base
5
+ def init(*args)
6
+ opts = args.extract_options!.with_defaults(:restful_contexts => [:admin, :orange], :not_found => false, :exposed_actions => {:admin => :all, :orange => :all})
7
+ @exposed = opts[:exposed_actions]
8
+ @contexts = opts[:restful_contexts]
9
+ @not_found = opts[:not_found]
10
+ end
11
+
12
+ # sets resource, resource_id, resource_action and resource_path
13
+ # /resource/id/action/[resource/path/if/any]
14
+ # /resource/action/[resource/path/if/any]
15
+ #
16
+ # In future - support for nested resources
17
+ def packet_call(packet)
18
+ return (pass packet) if packet['route.router'] # Don't route if other middleware
19
+ # already has
20
+ parts = route_parts(packet)
21
+ if(should_route?(packet, parts))
22
+ # Take parts of route and set packet info
23
+ packet['route.resource'] = parts[:resource] if parts[:resource]
24
+ packet['route.resource_id'] = parts[:resource_id] if parts[:resource_id]
25
+ packet['route.resource_action'] = parts[:resource_action] if parts[:resource_action]
26
+
27
+ # Take remainder and set to resource_path
28
+ packet['route.resource_path'] = parts[:remainder]
29
+
30
+ # Set self as router if resource was found
31
+ if(packet['route.resource', false])
32
+ packet['route.router'] = self
33
+ elsif(@not_found)
34
+ packet['route.resource'] = @not_found
35
+ packet['route.router'] = self
36
+ end
37
+ end
38
+
39
+ pass packet
40
+ end
41
+
42
+ def route_parts(packet)
43
+ return_parts = {}
44
+ path = packet['route.path'] || packet.request.path_info
45
+ parts = path.split('/')
46
+ pad = parts.shift
47
+ if !parts.empty?
48
+ resource = parts.shift
49
+ if orange.loaded?(resource.to_sym)
50
+ return_parts[:resource] = resource.to_sym
51
+ if !parts.empty?
52
+ second = parts.shift
53
+ if second =~ /^\d+$/
54
+ return_parts[:resource_id] = second
55
+ if !parts.empty?
56
+ return_parts[:resource_action] = parts.shift.to_sym
57
+ else
58
+ return_parts[:resource_action] = :show
59
+ end
60
+ else
61
+ return_parts[:resource_action] = second.to_sym
62
+ end
63
+ else
64
+ return_parts[:resource_action] = :list
65
+ end # end check for second part
66
+ else
67
+ parts.unshift(resource)
68
+ end # end check for loaded resource
69
+ end # end check for nonempty route
70
+ return_parts[:remainder] = parts.unshift(pad).join('/')
71
+ return_parts
72
+ end
73
+
74
+ def should_route?(packet, parts)
75
+ return false unless @exposed.has_key?(packet['route.context'])
76
+ action_exposed?(@exposed[packet['route.context']], parts)
77
+ end
78
+
79
+ def action_exposed?(list, route_parts)
80
+ return true if list == :all
81
+ return true if list == route_parts[:resource_action]
82
+ return true if list.is_a?(Array) && list.include?(route_parts[:resource_action])
83
+ if list.is_a?(Hash)
84
+ all = list.has_key?(:all) ? action_exposed?(list[:all], route_parts) : false
85
+ one = list.has_key?(route_parts[:resource]) ? action_exposed?(list[route_parts[:resource]], route_parts) : false
86
+ return all || one
87
+ end
88
+ false
89
+ end
90
+
91
+ def route(packet)
92
+ resource = packet['route.resource']
93
+ raise 'resource not found' unless orange.loaded? resource
94
+ mode = packet['route.resource_action'] ||
95
+ (packet['route.resource_id'] ? :show : :list)
96
+ packet[:content] = orange[resource].view packet
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,39 @@
1
+ require 'orange-core/middleware/base'
2
+
3
+ module Orange::Middleware
4
+ # This middleware handles setting orange.env['route.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 => [:preview, :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
+ pass packet
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ require 'orange-core/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
+ pass packet
49
+ end
50
+ end
51
+ end