orange-core 0.5.3

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 (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,80 @@
1
+ require 'ostruct'
2
+ require 'rack/request'
3
+ require 'rack/utils'
4
+ require 'orange-core/middleware/base'
5
+
6
+ module Orange::Middleware
7
+ # Rack::ShowExceptions catches all exceptions raised from the app it
8
+ # wraps. It shows a useful backtrace with the sourcefile and
9
+ # clickable context, the whole Rack environment and the request
10
+ # data.
11
+ #
12
+ # Be careful when you use this on public-facing sites as it could
13
+ # reveal information helpful to attackers.
14
+ #
15
+ # Orange::Middleware::ShowExceptions is a slightly modified
16
+ # version of Rack::ShowExceptions
17
+ class ShowExceptions < Base
18
+ CONTEXT = 7
19
+
20
+ def call(env)
21
+ @app.call(env)
22
+ rescue Exception => e
23
+ backtrace = pretty(env, e)
24
+
25
+ [500,
26
+ {"Content-Type" => "text/html",
27
+ "Content-Length" => backtrace.join.size.to_s},
28
+ backtrace]
29
+ end
30
+
31
+ def packet_call(packet)
32
+ backtrace = pretty()
33
+ end
34
+
35
+ def pretty(env, exception)
36
+ req = Rack::Request.new(env)
37
+ path = (req.script_name + req.path_info).squeeze("/")
38
+
39
+ frames = exception.backtrace.map { |line|
40
+ frame = OpenStruct.new
41
+ if line =~ /(.*?):(\d+)(:in `(.*)')?/
42
+ frame.filename = $1
43
+ frame.lineno = $2.to_i
44
+ frame.function = $4
45
+
46
+ begin
47
+ lineno = frame.lineno-1
48
+ lines = ::File.readlines(frame.filename)
49
+ frame.pre_context_lineno = [lineno-CONTEXT, 0].max
50
+ frame.pre_context = lines[frame.pre_context_lineno...lineno]
51
+ frame.context_line = lines[lineno].chomp
52
+ frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
53
+ frame.post_context = lines[lineno+1..frame.post_context_lineno]
54
+ rescue
55
+ end
56
+
57
+ frame
58
+ else
59
+ nil
60
+ end
61
+ }.compact
62
+
63
+ env["rack.errors"].puts "#{exception.class}: #{exception.message}"
64
+ env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
65
+ env["rack.errors"].flush
66
+ orange_env = env["orange.env"]
67
+ parse = orange[:parser].haml("exceptions.haml", binding, :template => true)
68
+ [parse]
69
+ end
70
+
71
+ def h(obj) # :nodoc:
72
+ case obj
73
+ when String
74
+ Rack::Utils.escape_html(obj)
75
+ else
76
+ Rack::Utils.escape_html(obj.inspect)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,67 @@
1
+ require 'orange-core/middleware/base'
2
+ require 'orange-core/middleware/static_file'
3
+
4
+ module Orange::Middleware
5
+ # The Orange::Middleware::Static middleware intercepts requests for static files
6
+ # (javascript files, images, stylesheets, etc) based on the url prefixes
7
+ # passed in the options, and serves them using a Rack::File object.
8
+ #
9
+ # This differs from Rack::Static in that it can serve from multiple roots
10
+ # to accommodate both Orange static files and site specific ones.
11
+ # urls and root act the same as they do for Rack::Static. Only :libs option acts
12
+ # specially.
13
+ #
14
+ # Each lib is responsible for responding to static_url and static_dir
15
+ #
16
+ # Examples:
17
+ # use Orange::Middleware::Static :libs => [Orange::Core, AwesomeMod]
18
+ # use Orange::Middleware::Static :libs => [Orange::Core, AwesomeMod],
19
+ # :urls => ["/favicon.ico"]
20
+ #
21
+ # => Example 1 would load a file root for Orange::Core and Awesome Mod
22
+ # Orange::Core static_url is _orange_, and dir is the
23
+ # orange lib/assets folder
24
+ # => Example 2 would also redirect favicon.ico to the assets dir
25
+ #
26
+ #
27
+ # Note that as a general rule, Orange will assume everything static to be in an
28
+ # /assets/ subfolder, therefore, '/assets' will be prepended to the url given
29
+ # by static_url
30
+ # Also note, that since this is the case - setting up a match for general '/assets'
31
+ # could yield unpredictable results
32
+ #
33
+ # a static_url corresponds to the :module => in the add_css and add_js helpers
34
+ class Static < Base
35
+
36
+ def initialize(app, core, options={})
37
+ @lib_urls = {'_orange_' => File.join(core.core_dir, 'assets') }
38
+ Orange.plugins.each{|p| @lib_urls[p.assets_name] = p.assets if p.has_assets?}
39
+
40
+ @app = app
41
+ @core = core
42
+ @urls = options[:urls] || ["/favicon.ico", "/assets/public", "/assets/uploaded"]
43
+ @root = options[:root] || File.join(orange.app_dir, 'assets')
44
+ @file_server = Orange::Middleware::StaticFile.new(@root)
45
+ end
46
+
47
+ def packet_call(packet)
48
+ path = packet.env["PATH_INFO"]
49
+ can_serve_lib = @lib_urls.select { |url, server|
50
+ path.index(File.join('', 'assets', url)) == 0
51
+ }.first
52
+ can_serve = @urls.any?{|url| path.index(url) == 0 }
53
+ if can_serve_lib
54
+ lib_url = can_serve_lib.first
55
+ packet['file.root'] = can_serve_lib.last
56
+ packet['route.path'] = path.split(lib_url, 2).last
57
+ @file_server.call(packet.env)
58
+ elsif can_serve
59
+ packet['route.path'] = path.gsub(/^\/assets/, '')
60
+ @file_server.call(packet.env)
61
+ else
62
+ pass packet
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ require 'rack'
2
+ require 'rack/utils'
3
+
4
+ module Orange::Middleware
5
+ # Rack::File serves files below the +root+ given, according to the
6
+ # path info of the Rack request.
7
+ # Orange::Middleware::StaticFile acts the same as Rack::File, but acts on
8
+ # the orange specific path if available. (So site url would be ignored, etc.)
9
+ #
10
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
11
+ # like sendfile on the +path+.
12
+
13
+ class StaticFile < Rack::File
14
+ def _call(env)
15
+ @path_info = Rack::Utils.unescape(env['orange.env']["route.path"]) || Rack::Utils.unescape(env["PATH_INFO"])
16
+ @root = env['orange.env']['file.root'] || @root
17
+ return forbidden if @path_info.include? ".."
18
+
19
+ @path = F.join(@root, @path_info)
20
+
21
+ begin
22
+ if F.file?(@path) && F.readable?(@path)
23
+ serving
24
+ else
25
+ raise Errno::EPERM
26
+ end
27
+ rescue SystemCallError
28
+ not_found
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ require 'orange-core/core'
2
+ require 'orange-core/middleware/base'
3
+
4
+ module Orange::Middleware
5
+
6
+ class Template < Base
7
+ def init(*args)
8
+ @core.add_pulp(Orange::Pulp::Template)
9
+ @core.mixin(Orange::Mixins::Template)
10
+
11
+ end
12
+
13
+ def packet_call(packet)
14
+ packet['template.file'] = orange.template_for packet
15
+ status, headers, content = pass packet
16
+ if needs_wrapped?(packet)
17
+ content = wrap(packet, content)
18
+ packet[:content] = content.first
19
+ orange.fire(:wrapped, packet)
20
+ end
21
+ orange.fire(:after_wrap, packet)
22
+ packet.finish
23
+ end
24
+
25
+ def needs_wrapped?(packet)
26
+ return false if packet.request.xhr? && !packet['template.enable'] # don't wrap xhr unless specifically asked to
27
+ packet['template.file'] && !packet['template.disable']
28
+ end
29
+
30
+ def wrap(packet, content = false)
31
+ content = packet.content unless content
32
+ content = content.join
33
+ content = orange[:parser].haml(packet['template.file'], packet, :wrapped_content => content, :template => true) do
34
+ content
35
+ end
36
+ [content]
37
+ end
38
+ end
39
+ end
40
+
41
+ module Orange::Pulp::Template
42
+ def wrap
43
+ packet[:content] = orange[:parser].haml(packet['template.file'], packet, :wrapped_content => packet[:content], :template => true) do
44
+ content
45
+ end
46
+ end
47
+ end
48
+
49
+ module Orange::Mixins::Template
50
+ def template_for(packet)
51
+ template_chooser.call(packet)
52
+ end
53
+ def template_chooser(&block)
54
+ if block_given?
55
+ @template_chooser = Proc.new
56
+ else
57
+ @template_chooser ||= Proc.new {|packet| false}
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,232 @@
1
+ module Orange
2
+ # Orange::Packet is a wrapper for Rack's basic env variable.
3
+ # It acts somewhat like Rack::Request, except with more functionality.
4
+ # For each request a unique Packet is generated, and this packet is
5
+ # used to by middleware to create the response.
6
+ #
7
+ # All orange enhanced middleware has a packet_call method that
8
+ # automatically turns the generic rack call(env) into a call
9
+ # that has a packet instead, so all functions for the packet should
10
+ # be available for the request.
11
+ #
12
+ # Pulps are modules that are mixed into the Packet, allowing
13
+ # additional functionality to be used by the packet.
14
+ #
15
+ # By default, haml files are parsed in the context of their
16
+ # packet. This means all of instance variables and functions should
17
+ # be available to the haml parser.
18
+ class Packet
19
+ # By default, header will be content-type
20
+ DEFAULT_HEADERS = {"Content-Type" => 'text/html'} unless defined?(DEFAULT_HEADERS)
21
+
22
+ # We override the instantiation to only create one packet per env
23
+ # @param [Orange::Core] orange a pointer to the orange core
24
+ # @param [Hash] env a standard Rack hash
25
+ def self.new(orange, env)
26
+ return env['orange.packet'] if env['orange.packet']
27
+ super(orange, env)
28
+ end
29
+
30
+ # Allows tying in to the method_missing method without redefining it
31
+ # elsewhere. This lets dynamic methods be defined on the packet.
32
+ # Regexes are defined to match method names. #method_missing will
33
+ # loop through and try to find a match, executing the proc defined in
34
+ # the block.
35
+ # @param [Regexp] regex the regex to match
36
+ # @yield [Orange::Packet, MatchData, args] the block to execute if matched
37
+ # (passed instance, match data and args)
38
+ def self.meta_methods(regex, &block)
39
+ return unless block_given?
40
+ proc = block
41
+ @@matchers ||= {}
42
+ @@matchers[regex] = proc
43
+ end
44
+
45
+ # Allows access to the matchers added via the #meta_methods method
46
+ # @return [Hash] the matchers hash
47
+ def matchers
48
+ @@matchers || {}
49
+ end
50
+
51
+ # Initialize is only called if a packet hasn't already been called for
52
+ # this env. Sets up the basic env, creating a pointer back to self
53
+ # and a Rack::Request object.
54
+ # @param [Orange::Core] orange a pointer to the orange core
55
+ # @param [Hash] env a standard Rack hash
56
+ def initialize(orange, env)
57
+ @orange = orange
58
+ @env = env
59
+ @env['orange.packet'] = self
60
+ @env['orange.env'] = {} unless @env['orange.env']
61
+ @env['orange.env'][:request] = Rack::Request.new(env)
62
+ @env['orange.env'][:headers] = {}
63
+ end
64
+
65
+ # Gives access to the orange.env key in the env, optionally
66
+ # including a default if key isn't involved.
67
+ # @param [Symbol, String] key the key to be found
68
+ # @param [optional, Object] default the return value if key doesn't exist (default is false)
69
+ # @return [Object] any value stored in the env
70
+ def [](key, default = false)
71
+ @env['orange.env'].has_key?(key) ? @env['orange.env'][key] : default
72
+ end
73
+
74
+ # Lets user set a value in the orange.env
75
+ # @param [Symbol, String] key the key to be set
76
+ # @param [Object] val the value to be set
77
+ def []=(key, val)
78
+ @env['orange.env'][key] = val
79
+ end
80
+
81
+ # Access to the main env (orange env is stored within the main env)
82
+ # @return [Hash] the request's env hash
83
+ def env
84
+ @env
85
+ end
86
+
87
+ # Access to the rack session
88
+ # @return [Hash] the session information made available by Rack
89
+ def session
90
+ env['rack.session']["flash"] ||= {}
91
+ env['rack.session']
92
+ end
93
+
94
+ # Access to the rack session flash
95
+ # @return [String] the string stored in the flash
96
+ def flash(key = nil, val = nil)
97
+ env['rack.session']["flash"] ||= {}
98
+ if key.nil? && val.nil?
99
+ env['rack.session']["flash"]
100
+ elsif val.nil?
101
+ env['rack.session']["flash"].delete(key)
102
+ else
103
+ env['rack.session']["flash"][key] = val
104
+ end
105
+ end
106
+
107
+ # Generate headers for finalization
108
+ # @return [Hash] the header information stored in the orange.env, combined with the defaults
109
+ # set as DEFAULT_HEADERS
110
+ def headers
111
+ packet[:headers, {}].with_defaults(DEFAULT_HEADERS)
112
+ end
113
+
114
+ # Set a header
115
+ # @param [String] key the key to be set
116
+ # @param [Object] val the value to be set
117
+ def header(key, val)
118
+ @env['orange.env'][:headers][key] = val
119
+ end
120
+
121
+ # Set a header (same as #header)
122
+ # @param [String] key the key to be set
123
+ # @param [Object] val the value to be set
124
+ def add_header(key, val)
125
+ header key, val
126
+ end
127
+
128
+ # Returns the content ready to be used by Rack (wrapped in an array)
129
+ # @return [Array] array of strings to be rendered
130
+ def content
131
+ # Stringify content if it isn't a string for some weird reason.
132
+ packet[:content] = packet[:content].to_s unless packet[:content].is_a? String
133
+ return [packet[:content]] if packet[:content]
134
+ return ['']
135
+ end
136
+
137
+ # Returns the request object generated by Rack::Request(packet.env)
138
+ # @return [Rack::Request] the request object
139
+ def request
140
+ packet[:request]
141
+ end
142
+
143
+ # A pointer to the Orange::Core instance
144
+ # @return [Orange::Core] the orange core run by the application
145
+ def orange
146
+ @orange
147
+ end
148
+
149
+ # Returns the array of [status, headers, content] Rack expects
150
+ # @return [Array] the triple array expected by Rack at the end
151
+ # of a call
152
+ def finish
153
+ headers = packet.headers
154
+ status = packet[:status, 200]
155
+ content = packet.content
156
+ if content.respond_to?(:to_ary)
157
+ headers["Content-Length"] = content.to_ary.
158
+ inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
159
+ end
160
+ [status, headers, content]
161
+ end
162
+
163
+ # Returns self
164
+ # @return [Orange::Packet] self
165
+ def packet
166
+ self
167
+ end
168
+
169
+ # Includes the module passed
170
+ # @param [Module] inc the module to be mixed into the class
171
+ def self.mixin(inc)
172
+ include inc
173
+ end
174
+
175
+ # Route calls the router object set in the packet
176
+ # @return [void] route doesn't return anything directly, the
177
+ # main application calls packet.route then returns packet.finish.
178
+ # Routers set content, headers and status if necessary.
179
+ # They can also raise redirect errors to circumvent the process.
180
+ def route
181
+ router = packet['route.router']
182
+ raise 'Router not found' unless router
183
+ router.route(self)
184
+ end
185
+
186
+ # Pulls options set out of the packet and places them into
187
+ # a hash. Options are retrieved from POST, GET, and route.resource_path
188
+ # and take that order of precedence (POST overrides GET, etc)
189
+ # @param [Array] key_list an array of keys to retrieve and merge together
190
+ # in order of precedence. :GET and :POST for request vars, the rest
191
+ # are keys directly available on packet
192
+ # @return [Hash] A hash of options set in the packet
193
+ def extract_opts(key_list = [:POST, :GET, 'route.resource_path'])
194
+ opts = {}
195
+ key_list.reverse.each do |key|
196
+ case key
197
+ when :GET then opts.merge! packet.request.GET
198
+ when :POST then opts.merge! packet.request.POST
199
+ else
200
+ opts.merge!(packet[key, {}].kind_of?(String) ? url_to_hash(packet[key]) : packet[key, {}])
201
+ end
202
+ end
203
+ opts
204
+ end
205
+
206
+ # Converts a url path to a hash with symbol keys. URL must
207
+ # be in /key/val/key/val/etc format
208
+ def url_to_hash(url)
209
+ parts = url.split('/')
210
+ hash = {}
211
+ while !parts.blank?
212
+ key = parts.shift
213
+ key = parts.shift if key.blank?
214
+ val = parts.shift
215
+ hash[key.to_sym] = val unless key.blank? or val.blank?
216
+ end
217
+ hash
218
+ end
219
+
220
+ # Method Missing allows defining custom methods
221
+ def method_missing(id, *args)
222
+ matched = false
223
+ id = id.to_s
224
+ @@matchers.each_key do |k|
225
+ matched = k if id =~ k
226
+ break if matched
227
+ end
228
+ return @@matchers[matched].call(packet, matched.match(id), args) if matched
229
+ raise NoMethodError.new("No method ##{id} found", id)
230
+ end
231
+ end
232
+ end