orange 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +154 -0
- data/lib/orange.rb +7 -0
- data/lib/orange/application.rb +125 -0
- data/lib/orange/carton.rb +114 -0
- data/lib/orange/cartons/site_carton.rb +9 -0
- data/lib/orange/core.rb +197 -0
- data/lib/orange/magick.rb +91 -0
- data/lib/orange/middleware/access_control.rb +100 -0
- data/lib/orange/middleware/base.rb +41 -0
- data/lib/orange/middleware/database.rb +22 -0
- data/lib/orange/middleware/globals.rb +18 -0
- data/lib/orange/middleware/recapture.rb +19 -0
- data/lib/orange/middleware/rerouter.rb +13 -0
- data/lib/orange/middleware/restful_router.rb +59 -0
- data/lib/orange/middleware/route_context.rb +39 -0
- data/lib/orange/middleware/route_site.rb +51 -0
- data/lib/orange/middleware/show_exceptions.rb +78 -0
- data/lib/orange/middleware/site_load.rb +33 -0
- data/lib/orange/middleware/static.rb +81 -0
- data/lib/orange/middleware/static_file.rb +32 -0
- data/lib/orange/middleware/template.rb +61 -0
- data/lib/orange/model_resource.rb +170 -0
- data/lib/orange/packet.rb +88 -0
- data/lib/orange/resource.rb +37 -0
- data/lib/orange/resources/flex_router.rb +13 -0
- data/lib/orange/resources/mapper.rb +55 -0
- data/lib/orange/resources/page_parts.rb +54 -0
- data/lib/orange/resources/parser.rb +60 -0
- data/lib/orange/routable_resource.rb +16 -0
- data/lib/orange/stack.rb +201 -0
- data/spec/application_spec.rb +146 -0
- data/spec/carton_spec.rb +5 -0
- data/spec/core_spec.rb +231 -0
- data/spec/magick_spec.rb +89 -0
- data/spec/mock/mock_app.rb +17 -0
- data/spec/mock/mock_core.rb +2 -0
- data/spec/mock/mock_middleware.rb +13 -0
- data/spec/mock/mock_mixins.rb +19 -0
- data/spec/mock/mock_pulp.rb +24 -0
- data/spec/mock/mock_resource.rb +5 -0
- data/spec/mock/mock_router.rb +10 -0
- data/spec/model_resource_spec.rb +5 -0
- data/spec/orange_spec.rb +19 -0
- data/spec/packet_spec.rb +134 -0
- data/spec/resource_spec.rb +5 -0
- data/spec/resources/flex_router_spec.rb +5 -0
- data/spec/resources/mapper_spec.rb +5 -0
- data/spec/resources/parser_spec.rb +5 -0
- data/spec/routable_resource_spec.rb +5 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/stack_spec.rb +202 -0
- data/spec/stats.rb +182 -0
- 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
|