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