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