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,78 @@
1
+ require 'ostruct'
2
+ require 'rack/request'
3
+ require 'rack/utils'
4
+ require 'orange/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
+ class ShowExceptions < Base
16
+ CONTEXT = 7
17
+
18
+ def call(env)
19
+ @app.call(env)
20
+ rescue StandardError, LoadError, SyntaxError => e
21
+ backtrace = pretty(env, e)
22
+
23
+ [500,
24
+ {"Content-Type" => "text/html",
25
+ "Content-Length" => backtrace.join.size.to_s},
26
+ backtrace]
27
+ end
28
+
29
+ def packet_call(packet)
30
+ backtrace = pretty()
31
+ end
32
+
33
+ def pretty(env, exception)
34
+ req = Rack::Request.new(env)
35
+ path = (req.script_name + req.path_info).squeeze("/")
36
+
37
+ frames = exception.backtrace.map { |line|
38
+ frame = OpenStruct.new
39
+ if line =~ /(.*?):(\d+)(:in `(.*)')?/
40
+ frame.filename = $1
41
+ frame.lineno = $2.to_i
42
+ frame.function = $4
43
+
44
+ begin
45
+ lineno = frame.lineno-1
46
+ lines = ::File.readlines(frame.filename)
47
+ frame.pre_context_lineno = [lineno-CONTEXT, 0].max
48
+ frame.pre_context = lines[frame.pre_context_lineno...lineno]
49
+ frame.context_line = lines[lineno].chomp
50
+ frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
51
+ frame.post_context = lines[lineno+1..frame.post_context_lineno]
52
+ rescue
53
+ end
54
+
55
+ frame
56
+ else
57
+ nil
58
+ end
59
+ }.compact
60
+
61
+ env["rack.errors"].puts "#{exception.class}: #{exception.message}"
62
+ env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
63
+ env["rack.errors"].flush
64
+ orange_env = env["orange.env"]
65
+ parse = orange[:parser].haml("exceptions.haml", binding, :template => true)
66
+ [parse]
67
+ end
68
+
69
+ def h(obj) # :nodoc:
70
+ case obj
71
+ when String
72
+ Rack::Utils.escape_html(obj)
73
+ else
74
+ Rack::Utils.escape_html(obj.inspect)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ require 'orange/middleware/base'
2
+ module Orange::Middleware
3
+ # This will load information about the site to into the orange env
4
+ # - packet['site'] will be an instance of the site object
5
+ #
6
+ class SiteLoad < Base
7
+ def init(*args)
8
+ orange.load Orange::SiteResource.new, :orange_sites
9
+ end
10
+
11
+ def packet_call(packet)
12
+ url = packet['route.site_url']
13
+ site = Orange::Site.first(:url.like => url)
14
+ packet['site'] = site if site
15
+ pass packet
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ module Orange
22
+ class Site < Carton
23
+ id
24
+ admin do
25
+ title :name
26
+ text :url
27
+ end
28
+ end
29
+
30
+ class SiteResource < ModelResource
31
+ use Orange::Site
32
+ end
33
+ end
@@ -0,0 +1,81 @@
1
+ require 'orange/middleware/base'
2
+ require 'orange/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
+ core.mixin Orange::Mixins::Static
38
+ core.add_static('_orange_', File.join(core.core_dir, 'assets'))
39
+ @app = app
40
+ @core = core
41
+ @libs = options[:libs] || [Orange::Core]
42
+
43
+ @urls = options[:urls] || ["/favicon.ico", "/assets/public"]
44
+ @root = options[:root] || File.join(orange.app_dir, 'assets')
45
+ @lib_urls = core.statics
46
+
47
+ @file_server = Orange::Middleware::StaticFile.new(@root)
48
+ end
49
+
50
+ def packet_call(packet)
51
+ path = packet.env["PATH_INFO"]
52
+ can_serve_lib = @lib_urls.select{ |url, server| path.index(url) == 0 }.first
53
+ can_serve = @urls.any?{|url| path.index(url) == 0 }
54
+
55
+ if can_serve_lib
56
+ lib_url = can_serve_lib.first
57
+ packet['file.root'] = can_serve_lib.last
58
+ packet['route.path'] = path.split(lib_url, 2).last
59
+ @file_server.call(packet.env)
60
+ elsif can_serve
61
+ packet['route.path'] = path
62
+ @file_server.call(packet.env)
63
+ else
64
+ pass packet
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ module Orange::Mixins::Static
72
+ def add_static(lib_name, path)
73
+ @statics ||= {}
74
+ key = File.join('', 'assets', lib_name)
75
+ @statics.merge!(key => path)
76
+ end
77
+
78
+ def statics
79
+ @statics ||= {}
80
+ end
81
+ 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,61 @@
1
+ require 'orange/core'
2
+ require 'orange/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
+ # Establish a default template chooser
12
+ @core.template_chooser do |packet|
13
+ if packet['route.context'] == :admin
14
+ packet.add_css('admin.css', :module => '_orange_')
15
+ 'admin.haml'
16
+ else
17
+ false
18
+ end
19
+ end
20
+ end
21
+
22
+ def packet_call(packet)
23
+ packet['template.file'] = orange.template_for packet
24
+ status, headers, content = pass packet
25
+ if needs_wrapped?(packet)
26
+ content = wrap(packet, content)
27
+ end
28
+ [status, headers, content]
29
+ end
30
+
31
+ def needs_wrapped?(packet)
32
+ packet['template.file'] && !packet['template.disable']
33
+ end
34
+
35
+ def wrap(packet, content = false)
36
+ content = packet.content unless content
37
+ content = content.join
38
+ content = orange[:parser].haml(packet['template.file'], packet, :wrapped_content => content, :template => true) do
39
+ content
40
+ end
41
+ [content]
42
+ end
43
+ end
44
+ end
45
+
46
+ module Orange::Pulp::Template
47
+ def wrap
48
+ packet[:content] = orange[:parser].haml(packet['template.file'], packet, :wrapped_content => packet[:content], :template => true) do
49
+ content
50
+ end
51
+ end
52
+ end
53
+
54
+ module Orange::Mixins::Template
55
+ def template_for(packet)
56
+ @template_chooser.call(packet)
57
+ end
58
+ def template_chooser(&block)
59
+ @template_chooser = Proc.new
60
+ end
61
+ end
@@ -0,0 +1,170 @@
1
+ require 'orange/routable_resource'
2
+
3
+ module Orange
4
+ class ModelResource < RoutableResource
5
+ extend ClassInheritableAttributes
6
+ cattr_inheritable :model_class
7
+
8
+ def self.use(my_model_class)
9
+ @model_class = my_model_class
10
+ end
11
+
12
+
13
+ def model_class
14
+ self.class.model_class
15
+ end
16
+
17
+ def view(packet, *args)
18
+ opts = args.extract_options!.with_defaults({:path => ''})
19
+ props = model_class.form_props(packet['route.context'])
20
+ resource_id = opts[:id] || packet['route.resource_id'] || false
21
+ mode = opts[:mode] || packet['route.resource_action'] ||
22
+ (resource_id ? :show : :list)
23
+
24
+ haml_opts = {:props => props, :resource => self.class.to_s, :model_name => @my_orange_name}.merge!(opts)
25
+
26
+ case mode
27
+ when :show
28
+ haml_opts.with_defaults! :model => findOne(packet, mode, resource_id)
29
+ orange[:parser].haml('show.haml', packet, haml_opts)
30
+ when :edit
31
+ haml_opts.with_defaults! :model => findOne(packet, mode, resource_id)
32
+ orange[:parser].haml('edit.haml', packet, haml_opts)
33
+ when :create
34
+ haml_opts.with_defaults! :model => findOne(packet, mode, resource_id)
35
+ orange[:parser].haml('create.haml', packet, haml_opts)
36
+ when :table_row
37
+ haml_opts.with_defaults! :model => findOne(packet, mode, resource_id)
38
+ orange[:parser].haml('table_row.haml', packet, haml_opts)
39
+ when :list
40
+ haml_opts.with_defaults! :list => findList(packet, mode)
41
+ orange[:parser].haml('list.haml', packet, haml_opts)
42
+ else
43
+ self.__send__(mode, packet, haml_opts)
44
+ end
45
+ end
46
+
47
+ def findOne(packet, mode, id = false)
48
+ return false unless id
49
+ model_class.get(id)
50
+ end
51
+
52
+ def findList(packet, mode)
53
+ model_class.all || []
54
+ end
55
+
56
+ def viewExtras(packet, mode)
57
+ {}
58
+ end
59
+
60
+ def route(path, packet)
61
+ parts = path.split('/')
62
+ if parts[0] =~ /^[0-9]+$/
63
+ route_id = parts.shift
64
+ else
65
+ route_id = false
66
+ end
67
+ parts.unshift('show') if parts.empty? && route_id
68
+ new_path = parts.join('/')
69
+ packet['route.resource_id'] = route_id if route_id
70
+ super(new_path, packet)
71
+ end
72
+
73
+ def new(packet, *opts)
74
+ if packet.request.post?
75
+ model_class.new(packet.request.params[@my_orange_name.to_s]).save
76
+ end
77
+ packet.reroute(@my_orange_name, :orange)
78
+ end
79
+
80
+ def delete(packet, *opts)
81
+ if packet.request.delete?
82
+ m = model_class.get(packet['route.resource_id'])
83
+ m.destroy! if m
84
+ end
85
+ packet.reroute(@my_orange_name, :orange)
86
+ end
87
+
88
+ def save(packet, *opts)
89
+ if packet.request.post?
90
+ m = model_class.get(packet['route.resource_id'])
91
+ if m
92
+ m.update(packet.request.params[@my_orange_name.to_s])
93
+ else
94
+ end
95
+ end
96
+ packet.reroute(@my_orange_name, :orange)
97
+ end
98
+
99
+
100
+ def show(packet, *opts)
101
+ view(packet, :mode => :show, *opts)
102
+ end
103
+
104
+ def edit(packet, *opts)
105
+ view(packet, :mode => :edit, *opts)
106
+ end
107
+
108
+ def create(packet, *opts)
109
+ view(packet, :mode => :create, *opts)
110
+ end
111
+
112
+ def table_row(packet, *opts)
113
+ view(packet, :mode => :table_row, *opts)
114
+ end
115
+
116
+ def list(packet, *opts)
117
+ view(packet, :mode => :list, *opts)
118
+ end
119
+
120
+ def index(packet, *opts)
121
+ view(packet, :mode => :list, *opts)
122
+ end
123
+
124
+ end
125
+
126
+ class Packet
127
+ def form_link(text, link, confirm = false, *args)
128
+ opts = args.extract_options!
129
+ meth = (opts[:method]? "<input type='hidden' name='_method' value='#{opts[:method]}' />" : '')
130
+ if confirm
131
+ "<form action='#{link}' method='post' class='mini' onsubmit='return confirm(\"#{confirm}\")'><button class='link_button'><a href='#'>#{text}</a></button>#{meth}</form>"
132
+ else
133
+ "<form action='#{link}' method='post' class='mini'><button class='link_button'><a href='#'>#{text}</a></button>#{meth}</form>"
134
+ end
135
+ end
136
+
137
+ def view(model_name, *args)
138
+ orange[model_name].view(self, *args)
139
+ end
140
+
141
+ def view_attribute(prop, model_name, *args)
142
+ args = args.extract_options!
143
+ val = args[:value] || ''
144
+ label = args[:label] || false
145
+ show = args[:show] || false
146
+ name = prop[:name]
147
+ if !show
148
+ case prop[:type]
149
+ when :title
150
+ ret = "<input class='title' type='text' value='#{val}' name='#{model_name}[#{name}]' />"
151
+ when :text
152
+ ret = "<input type='text' value='#{val}' name='#{model_name}[#{name}]' />"
153
+ when :fulltext
154
+ ret = "<textarea name='#{model_name}[#{name}]'>#{val}</textarea>"
155
+ end
156
+ ret = "<label for=''>#{name}</label><br />" + ret if label
157
+ else
158
+ case prop[:type]
159
+ when :title
160
+ ret = "<h3 class='#{model_name}-#{name}'>#{val}</h3>"
161
+ when :text
162
+ ret = "<p class='#{model_name}-#{name}'>#{val}</p>"
163
+ when :fulltext
164
+ ret = "<div class='#{model_name}-#{name}'>#{val}</div>"
165
+ end
166
+ end
167
+ ret
168
+ end
169
+ end
170
+ end