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,88 @@
1
+ module Orange
2
+ # By default, haml files are parsed in the context of their
3
+ # packet. This means all of instance variables and functions should
4
+ # be available to the haml parser.
5
+ class Packet
6
+ DEFAULT_HEADERS = {"Content-Type" => 'text/html'} unless defined?(DEFAULT_HEADERS)
7
+
8
+ def self.new(orange, env)
9
+ return env['orange.packet'] if env['orange.packet']
10
+ super(orange, env)
11
+ end
12
+
13
+ def initialize(orange, env)
14
+ @orange = orange
15
+ @env = env
16
+ @env['orange.packet'] = self
17
+ @env['orange.env'] = {} unless @env['orange.env']
18
+ @env['orange.env'][:request] = Rack::Request.new(env)
19
+ @env['orange.env'][:headers] = {}
20
+ end
21
+
22
+ def [](key, default = false)
23
+ @env['orange.env'].has_key?(key) ? @env['orange.env'][key] : default
24
+ end
25
+
26
+ def []=(key, val)
27
+ @env['orange.env'][key] = val
28
+ end
29
+
30
+ def env
31
+ @env
32
+ end
33
+
34
+ def session
35
+ env['rack.session']
36
+ end
37
+
38
+ def headers
39
+ packet[:headers, {}].with_defaults(DEFAULT_HEADERS)
40
+ end
41
+ def header(key, val)
42
+ @env['orange.env'][:headers][key] = val
43
+ end
44
+
45
+ def add_header(key, val)
46
+ header key, val
47
+ end
48
+
49
+ def content
50
+ return [packet[:content]] if packet[:content]
51
+ return []
52
+ end
53
+
54
+ def request
55
+ packet[:request]
56
+ end
57
+
58
+ def orange
59
+ @orange
60
+ end
61
+
62
+ def finish
63
+ headers = packet.headers
64
+ status = packet[:status, 200]
65
+ content = packet.content
66
+ if content.respond_to?(:to_ary)
67
+ headers["Content-Length"] = content.to_ary.
68
+ inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
69
+ end
70
+ [status, headers, content]
71
+ end
72
+
73
+ def packet
74
+ self
75
+ end
76
+
77
+ def self.mixin(inc)
78
+ include inc
79
+ end
80
+
81
+ def route
82
+ router = packet['route.router']
83
+ raise 'Router not found' unless router
84
+ router.route(self)
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,37 @@
1
+ require 'orange/core'
2
+
3
+ module Orange
4
+ # Orange Resource for being subclassed
5
+ class Resource
6
+ def initialize(*args, &block)
7
+ @options = Options.new(args, &block).hash
8
+ end
9
+
10
+ def set_orange(orange, name)
11
+ @orange = orange
12
+ @my_orange_name = name
13
+ afterLoad
14
+ self
15
+ end
16
+
17
+ def self.set_orange(*args)
18
+ raise 'trying to call set orange on a class (you probably need to instantiate a resource)'
19
+ end
20
+
21
+ def afterLoad
22
+ true
23
+ end
24
+
25
+ def orange
26
+ @orange
27
+ end
28
+
29
+ def routable
30
+ false
31
+ end
32
+
33
+ def view(packet = false)
34
+ ''
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require 'orange/core'
2
+ require 'dm-is-nested_set'
3
+ module Orange
4
+ class FlexRouter
5
+
6
+ end
7
+
8
+ class Route < SiteCarton
9
+ id
10
+
11
+ is :nested_set, :scope => [:orange_site_id]
12
+ end
13
+ end
@@ -0,0 +1,55 @@
1
+ require 'orange/core'
2
+
3
+ module Orange
4
+ class Mapper < Resource
5
+ # Takes a packet extracts request information, then calls packet.route
6
+ def afterLoad
7
+ orange.add_pulp Pulp::Packet_Mapper
8
+ end
9
+
10
+ def route_to(packet, resource, *args)
11
+ context = packet['route.context', nil]
12
+ site = packet['route.faked_site'] ? packet['route.site_url', nil] : nil
13
+ args.unshift(resource)
14
+ args.unshift(context)
15
+ args.unshift(site)
16
+ '/'+args.compact.join('/')
17
+ end
18
+ end
19
+
20
+ module Pulp::Packet_Mapper
21
+ def route_to(resource, *args)
22
+ orange[:mapper].route_to(self, resource, *args)
23
+ end
24
+
25
+ def reroute(url, type = :real)
26
+ packet['reroute.to'] = url
27
+ packet['reroute.type'] = type
28
+ raise Reroute.new(self), 'Unhandled reroute'
29
+ end
30
+
31
+ end
32
+
33
+ class Reroute < Exception
34
+ def initialize(packet)
35
+ @packet = packet
36
+ @packet[:headers] = {"Content-Type" => 'text/html', "Location" => self.url}
37
+ @packet[:status] = 302
38
+ end
39
+
40
+ def url
41
+ case packet['reroute.type']
42
+ when :real
43
+ packet['reroute.to']
44
+ # Parsing for orange urls or something
45
+ when :orange
46
+ packet.route_to(packet['reroute.to'])
47
+ end
48
+ end
49
+
50
+ def packet
51
+ @packet
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,54 @@
1
+ module Orange
2
+ class PageParts < Resource
3
+ def afterLoad
4
+ orange.add_pulp Orange::Pulp::PageParts
5
+ end
6
+ end
7
+
8
+ module Pulp::PageParts
9
+
10
+ def part
11
+ unless packet[:page_parts, false]
12
+ packet[:page_parts] = DefaultHash.new
13
+ packet[:page_parts].default = ''
14
+ end
15
+ packet[:page_parts]
16
+ end
17
+
18
+ # Feels like part should be plural, no?
19
+ def parts; part; end
20
+
21
+ def admin_sidebar_link(section, *args)
22
+ args = args.extract_options!.with_defaults(:position => 0)
23
+ sidebar = part[:admin_sidebar, {}]
24
+ sidebar[section] = [] unless sidebar.has_key?(section)
25
+ sidebar[section].insert(args[:position], {:href => args[:link], :text => args[:text]})
26
+ part[:admin_sidebar] = sidebar
27
+ end
28
+
29
+ def add_css(file, opts = {})
30
+ ie = opts[:ie] || false
31
+ mod = opts[:module] || 'public'
32
+ # module set to false gives the root assets dir
33
+ assets = File.join('assets', mod)
34
+ file = File.join('', assets, 'css', file)
35
+ if ie
36
+ part[:ie_css] = part[:ie_css] + "<link rel=\"stylesheet\" href=\"#{file}\" type=\"text/css\" media=\"screen\" charset=\"utf-8\" />"
37
+ else
38
+ part[:css] = part[:css] + "<link rel=\"stylesheet\" href=\"#{file}\" type=\"text/css\" media=\"screen\" charset=\"utf-8\" />"
39
+ end
40
+ end
41
+
42
+ def add_js(file, opts = {})
43
+ ie = opts[:ie] || false
44
+ mod = opts[:module] || 'public'
45
+ assets = File.join('assets', mod)
46
+ file = File.join('', assets, 'js', file)
47
+ if ie
48
+ part[:ie_js] = part[:ie_js] + "<script src=\"#{file}\" type=\"text/javascript\"></script>"
49
+ else
50
+ part[:js] = part[:js] + "<script src=\"#{file}\" type=\"text/javascript\"></script>"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,60 @@
1
+ require 'orange/core'
2
+ require 'haml'
3
+ require 'yaml'
4
+ require 'hpricot'
5
+
6
+ module Orange
7
+ class Parser < Resource
8
+ def afterLoad
9
+ orange.add_pulp Orange::Pulp::ParserPulp
10
+ end
11
+
12
+ def yaml(file)
13
+ string = File.read(file)
14
+ string.gsub!('__ORANGE__', orange.app_dir)
15
+ out = YAML::load(string)
16
+ end
17
+
18
+ def haml(file, packet, *vars, &block)
19
+ opts = vars.extract_options!
20
+ temp = opts.delete(:template)
21
+ resource = (opts[:resource] || '').downcase
22
+ opts.merge :orange => orange
23
+
24
+ templates_dir = File.join(orange.core_dir, 'templates')
25
+ views_dir = File.join(orange.core_dir, 'views')
26
+ default_dir = File.join(views_dir, 'default_resource')
27
+
28
+ string = false
29
+ string ||= read_if('templates', file) if temp
30
+ string ||= read_if(templates_dir, file) if temp
31
+ string ||= read_if('views', resource, file) if resource
32
+ string ||= read_if('views', file)
33
+ string ||= read_if(views_dir, file)
34
+ string ||= read_if(views_dir, 'default_resource', file)
35
+ raise LoadError, "Couldn't find haml file '#{file}" unless string
36
+
37
+ haml_engine = Haml::Engine.new(string)
38
+ out = haml_engine.render(packet, opts, &block)
39
+ end
40
+
41
+ def read_if(*args)
42
+ return File.read(File.join(*args)) if File.exists?(File.join(*args))
43
+ false
44
+ end
45
+
46
+ def hpricot(text)
47
+ Hpricot(text)
48
+ end
49
+ end
50
+
51
+ module Pulp::ParserPulp
52
+ def html(&block)
53
+ if block_given?
54
+ doc = orange[:parser].hpricot(packet[:content])
55
+ yield doc
56
+ packet[:content] = doc.to_s
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ require 'orange/resource'
2
+
3
+ module Orange
4
+ class RoutableResource < Resource
5
+ def routable; true; end
6
+
7
+ def route(path, packet)
8
+ parts = path.split('/')
9
+ first = parts[0].respond_to?(:to_sym) ? parts.shift.to_sym : :index
10
+ new_path = parts.join('/')
11
+ if self.respond_to?(first)
12
+ packet[:content] = self.__send__(first, new_path, packet)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,201 @@
1
+ require 'orange/core'
2
+ require 'rack/builder'
3
+ module Orange
4
+ # Builds an orange stack of middleware
5
+ # Use in the rackup file as follows:
6
+ # app = Orange::Stack.new do
7
+ # stack Orange::DataMapper 'sqlite3::memory:' <= loads orange specific middleware
8
+ # use OtherMiddleware
9
+ # run SomeApp.new
10
+ # end
11
+ # run app
12
+ #
13
+ # All middleware placed inside the Orange::Stack will have access
14
+ # to the Orange Core (as long as it's been written to accept it as the second
15
+ # initialization argument) when added with the 'stack' method
16
+ #
17
+ # In general, Orange::Stack works like Rack::Builder.
18
+ class Stack
19
+
20
+ # Creates a new Orange::Stack out of the passed block.
21
+ #
22
+ # If a block is not passed, it will try to build one from scratch.
23
+ # The bare minimum will be `run app_class.new(@core)`, there are also
24
+ # other stacks that can be used.
25
+ #
26
+ # @param [Orange::Application] app_class the class of the main application
27
+ # @param [prebuilt] prebuilt the optional prebuilt stack, if one isn't passed as block
28
+ def initialize(app_class = nil, prebuilt = :none, &block)
29
+ @build = Rack::Builder.new
30
+ @core = Orange::Core.new
31
+ @auto_reload = false
32
+ @recapture = true
33
+ @main_app = app_class
34
+ if block_given?
35
+ instance_eval(&block)
36
+ else
37
+ @main_app = app_class.new(@core) unless app_class.nil?
38
+ prebuild(prebuilt)
39
+ end
40
+ end
41
+
42
+ # Runs methods necessary to build a stack. Don't use if a stack
43
+ # has already been built by the initialize block.
44
+ #
45
+ # @todo Offer more choices for default stacks
46
+ def prebuild(choice)
47
+ case choice
48
+ when :none
49
+ no_recapture
50
+ run @main_app
51
+ else
52
+ no_recapture
53
+ run @main_app
54
+ end
55
+ end
56
+
57
+ # Returns the main application instance that was added by the
58
+ # run method. Obviously won't return anything useful if the
59
+ # middleware stack hasn't been set up with an explicit exit point,
60
+ # as could be the case for a pure orange middleware stack on
61
+ # top of a different exit application (like Sinatra or Rails)
62
+ def main_app
63
+ @main_app
64
+ end
65
+
66
+ # Adds middleware using the Rack::Builder#use method
67
+ # @param [Object] middleware A class of middleware that meets rack middleware requirements
68
+ def use(middleware, *args, &block)
69
+ @build.use(middleware, *args, &block)
70
+ end
71
+
72
+ # Loads resources into the core using the Orange::Core#load method
73
+ #
74
+ # all args are passed on
75
+ def load(*args, &block)
76
+ orange.load(*args, &block)
77
+ end
78
+
79
+ # Adds Orange-aware middleware using the Rack::Builder#use method, adding
80
+ # the orange core to the args passed on
81
+ def stack(middleware, *args, &block)
82
+ @build.use(middleware, @core, *args, &block)
83
+ end
84
+
85
+ # Set the auto_reload option, called without args, defaults to true,
86
+ # other option is to set it to false
87
+ def auto_reload!(val = true)
88
+ @auto_reload = val
89
+ end
90
+
91
+ # Shortcut for adding Orange::Middleware::ShowExceptions to the middleware
92
+ # stack
93
+ def use_exceptions
94
+ stack Orange::Middleware::ShowExceptions
95
+ end
96
+
97
+ # Turn off recapture middleware, which is normally just on top of the exit
98
+ # point
99
+ # @see Orange::Middleware::Recapture
100
+ def no_recapture
101
+ @recapture = false
102
+ end
103
+
104
+ # A shortcut for adding many of the routing middleware options
105
+ # simultaneously. Includes:
106
+ # * Orange::Middleware::Rerouter
107
+ # * Orange::Middleware::Static
108
+ # * Rack::AbstractFormat
109
+ # * Orange::Middleware::RouteSite
110
+ # * Orange::Middleware::RouteContext
111
+ #
112
+ # All of these are passed the args hash to use as they will, except
113
+ # for Rack::AbstractFormat
114
+ #
115
+ def prerouting(*args)
116
+ opts = args.extract_options!
117
+ stack Orange::Middleware::Rerouter, opts
118
+ stack Orange::Middleware::Static, opts
119
+ use Rack::AbstractFormat unless opts[:no_abstract_format]
120
+ # Must be used before non-destructive route altering done by Orange,
121
+ # since all orange stuff is non-destructive
122
+ stack Orange::Middleware::RouteSite, opts
123
+ stack Orange::Middleware::RouteContext, opts
124
+ end
125
+
126
+ # A shortcut for enabling restful routing via Orange::Middleware::RestfulRouter
127
+ #
128
+ # Any args are passed on to the middleware
129
+ def restful_routing(*args)
130
+ opts = args.extract_options!
131
+ stack Orange::Middleware::RestfulRouter, opts
132
+ end
133
+
134
+ # A shortcut to enable Rack::OpenID and Orange::Middleware::AccessControl
135
+ #
136
+ # Args will be passed on to Orange::Middleware::AccessControl
137
+ # @todo Make it so this is not dependent on the openid_dm_store gem
138
+ def openid_access_control(*args)
139
+ opts = args.extract_options!
140
+ require 'rack/openid'
141
+ require 'openid_dm_store'
142
+
143
+ use Rack::OpenID, OpenIDDataMapper::DataMapperStore.new
144
+ stack Orange::Middleware::AccessControl, opts
145
+ end
146
+
147
+ # Adds pulp to the core via the Orange::Core#add_pulp method
148
+ # @param [Orange::Mixin] mod a mixin to be included in the packet
149
+ def add_pulp(mod)
150
+ orange.add_pulp(mod)
151
+ end
152
+
153
+ # The exit point for the middleware stack,
154
+ # this method will add the Orange::Middleware::Recapture if applicable
155
+ # add the app to @main_app and then call Rack::Builder#run with the main app
156
+ def run(app, *args)
157
+ opts = args.extract_options!
158
+ if @recapture
159
+ stack Orange::Middleware::Recapture
160
+ @recapture = false
161
+ end
162
+ @main_app = app
163
+ @build.run(app)
164
+ end
165
+
166
+ # Returns the Orange::Core
167
+ # @return [Orange::Core] The orange core
168
+ def orange
169
+ @core
170
+ end
171
+
172
+ # Passes through to Rack::Builder#map
173
+ # @todo Make this work - passing the block on to builder
174
+ # means we can't intercept anything, which will yield
175
+ # unexpected results
176
+ def map(path, &block)
177
+ raise 'not yet supported'
178
+ @build.map(path, &block)
179
+ end
180
+
181
+ # Builds the middleware stack (or uses a cached one)
182
+ #
183
+ # If auto_reload is enabled ({#auto_reload!}), builds every time
184
+ #
185
+ # @return [Object] a full stack of middleware and the exit application,
186
+ # conforming to Rack guidelines
187
+ def app
188
+ @app = false if @auto_reload # Rebuild no matter what if autoload
189
+ @app ||= @build.to_app # Build if necessary
190
+ orange.fire(:stack_loaded, @app)
191
+ @app
192
+ end
193
+
194
+ # Sets the core and then passes on to the stack, according to standard
195
+ # rack procedure
196
+ def call(env)
197
+ env['orange.core'] = @core
198
+ app.call(env)
199
+ end
200
+ end
201
+ end