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