crystal 0.0.1 → 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/.gitignore +3 -0
- data/lib/crystal/core.rb +25 -0
- data/lib/crystal/garbage.rb +109 -0
- data/lib/crystal/helpers.rb +213 -0
- data/lib/crystal/rack/adapter.rb +60 -0
- data/lib/crystal/rack/middleware/static_files.rb +27 -0
- data/lib/crystal/rack/rack_app.rb +94 -0
- data/lib/crystal/rack/showexceptions.rb +307 -0
- data/lib/crystal/rack/support.rb +62 -0
- data/lib/crystal/readme.txt +1 -0
- data/lib/crystal/remote.rb +5 -0
- data/lib/crystal/setting.rb +40 -0
- data/lib/crystal/support/active_support.rb +30 -0
- data/lib/crystal/template.rb +130 -0
- data/{Rakefile → rakefile} +14 -9
- data/readme.md +4 -0
- metadata +83 -12
- data/README.rdoc +0 -1
- data/spec/spec.opts +0 -3
data/.gitignore
ADDED
data/lib/crystal/core.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'crystal/support/active_support'
|
|
2
|
+
|
|
3
|
+
require 'ruby_ext'
|
|
4
|
+
|
|
5
|
+
require 'crystal/rack/adapter'
|
|
6
|
+
require 'crystal/remote'
|
|
7
|
+
require 'crystal/setting'
|
|
8
|
+
|
|
9
|
+
module Crystal
|
|
10
|
+
VERSION = '0.0.1'
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def setting
|
|
14
|
+
@setting ||= Setting.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def load_environment
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
load_environment
|
|
22
|
+
Crystal::Adapter.run
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Use the specified Rack middleware
|
|
2
|
+
def use(middleware, *args, &block)
|
|
3
|
+
@prototype = nil
|
|
4
|
+
@middleware << [middleware, args, block]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compile(path)
|
|
9
|
+
keys = []
|
|
10
|
+
if path.respond_to? :to_str
|
|
11
|
+
special_chars = %w{. + ( )}
|
|
12
|
+
pattern =
|
|
13
|
+
path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
|
14
|
+
case match
|
|
15
|
+
when "*"
|
|
16
|
+
keys << 'splat'
|
|
17
|
+
"(.*?)"
|
|
18
|
+
when *special_chars
|
|
19
|
+
Regexp.escape(match)
|
|
20
|
+
else
|
|
21
|
+
keys << $2[1..-1]
|
|
22
|
+
"([^/?&#]+)"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
[/^#{pattern}$/, keys]
|
|
26
|
+
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
|
27
|
+
[path, path.keys]
|
|
28
|
+
elsif path.respond_to? :match
|
|
29
|
+
[path, keys]
|
|
30
|
+
else
|
|
31
|
+
raise TypeError, path
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def compile!(verb, path, block)
|
|
36
|
+
method_name = "#{verb} #{path}"
|
|
37
|
+
define_method(method_name, &block)
|
|
38
|
+
unbound_method = instance_method method_name
|
|
39
|
+
remove_method method_name
|
|
40
|
+
[block.arity != 0 ?
|
|
41
|
+
proc { unbound_method.bind(self).call(*@block_params) } :
|
|
42
|
+
proc { unbound_method.bind(self).call }, *compile(path)]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Define a named template. The block must return the template source.
|
|
46
|
+
def template(name, &block)
|
|
47
|
+
filename, line = caller_locations.first
|
|
48
|
+
templates[name] = [block, filename, line.to_i]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Define a custom error handler. Optionally takes either an Exception
|
|
53
|
+
# class, or an HTTP status code to specify which errors should be
|
|
54
|
+
# handled.
|
|
55
|
+
def error(codes=Exception, &block)
|
|
56
|
+
Array(codes).each { |code| @errors[code] = block }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sugar for `error(404) { ... }`
|
|
60
|
+
def not_found(&block)
|
|
61
|
+
error 404, &block
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Find an custom error block for the key(s) specified.
|
|
66
|
+
def error_block!(*keys)
|
|
67
|
+
keys.each do |key|
|
|
68
|
+
base = self.class
|
|
69
|
+
while base.respond_to?(:errors)
|
|
70
|
+
if block = base.errors[key]
|
|
71
|
+
# found a handler, eval and return result
|
|
72
|
+
return instance_eval(&block)
|
|
73
|
+
else
|
|
74
|
+
base = base.superclass
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def dump_errors!(boom)
|
|
82
|
+
msg = ["#{boom.class} - #{boom.message}:",
|
|
83
|
+
*boom.backtrace].join("\n ")
|
|
84
|
+
@env['rack.errors'].puts(msg)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Exit the current block, halts any further processing
|
|
88
|
+
# of the request, and returns the specified response.
|
|
89
|
+
def halt(*response)
|
|
90
|
+
response = response.first if response.length == 1
|
|
91
|
+
throw :halt, response
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Pass control to the next matching route.
|
|
95
|
+
# If there are no more matching routes, Sinatra will
|
|
96
|
+
# return a 404 response.
|
|
97
|
+
def pass(&block)
|
|
98
|
+
throw :pass, block
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Forward the request to the downstream app -- middleware only.
|
|
102
|
+
def forward
|
|
103
|
+
fail "downstream app not set" unless @app.respond_to? :call
|
|
104
|
+
status, headers, body = @app.call(@request.env)
|
|
105
|
+
@response.status = status
|
|
106
|
+
@response.body = body
|
|
107
|
+
@response.headers.merge! headers
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Methods available to routes, before/after filters, and views.
|
|
2
|
+
module Helpers
|
|
3
|
+
# Lookup or register a mime type in Rack's mime registry.
|
|
4
|
+
def mime_type(type, value=nil)
|
|
5
|
+
return type if type.nil? || type.to_s.include?('/')
|
|
6
|
+
type = ".#{type}" unless type.to_s[0] == ?.
|
|
7
|
+
return Rack::Mime.mime_type(type, nil) unless value
|
|
8
|
+
Rack::Mime::MIME_TYPES[type] = value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Set or retrieve the response status code.
|
|
12
|
+
def status(value=nil)
|
|
13
|
+
response.status = value if value
|
|
14
|
+
response.status
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Set or retrieve the response body. When a block is given,
|
|
18
|
+
# evaluation is deferred until the body is read with #each.
|
|
19
|
+
def body(value=nil, &block)
|
|
20
|
+
if block_given?
|
|
21
|
+
def block.each ; yield call ; end
|
|
22
|
+
response.body = block
|
|
23
|
+
else
|
|
24
|
+
response.body = value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Halt processing and redirect to the URI provided.
|
|
29
|
+
def redirect(uri, *args)
|
|
30
|
+
status 302
|
|
31
|
+
response['Location'] = uri
|
|
32
|
+
halt(*args)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Halt processing and return the error status provided.
|
|
36
|
+
def error(code, body=nil)
|
|
37
|
+
code, body = 500, code.to_str if code.respond_to? :to_str
|
|
38
|
+
response.body = body unless body.nil?
|
|
39
|
+
halt code
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Halt processing and return a 404 Not Found.
|
|
43
|
+
def not_found(body=nil)
|
|
44
|
+
error 404, body
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Set multiple response headers with Hash.
|
|
48
|
+
def headers(hash=nil)
|
|
49
|
+
response.headers.merge! hash if hash
|
|
50
|
+
response.headers
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Access the underlying Rack session.
|
|
54
|
+
def session
|
|
55
|
+
env['rack.session'] ||= {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Look up a media type by file extension in Rack's mime registry.
|
|
59
|
+
def mime_type(type)
|
|
60
|
+
Base.mime_type(type)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Set the Content-Type of the response body given a media type or file
|
|
64
|
+
# extension.
|
|
65
|
+
def content_type(type, params={})
|
|
66
|
+
mime_type = mime_type(type)
|
|
67
|
+
fail "Unknown media type: %p" % type if mime_type.nil?
|
|
68
|
+
if params.any?
|
|
69
|
+
params = params.collect { |kv| "%s=%s" % kv }.join(', ')
|
|
70
|
+
response['Content-Type'] = [mime_type, params].join(";")
|
|
71
|
+
else
|
|
72
|
+
response['Content-Type'] = mime_type
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set the Content-Disposition to "attachment" with the specified filename,
|
|
77
|
+
# instructing the user agents to prompt to save.
|
|
78
|
+
def attachment(filename=nil)
|
|
79
|
+
response['Content-Disposition'] = 'attachment'
|
|
80
|
+
if filename
|
|
81
|
+
params = '; filename="%s"' % File.basename(filename)
|
|
82
|
+
response['Content-Disposition'] << params
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Use the contents of the file at +path+ as the response body.
|
|
87
|
+
def send_file(path, opts={})
|
|
88
|
+
stat = File.stat(path)
|
|
89
|
+
last_modified stat.mtime
|
|
90
|
+
|
|
91
|
+
content_type mime_type(opts[:type]) ||
|
|
92
|
+
mime_type(File.extname(path)) ||
|
|
93
|
+
response['Content-Type'] ||
|
|
94
|
+
'application/octet-stream'
|
|
95
|
+
|
|
96
|
+
response['Content-Length'] ||= (opts[:length] || stat.size).to_s
|
|
97
|
+
|
|
98
|
+
if opts[:disposition] == 'attachment' || opts[:filename]
|
|
99
|
+
attachment opts[:filename] || path
|
|
100
|
+
elsif opts[:disposition] == 'inline'
|
|
101
|
+
response['Content-Disposition'] = 'inline'
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
halt StaticFile.open(path, 'rb')
|
|
105
|
+
rescue Errno::ENOENT
|
|
106
|
+
not_found
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Rack response body used to deliver static files. The file contents are
|
|
110
|
+
# generated iteratively in 8K chunks.
|
|
111
|
+
class StaticFile < ::File #:nodoc:
|
|
112
|
+
alias_method :to_path, :path
|
|
113
|
+
def each
|
|
114
|
+
rewind
|
|
115
|
+
while buf = read(8192)
|
|
116
|
+
yield buf
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
|
122
|
+
# Any number of non-value directives (:public, :private, :no_cache,
|
|
123
|
+
# :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
|
|
124
|
+
# a Hash of value directives (:max_age, :min_stale, :s_max_age).
|
|
125
|
+
#
|
|
126
|
+
# cache_control :public, :must_revalidate, :max_age => 60
|
|
127
|
+
# => Cache-Control: public, must-revalidate, max-age=60
|
|
128
|
+
#
|
|
129
|
+
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
|
130
|
+
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
|
131
|
+
def cache_control(*values)
|
|
132
|
+
if values.last.kind_of?(Hash)
|
|
133
|
+
hash = values.pop
|
|
134
|
+
hash.reject! { |k,v| v == false }
|
|
135
|
+
hash.reject! { |k,v| values << k if v == true }
|
|
136
|
+
else
|
|
137
|
+
hash = {}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
values = values.map { |value| value.to_s.tr('_','-') }
|
|
141
|
+
hash.each { |k,v| values << [k.to_s.tr('_', '-'), v].join('=') }
|
|
142
|
+
|
|
143
|
+
response['Cache-Control'] = values.join(', ') if values.any?
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Set the Expires header and Cache-Control/max-age directive. Amount
|
|
147
|
+
# can be an integer number of seconds in the future or a Time object
|
|
148
|
+
# indicating when the response should be considered "stale". The remaining
|
|
149
|
+
# "values" arguments are passed to the #cache_control helper:
|
|
150
|
+
#
|
|
151
|
+
# expires 500, :public, :must_revalidate
|
|
152
|
+
# => Cache-Control: public, must-revalidate, max-age=60
|
|
153
|
+
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
|
154
|
+
#
|
|
155
|
+
def expires(amount, *values)
|
|
156
|
+
values << {} unless values.last.kind_of?(Hash)
|
|
157
|
+
|
|
158
|
+
if amount.respond_to?(:to_time)
|
|
159
|
+
max_age = amount.to_time - Time.now
|
|
160
|
+
time = amount.to_time
|
|
161
|
+
else
|
|
162
|
+
max_age = amount
|
|
163
|
+
time = Time.now + amount
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
values.last.merge!(:max_age => max_age)
|
|
167
|
+
cache_control(*values)
|
|
168
|
+
|
|
169
|
+
response['Expires'] = time.httpdate
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
|
|
173
|
+
# and halt if conditional GET matches. The +time+ argument is a Time,
|
|
174
|
+
# DateTime, or other object that responds to +to_time+.
|
|
175
|
+
#
|
|
176
|
+
# When the current request includes an 'If-Modified-Since' header that
|
|
177
|
+
# matches the time specified, execution is immediately halted with a
|
|
178
|
+
# '304 Not Modified' response.
|
|
179
|
+
def last_modified(time)
|
|
180
|
+
return unless time
|
|
181
|
+
time = time.to_time if time.respond_to?(:to_time)
|
|
182
|
+
time = time.httpdate if time.respond_to?(:httpdate)
|
|
183
|
+
response['Last-Modified'] = time
|
|
184
|
+
halt 304 if time == request.env['HTTP_IF_MODIFIED_SINCE']
|
|
185
|
+
time
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
|
189
|
+
# GET matches. The +value+ argument is an identifier that uniquely
|
|
190
|
+
# identifies the current version of the resource. The +kind+ argument
|
|
191
|
+
# indicates whether the etag should be used as a :strong (default) or :weak
|
|
192
|
+
# cache validator.
|
|
193
|
+
#
|
|
194
|
+
# When the current request includes an 'If-None-Match' header with a
|
|
195
|
+
# matching etag, execution is immediately halted. If the request method is
|
|
196
|
+
# GET or HEAD, a '304 Not Modified' response is sent.
|
|
197
|
+
def etag(value, kind=:strong)
|
|
198
|
+
raise TypeError, ":strong or :weak expected" if ![:strong,:weak].include?(kind)
|
|
199
|
+
value = '"%s"' % value
|
|
200
|
+
value = 'W/' + value if kind == :weak
|
|
201
|
+
response['ETag'] = value
|
|
202
|
+
|
|
203
|
+
# Conditional GET check
|
|
204
|
+
if etags = env['HTTP_IF_NONE_MATCH']
|
|
205
|
+
etags = etags.split(/\s*,\s*/)
|
|
206
|
+
halt 304 if etags.include?(value) || etags.include?('*')
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
## Sugar for redirect (example: redirect back)
|
|
211
|
+
def back ; request.referer ; end
|
|
212
|
+
|
|
213
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'crystal/rack/support'
|
|
2
|
+
require 'crystal/rack/rack_app'
|
|
3
|
+
|
|
4
|
+
module Crystal
|
|
5
|
+
class Adapter
|
|
6
|
+
SERVERS = %w{mongrel thin webrick}
|
|
7
|
+
|
|
8
|
+
class Callback
|
|
9
|
+
def initialize app; end
|
|
10
|
+
def self.call env; Crystal::Adapter.call env end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
app = build_app
|
|
17
|
+
handler = detect_rack_handler
|
|
18
|
+
handler_name = handler.name.gsub(/.*::/, '')
|
|
19
|
+
puts " Crystal #{Crystal::VERSION} started on #{Crystal.setting.port} in #{Crystal.setting.environment} mode" unless handler_name =~/cgi/i
|
|
20
|
+
handler.run app, :Host => Crystal.setting.host, :Port => Crystal.setting.port do |server|
|
|
21
|
+
[:INT, :TERM].each {|sig| trap(sig){quit!(server, handler_name)}}
|
|
22
|
+
end
|
|
23
|
+
rescue Errno::EADDRINUSE => e
|
|
24
|
+
puts " #{port} is taken!"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def quit!(server, handler_name)
|
|
28
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
|
29
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
|
30
|
+
puts "\n Crystal stopped" unless handler_name =~/cgi/i
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
def build_app
|
|
35
|
+
builder = Rack::Builder.new
|
|
36
|
+
builder.use Rack::Session::Cookie if Crystal.setting.session?
|
|
37
|
+
# builder.use Rack::CommonLogger
|
|
38
|
+
builder.use Rack::MethodOverride
|
|
39
|
+
# builder.use ShowExceptions if Crystal.setting.show_exceptions?
|
|
40
|
+
|
|
41
|
+
# TODO add middleware here
|
|
42
|
+
|
|
43
|
+
builder.run Crystal::RackApp.new
|
|
44
|
+
builder.to_app
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def detect_rack_handler
|
|
48
|
+
SERVERS.each do |server_name|
|
|
49
|
+
begin
|
|
50
|
+
return Rack::Handler.get(server_name.downcase)
|
|
51
|
+
rescue LoadError
|
|
52
|
+
rescue NameError
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
fail " Server handler (#{servers.join(',')}) not found."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Attempt to serve static files from public directory. Throws :halt when
|
|
2
|
+
# a matching file is found, returns nil otherwise.
|
|
3
|
+
def static!
|
|
4
|
+
return if (public_dir = settings.public).nil?
|
|
5
|
+
public_dir = File.expand_path(public_dir)
|
|
6
|
+
|
|
7
|
+
path = File.expand_path(public_dir + unescape(request.path_info))
|
|
8
|
+
return if path[0, public_dir.length] != public_dir
|
|
9
|
+
return unless File.file?(path)
|
|
10
|
+
|
|
11
|
+
env['sinatra.static_file'] = path
|
|
12
|
+
send_file path, :disposition => nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Dispatch a request with error handling.
|
|
17
|
+
def dispatch!
|
|
18
|
+
static! if settings.static? && (request.get? || request.head?)
|
|
19
|
+
filter! :before
|
|
20
|
+
route!
|
|
21
|
+
rescue NotFound => boom
|
|
22
|
+
handle_not_found!(boom)
|
|
23
|
+
rescue ::Exception => boom
|
|
24
|
+
handle_exception!(boom)
|
|
25
|
+
ensure
|
|
26
|
+
filter! :after unless env['sinatra.static_file']
|
|
27
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Crystal
|
|
2
|
+
class RackApp
|
|
3
|
+
|
|
4
|
+
def initialize
|
|
5
|
+
@monitor = Monitor.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call env
|
|
9
|
+
@monitor.synchronize do
|
|
10
|
+
p :here
|
|
11
|
+
[200, {"Content-Type" => "text/plain"}, ["Hello world!"]]
|
|
12
|
+
|
|
13
|
+
# @env = env
|
|
14
|
+
# @request = Request.new(env)
|
|
15
|
+
# @response = Response.new
|
|
16
|
+
# @params = indifferent_params(@request.params)
|
|
17
|
+
# template_cache.clear if settings.reload_templates
|
|
18
|
+
#
|
|
19
|
+
# invoke { dispatch! }
|
|
20
|
+
# invoke { error_block!(response.status) }
|
|
21
|
+
#
|
|
22
|
+
# status, header, body = @response.finish
|
|
23
|
+
#
|
|
24
|
+
# # Never produce a body on HEAD requests. Do retain the Content-Length
|
|
25
|
+
# # unless it's "0", in which case we assume it was calculated erroneously
|
|
26
|
+
# # for a manual HEAD response and remove it entirely.
|
|
27
|
+
# if @env['REQUEST_METHOD'] == 'HEAD'
|
|
28
|
+
# body = []
|
|
29
|
+
# header.delete('Content-Length') if header['Content-Length'] == '0'
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# [status, header, body]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# def handle_not_found!(boom)
|
|
37
|
+
# @env['sinatra.error'] = boom
|
|
38
|
+
# @response.status = 404
|
|
39
|
+
# @response.headers['X-Cascade'] = 'pass'
|
|
40
|
+
# @response.body = ['<h1>Not Found</h1>']
|
|
41
|
+
# error_block! boom.class, NotFound
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# def handle_exception!(boom)
|
|
45
|
+
# @env['sinatra.error'] = boom
|
|
46
|
+
#
|
|
47
|
+
# dump_errors!(boom) if settings.dump_errors?
|
|
48
|
+
# raise boom if settings.show_exceptions?
|
|
49
|
+
#
|
|
50
|
+
# @response.status = 500
|
|
51
|
+
# if res = error_block!(boom.class)
|
|
52
|
+
# res
|
|
53
|
+
# elsif settings.raise_errors?
|
|
54
|
+
# raise boom
|
|
55
|
+
# else
|
|
56
|
+
# error_block!(Exception)
|
|
57
|
+
# end
|
|
58
|
+
# end
|
|
59
|
+
|
|
60
|
+
# Run the block with 'throw :halt' support and apply result to the response.
|
|
61
|
+
# def invoke(&block)
|
|
62
|
+
# res = catch(:halt) { instance_eval(&block) }
|
|
63
|
+
# return if res.nil?
|
|
64
|
+
#
|
|
65
|
+
# case
|
|
66
|
+
# when res.respond_to?(:to_str)
|
|
67
|
+
# @response.body = [res]
|
|
68
|
+
# when res.respond_to?(:to_ary)
|
|
69
|
+
# res = res.to_ary
|
|
70
|
+
# if Fixnum === res.first
|
|
71
|
+
# if res.length == 3
|
|
72
|
+
# @response.status, headers, body = res
|
|
73
|
+
# @response.body = body if body
|
|
74
|
+
# headers.each { |k, v| @response.headers[k] = v } if headers
|
|
75
|
+
# elsif res.length == 2
|
|
76
|
+
# @response.status = res.first
|
|
77
|
+
# @response.body = res.last
|
|
78
|
+
# else
|
|
79
|
+
# raise TypeError, "#{res.inspect} not supported"
|
|
80
|
+
# end
|
|
81
|
+
# else
|
|
82
|
+
# @response.body = res
|
|
83
|
+
# end
|
|
84
|
+
# when res.respond_to?(:each)
|
|
85
|
+
# @response.body = res
|
|
86
|
+
# when (100...599) === res
|
|
87
|
+
# @response.status = res
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# res
|
|
91
|
+
# end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
require 'rack/showexceptions'
|
|
2
|
+
|
|
3
|
+
module Sinatra
|
|
4
|
+
class ShowExceptions < Rack::ShowExceptions
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
@template = ERB.new(TEMPLATE)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def frame_class(frame)
|
|
11
|
+
if frame.filename =~ /lib\/sinatra.*\.rb/
|
|
12
|
+
"framework"
|
|
13
|
+
elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) ||
|
|
14
|
+
frame.filename =~ /\/bin\/(\w+)$/
|
|
15
|
+
"system"
|
|
16
|
+
else
|
|
17
|
+
"app"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
TEMPLATE = <<HTML
|
|
22
|
+
<!DOCTYPE html>
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
26
|
+
<title><%=h exception.class %> at <%=h path %></title>
|
|
27
|
+
|
|
28
|
+
<script type="text/javascript">
|
|
29
|
+
//<!--
|
|
30
|
+
function toggle(id) {
|
|
31
|
+
var pre = document.getElementById("pre-" + id);
|
|
32
|
+
var post = document.getElementById("post-" + id);
|
|
33
|
+
var context = document.getElementById("context-" + id);
|
|
34
|
+
|
|
35
|
+
if (pre.style.display == 'block') {
|
|
36
|
+
pre.style.display = 'none';
|
|
37
|
+
post.style.display = 'none';
|
|
38
|
+
context.style.background = "none";
|
|
39
|
+
} else {
|
|
40
|
+
pre.style.display = 'block';
|
|
41
|
+
post.style.display = 'block';
|
|
42
|
+
context.style.background = "#fffed9";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toggleBacktrace(){
|
|
47
|
+
var bt = document.getElementById("backtrace");
|
|
48
|
+
var toggler = document.getElementById("expando");
|
|
49
|
+
|
|
50
|
+
if (bt.className == 'condensed') {
|
|
51
|
+
bt.className = 'expanded';
|
|
52
|
+
toggler.innerHTML = "(condense)";
|
|
53
|
+
} else {
|
|
54
|
+
bt.className = 'condensed';
|
|
55
|
+
toggler.innerHTML = "(expand)";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//-->
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style type="text/css" media="screen">
|
|
62
|
+
* {margin: 0; padding: 0; border: 0; outline: 0;}
|
|
63
|
+
div.clear {clear: both;}
|
|
64
|
+
body {background: #EEEEEE; margin: 0; padding: 0;
|
|
65
|
+
font-family: 'Lucida Grande', 'Lucida Sans Unicode',
|
|
66
|
+
'Garuda';}
|
|
67
|
+
code {font-family: 'Lucida Console', monospace;
|
|
68
|
+
font-size: 12px;}
|
|
69
|
+
li {height: 18px;}
|
|
70
|
+
ul {list-style: none; margin: 0; padding: 0;}
|
|
71
|
+
ol:hover {cursor: pointer;}
|
|
72
|
+
ol li {white-space: pre;}
|
|
73
|
+
#explanation {font-size: 12px; color: #666666;
|
|
74
|
+
margin: 20px 0 0 100px;}
|
|
75
|
+
/* WRAP */
|
|
76
|
+
#wrap {width: 1000px; background: #FFFFFF; margin: 0 auto;
|
|
77
|
+
padding: 30px 50px 20px 50px;
|
|
78
|
+
border-left: 1px solid #DDDDDD;
|
|
79
|
+
border-right: 1px solid #DDDDDD;}
|
|
80
|
+
/* HEADER */
|
|
81
|
+
#header {margin: 0 auto 25px auto;}
|
|
82
|
+
#header img {float: left;}
|
|
83
|
+
#header #summary {float: left; margin: 12px 0 0 20px; width:660px;
|
|
84
|
+
font-family: 'Lucida Grande', 'Lucida Sans Unicode';}
|
|
85
|
+
h1 {margin: 0; font-size: 36px; color: #981919;}
|
|
86
|
+
h2 {margin: 0; font-size: 22px; color: #333333;}
|
|
87
|
+
#header ul {margin: 0; font-size: 12px; color: #666666;}
|
|
88
|
+
#header ul li strong{color: #444444;}
|
|
89
|
+
#header ul li {display: inline; padding: 0 10px;}
|
|
90
|
+
#header ul li.first {padding-left: 0;}
|
|
91
|
+
#header ul li.last {border: 0; padding-right: 0;}
|
|
92
|
+
/* BODY */
|
|
93
|
+
#backtrace,
|
|
94
|
+
#get,
|
|
95
|
+
#post,
|
|
96
|
+
#cookies,
|
|
97
|
+
#rack {width: 980px; margin: 0 auto 10px auto;}
|
|
98
|
+
p#nav {float: right; font-size: 14px;}
|
|
99
|
+
/* BACKTRACE */
|
|
100
|
+
a#expando {float: left; padding-left: 5px; color: #666666;
|
|
101
|
+
font-size: 14px; text-decoration: none; cursor: pointer;}
|
|
102
|
+
a#expando:hover {text-decoration: underline;}
|
|
103
|
+
h3 {float: left; width: 100px; margin-bottom: 10px;
|
|
104
|
+
color: #981919; font-size: 14px; font-weight: bold;}
|
|
105
|
+
#nav a {color: #666666; text-decoration: none; padding: 0 5px;}
|
|
106
|
+
#backtrace li.frame-info {background: #f7f7f7; padding-left: 10px;
|
|
107
|
+
font-size: 12px; color: #333333;}
|
|
108
|
+
#backtrace ul {list-style-position: outside; border: 1px solid #E9E9E9;
|
|
109
|
+
border-bottom: 0;}
|
|
110
|
+
#backtrace ol {width: 920px; margin-left: 50px;
|
|
111
|
+
font: 10px 'Lucida Console', monospace; color: #666666;}
|
|
112
|
+
#backtrace ol li {border: 0; border-left: 1px solid #E9E9E9;
|
|
113
|
+
padding: 2px 0;}
|
|
114
|
+
#backtrace ol code {font-size: 10px; color: #555555; padding-left: 5px;}
|
|
115
|
+
#backtrace-ul li {border-bottom: 1px solid #E9E9E9; height: auto;
|
|
116
|
+
padding: 3px 0;}
|
|
117
|
+
#backtrace-ul .code {padding: 6px 0 4px 0;}
|
|
118
|
+
#backtrace.condensed .system,
|
|
119
|
+
#backtrace.condensed .framework {display:none;}
|
|
120
|
+
/* REQUEST DATA */
|
|
121
|
+
p.no-data {padding-top: 2px; font-size: 12px; color: #666666;}
|
|
122
|
+
table.req {width: 980px; text-align: left; font-size: 12px;
|
|
123
|
+
color: #666666; padding: 0; border-spacing: 0;
|
|
124
|
+
border: 1px solid #EEEEEE; border-bottom: 0;
|
|
125
|
+
border-left: 0;
|
|
126
|
+
clear:both}
|
|
127
|
+
table.req tr th {padding: 2px 10px; font-weight: bold;
|
|
128
|
+
background: #F7F7F7; border-bottom: 1px solid #EEEEEE;
|
|
129
|
+
border-left: 1px solid #EEEEEE;}
|
|
130
|
+
table.req tr td {padding: 2px 20px 2px 10px;
|
|
131
|
+
border-bottom: 1px solid #EEEEEE;
|
|
132
|
+
border-left: 1px solid #EEEEEE;}
|
|
133
|
+
/* HIDE PRE/POST CODE AT START */
|
|
134
|
+
.pre-context,
|
|
135
|
+
.post-context {display: none;}
|
|
136
|
+
|
|
137
|
+
table td.code {width:750px}
|
|
138
|
+
table td.code div {width:750px;overflow:hidden}
|
|
139
|
+
</style>
|
|
140
|
+
</head>
|
|
141
|
+
<body>
|
|
142
|
+
<div id="wrap">
|
|
143
|
+
<div id="header">
|
|
144
|
+
<img src="/__sinatra__/500.png" alt="application error" height="161" width="313" />
|
|
145
|
+
<div id="summary">
|
|
146
|
+
<h1><strong><%=h exception.class %></strong> at <strong><%=h path %>
|
|
147
|
+
</strong></h1>
|
|
148
|
+
<h2><%=h exception.message %></h2>
|
|
149
|
+
<ul>
|
|
150
|
+
<li class="first"><strong>file:</strong> <code>
|
|
151
|
+
<%=h frames.first.filename.split("/").last %></code></li>
|
|
152
|
+
<li><strong>location:</strong> <code><%=h frames.first.function %>
|
|
153
|
+
</code></li>
|
|
154
|
+
<li class="last"><strong>line:
|
|
155
|
+
</strong> <%=h frames.first.lineno %></li>
|
|
156
|
+
</ul>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="clear"></div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div id="backtrace" class='condensed'>
|
|
162
|
+
<h3>BACKTRACE</h3>
|
|
163
|
+
<p><a href="#" id="expando"
|
|
164
|
+
onclick="toggleBacktrace(); return false">(expand)</a></p>
|
|
165
|
+
<p id="nav"><strong>JUMP TO:</strong>
|
|
166
|
+
<a href="#get-info">GET</a>
|
|
167
|
+
<a href="#post-info">POST</a>
|
|
168
|
+
<a href="#cookie-info">COOKIES</a>
|
|
169
|
+
<a href="#env-info">ENV</a>
|
|
170
|
+
</p>
|
|
171
|
+
<div class="clear"></div>
|
|
172
|
+
|
|
173
|
+
<ul id="backtrace-ul">
|
|
174
|
+
|
|
175
|
+
<% id = 1 %>
|
|
176
|
+
<% frames.each do |frame| %>
|
|
177
|
+
<% if frame.context_line && frame.context_line != "#" %>
|
|
178
|
+
|
|
179
|
+
<li class="frame-info <%= frame_class(frame) %>">
|
|
180
|
+
<code><%=h frame.filename %></code> in
|
|
181
|
+
<code><strong><%=h frame.function %></strong></code>
|
|
182
|
+
</li>
|
|
183
|
+
|
|
184
|
+
<li class="code <%= frame_class(frame) %>">
|
|
185
|
+
<% if frame.pre_context %>
|
|
186
|
+
<ol start="<%=h frame.pre_context_lineno + 1 %>"
|
|
187
|
+
class="pre-context" id="pre-<%= id %>"
|
|
188
|
+
onclick="toggle(<%= id %>);">
|
|
189
|
+
<% frame.pre_context.each do |line| %>
|
|
190
|
+
<li class="pre-context-line"><code><%=h line %></code></li>
|
|
191
|
+
<% end %>
|
|
192
|
+
</ol>
|
|
193
|
+
<% end %>
|
|
194
|
+
|
|
195
|
+
<ol start="<%= frame.lineno %>" class="context" id="<%= id %>"
|
|
196
|
+
onclick="toggle(<%= id %>);">
|
|
197
|
+
<li class="context-line" id="context-<%= id %>"><code><%=
|
|
198
|
+
h frame.context_line %></code></li>
|
|
199
|
+
</ol>
|
|
200
|
+
|
|
201
|
+
<% if frame.post_context %>
|
|
202
|
+
<ol start="<%=h frame.lineno + 1 %>" class="post-context"
|
|
203
|
+
id="post-<%= id %>" onclick="toggle(<%= id %>);">
|
|
204
|
+
<% frame.post_context.each do |line| %>
|
|
205
|
+
<li class="post-context-line"><code><%=h line %></code></li>
|
|
206
|
+
<% end %>
|
|
207
|
+
</ol>
|
|
208
|
+
<% end %>
|
|
209
|
+
<div class="clear"></div>
|
|
210
|
+
</li>
|
|
211
|
+
|
|
212
|
+
<% end %>
|
|
213
|
+
|
|
214
|
+
<% id += 1 %>
|
|
215
|
+
<% end %>
|
|
216
|
+
|
|
217
|
+
</ul>
|
|
218
|
+
</div> <!-- /BACKTRACE -->
|
|
219
|
+
|
|
220
|
+
<div id="get">
|
|
221
|
+
<h3 id="get-info">GET</h3>
|
|
222
|
+
<% unless req.GET.empty? %>
|
|
223
|
+
<table class="req">
|
|
224
|
+
<tr>
|
|
225
|
+
<th>Variable</th>
|
|
226
|
+
<th>Value</th>
|
|
227
|
+
</tr>
|
|
228
|
+
<% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
|
229
|
+
<tr>
|
|
230
|
+
<td><%=h key %></td>
|
|
231
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
|
232
|
+
</tr>
|
|
233
|
+
<% } %>
|
|
234
|
+
</table>
|
|
235
|
+
<% else %>
|
|
236
|
+
<p class="no-data">No GET data.</p>
|
|
237
|
+
<% end %>
|
|
238
|
+
<div class="clear"></div>
|
|
239
|
+
</div> <!-- /GET -->
|
|
240
|
+
|
|
241
|
+
<div id="post">
|
|
242
|
+
<h3 id="post-info">POST</h3>
|
|
243
|
+
<% unless req.POST.empty? %>
|
|
244
|
+
<table class="req">
|
|
245
|
+
<tr>
|
|
246
|
+
<th>Variable</th>
|
|
247
|
+
<th>Value</th>
|
|
248
|
+
</tr>
|
|
249
|
+
<% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
|
250
|
+
<tr>
|
|
251
|
+
<td><%=h key %></td>
|
|
252
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
|
253
|
+
</tr>
|
|
254
|
+
<% } %>
|
|
255
|
+
</table>
|
|
256
|
+
<% else %>
|
|
257
|
+
<p class="no-data">No POST data.</p>
|
|
258
|
+
<% end %>
|
|
259
|
+
<div class="clear"></div>
|
|
260
|
+
</div> <!-- /POST -->
|
|
261
|
+
|
|
262
|
+
<div id="cookies">
|
|
263
|
+
<h3 id="cookie-info">COOKIES</h3>
|
|
264
|
+
<% unless req.cookies.empty? %>
|
|
265
|
+
<table class="req">
|
|
266
|
+
<tr>
|
|
267
|
+
<th>Variable</th>
|
|
268
|
+
<th>Value</th>
|
|
269
|
+
</tr>
|
|
270
|
+
<% req.cookies.each { |key, val| %>
|
|
271
|
+
<tr>
|
|
272
|
+
<td><%=h key %></td>
|
|
273
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
|
274
|
+
</tr>
|
|
275
|
+
<% } %>
|
|
276
|
+
</table>
|
|
277
|
+
<% else %>
|
|
278
|
+
<p class="no-data">No cookie data.</p>
|
|
279
|
+
<% end %>
|
|
280
|
+
<div class="clear"></div>
|
|
281
|
+
</div> <!-- /COOKIES -->
|
|
282
|
+
|
|
283
|
+
<div id="rack">
|
|
284
|
+
<h3 id="env-info">Rack ENV</h3>
|
|
285
|
+
<table class="req">
|
|
286
|
+
<tr>
|
|
287
|
+
<th>Variable</th>
|
|
288
|
+
<th>Value</th>
|
|
289
|
+
</tr>
|
|
290
|
+
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
|
291
|
+
<tr>
|
|
292
|
+
<td><%=h key %></td>
|
|
293
|
+
<td class="code"><div><%=h val %></div></td>
|
|
294
|
+
</tr>
|
|
295
|
+
<% } %>
|
|
296
|
+
</table>
|
|
297
|
+
<div class="clear"></div>
|
|
298
|
+
</div> <!-- /RACK ENV -->
|
|
299
|
+
|
|
300
|
+
<p id="explanation">You're seeing this error because you have
|
|
301
|
+
enabled the <code>show_exceptions</code> setting.</p>
|
|
302
|
+
</div> <!-- /WRAP -->
|
|
303
|
+
</body>
|
|
304
|
+
</html>
|
|
305
|
+
HTML
|
|
306
|
+
end
|
|
307
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
require 'time'
|
|
3
|
+
require 'uri'
|
|
4
|
+
# require 'addressable/uri'
|
|
5
|
+
require 'rack'
|
|
6
|
+
require 'rack/builder'
|
|
7
|
+
|
|
8
|
+
module Crystal
|
|
9
|
+
# The request object. See Rack::Request for more info:
|
|
10
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Request.html
|
|
11
|
+
class Request < Rack::Request
|
|
12
|
+
# Returns an array of acceptable media types for the response
|
|
13
|
+
def accept
|
|
14
|
+
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.split(';')[0].strip }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def secure?
|
|
18
|
+
(@env['HTTP_X_FORWARDED_PROTO'] || @env['rack.url_scheme']) == 'https'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Override Rack < 1.1's Request#params implementation (see lh #72 for
|
|
22
|
+
# more info) and add a Request#user_agent method.
|
|
23
|
+
# XXX remove when we require rack > 1.1
|
|
24
|
+
if Rack.release < '1.1'
|
|
25
|
+
def params
|
|
26
|
+
self.GET.update(self.POST)
|
|
27
|
+
rescue EOFError, Errno::ESPIPE
|
|
28
|
+
self.GET
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def user_agent
|
|
32
|
+
@env['HTTP_USER_AGENT']
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The response object. See Rack::Response and Rack::ResponseHelpers for
|
|
38
|
+
# more info:
|
|
39
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Response.html
|
|
40
|
+
# http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html
|
|
41
|
+
class Response < Rack::Response
|
|
42
|
+
def finish
|
|
43
|
+
@body = block if block_given?
|
|
44
|
+
if [204, 304].include?(status.to_i)
|
|
45
|
+
header.delete "Content-Type"
|
|
46
|
+
[status.to_i, header.to_hash, []]
|
|
47
|
+
else
|
|
48
|
+
body = @body || []
|
|
49
|
+
body = [body] if body.respond_to? :to_str
|
|
50
|
+
if body.respond_to?(:to_ary)
|
|
51
|
+
header["Content-Length"] = body.to_ary.
|
|
52
|
+
inject(0) { |len, part| len + Rack::Utils.bytesize(part) }.to_s
|
|
53
|
+
end
|
|
54
|
+
[status.to_i, header.to_hash, body]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class NotFound < NameError #:nodoc:
|
|
60
|
+
def code ; 404 ; end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- Use Rack::Utils for escape
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Crystal
|
|
2
|
+
class Setting
|
|
3
|
+
attr_accessor :host, :port, :environment, :server, :session, :static
|
|
4
|
+
%w{session show_exceptions static}.each do |m|
|
|
5
|
+
iv = "@#{m}"
|
|
6
|
+
define_method("#{m}?"){!!instance_variable_get(iv)}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
{
|
|
11
|
+
:host => '0.0.0.0',
|
|
12
|
+
:port => 4000,
|
|
13
|
+
:environment => 'development',
|
|
14
|
+
:server => 'Mongrel',
|
|
15
|
+
:session => true,
|
|
16
|
+
:static => true
|
|
17
|
+
}.each{|k, v| self.send "#{k}=", v if self.send(k).nil?}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def development?; environment == 'development' end
|
|
21
|
+
def production?; environment == 'production' end
|
|
22
|
+
def test?; environment == 'test' end
|
|
23
|
+
|
|
24
|
+
def apply_command_line_arguments!
|
|
25
|
+
require 'optparse'
|
|
26
|
+
|
|
27
|
+
case ARGV.first
|
|
28
|
+
when "p" then self.environment = "production"
|
|
29
|
+
when "d" then self.environment = "development"
|
|
30
|
+
when "t" then self.environment = "test"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
OptionParser.new{|op|
|
|
34
|
+
op.on('-e env'){|val| self.environment = val}
|
|
35
|
+
op.on('-s server'){|val| self.server = val}
|
|
36
|
+
op.on('-p port'){|val| self.port = val.to_i}
|
|
37
|
+
}.parse!(ARGV.dup)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
gem 'activeresource', '= 2.3.5'
|
|
2
|
+
|
|
3
|
+
# autoload :BacktraceCleaner, 'active_support/backtrace_cleaner'
|
|
4
|
+
autoload :Base64, 'active_support/base64'
|
|
5
|
+
autoload :BasicObject, 'active_support/basic_object'
|
|
6
|
+
# autoload :BufferedLogger, 'active_support/buffered_logger'
|
|
7
|
+
# autoload :Cache, 'active_support/cache'
|
|
8
|
+
# autoload :Callbacks, 'active_support/callbacks'
|
|
9
|
+
# autoload :Deprecation, 'active_support/deprecation'
|
|
10
|
+
# autoload :Duration, 'active_support/duration'
|
|
11
|
+
# autoload :Gzip, 'active_support/gzip'
|
|
12
|
+
autoload :Inflector, 'active_support/inflector'
|
|
13
|
+
# autoload :Memoizable, 'active_support/memoizable'
|
|
14
|
+
# autoload :MessageEncryptor, 'active_support/message_encryptor'
|
|
15
|
+
# autoload :MessageVerifier, 'active_support/message_verifier'
|
|
16
|
+
# autoload :Multibyte, 'active_support/multibyte'
|
|
17
|
+
# autoload :OptionMerger, 'active_support/option_merger'
|
|
18
|
+
# autoload :OrderedHash, 'active_support/ordered_hash'
|
|
19
|
+
# autoload :OrderedOptions, 'active_support/ordered_options'
|
|
20
|
+
# autoload :Rescuable, 'active_support/rescuable'
|
|
21
|
+
# autoload :SecureRandom, 'active_support/secure_random'
|
|
22
|
+
# autoload :StringInquirer, 'active_support/string_inquirer'
|
|
23
|
+
autoload :TimeWithZone, 'active_support/time_with_zone'
|
|
24
|
+
autoload :TimeZone, 'active_support/values/time_zone'
|
|
25
|
+
# autoload :XmlMini, 'active_support/xml_mini'
|
|
26
|
+
|
|
27
|
+
# require 'active_support/vendor'
|
|
28
|
+
require 'active_support/core_ext'
|
|
29
|
+
# require 'active_support/dependencies'
|
|
30
|
+
require 'active_support/json'
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
attr_reader :template_cache
|
|
2
|
+
|
|
3
|
+
def initialize(app=nil)
|
|
4
|
+
@app = app
|
|
5
|
+
@template_cache = Tilt::Cache.new
|
|
6
|
+
yield self if block_given?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Template rendering methods. Each method takes the name of a template
|
|
34
|
+
# to render as a Symbol and returns a String with the rendered output,
|
|
35
|
+
# as well as an optional hash with additional options.
|
|
36
|
+
#
|
|
37
|
+
# `template` is either the name or path of the template as symbol
|
|
38
|
+
# (Use `:'subdir/myview'` for views in subdirectories), or a string
|
|
39
|
+
# that will be rendered.
|
|
40
|
+
#
|
|
41
|
+
# Possible options are:
|
|
42
|
+
# :layout If set to false, no layout is rendered, otherwise
|
|
43
|
+
# the specified layout is used (Ignored for `sass` and `less`)
|
|
44
|
+
# :locals A hash with local variables that should be available
|
|
45
|
+
# in the template
|
|
46
|
+
module Templates
|
|
47
|
+
include Tilt::CompileSite
|
|
48
|
+
|
|
49
|
+
def erb(template, options={}, locals={})
|
|
50
|
+
options[:outvar] = '@_out_buf'
|
|
51
|
+
render :erb, template, options, locals
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def erubis(template, options={}, locals={})
|
|
55
|
+
options[:outvar] = '@_out_buf'
|
|
56
|
+
render :erubis, template, options, locals
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def haml(template, options={}, locals={})
|
|
60
|
+
render :haml, template, options, locals
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def sass(template, options={}, locals={})
|
|
64
|
+
options[:layout] = false
|
|
65
|
+
render :sass, template, options, locals
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def less(template, options={}, locals={})
|
|
69
|
+
options[:layout] = false
|
|
70
|
+
render :less, template, options, locals
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def builder(template=nil, options={}, locals={}, &block)
|
|
74
|
+
options, template = template, nil if template.is_a?(Hash)
|
|
75
|
+
template = Proc.new { block } if template.nil?
|
|
76
|
+
render :builder, template, options, locals
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
def render(engine, data, options={}, locals={}, &block)
|
|
81
|
+
# merge app-level options
|
|
82
|
+
options = settings.send(engine).merge(options) if settings.respond_to?(engine)
|
|
83
|
+
|
|
84
|
+
# extract generic options
|
|
85
|
+
locals = options.delete(:locals) || locals || {}
|
|
86
|
+
views = options.delete(:views) || settings.views || "./views"
|
|
87
|
+
layout = options.delete(:layout)
|
|
88
|
+
layout = :layout if layout.nil? || layout == true
|
|
89
|
+
|
|
90
|
+
# compile and render template
|
|
91
|
+
template = compile_template(engine, data, options, views)
|
|
92
|
+
output = template.render(self, locals, &block)
|
|
93
|
+
|
|
94
|
+
# render layout
|
|
95
|
+
if layout
|
|
96
|
+
begin
|
|
97
|
+
options = options.merge(:views => views, :layout => false)
|
|
98
|
+
output = render(engine, layout, options, locals) { output }
|
|
99
|
+
rescue Errno::ENOENT
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
output
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def compile_template(engine, data, options, views)
|
|
107
|
+
template_cache.fetch engine, data, options do
|
|
108
|
+
template = Tilt[engine]
|
|
109
|
+
raise "Template engine not found: #{engine}" if template.nil?
|
|
110
|
+
|
|
111
|
+
case
|
|
112
|
+
when data.is_a?(Symbol)
|
|
113
|
+
body, path, line = self.class.templates[data]
|
|
114
|
+
if body
|
|
115
|
+
body = body.call if body.respond_to?(:call)
|
|
116
|
+
template.new(path, line.to_i, options) { body }
|
|
117
|
+
else
|
|
118
|
+
path = ::File.join(views, "#{data}.#{engine}")
|
|
119
|
+
template.new(path, 1, options)
|
|
120
|
+
end
|
|
121
|
+
when data.is_a?(Proc) || data.is_a?(String)
|
|
122
|
+
body = data.is_a?(String) ? Proc.new { data } : data
|
|
123
|
+
path, line = self.class.caller_locations.first
|
|
124
|
+
template.new(path, line.to_i, options, &body)
|
|
125
|
+
else
|
|
126
|
+
raise ArgumentError
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/{Rakefile → rakefile}
RENAMED
|
@@ -4,7 +4,12 @@ require 'spec/rake/spectask'
|
|
|
4
4
|
Dir.chdir File.dirname(__FILE__)
|
|
5
5
|
|
|
6
6
|
# Specs
|
|
7
|
-
task :default => :spec
|
|
7
|
+
# task :default => :spec
|
|
8
|
+
|
|
9
|
+
task :default => :tst
|
|
10
|
+
task :tst do
|
|
11
|
+
p :hello
|
|
12
|
+
end
|
|
8
13
|
|
|
9
14
|
Spec::Rake::SpecTask.new('spec') do |t|
|
|
10
15
|
t.spec_files = FileList["spec/**/*_spec.rb"]
|
|
@@ -18,19 +23,19 @@ require 'fileutils'
|
|
|
18
23
|
|
|
19
24
|
spec = Gem::Specification.new do |s|
|
|
20
25
|
s.name = "crystal"
|
|
21
|
-
s.version = "0.0.
|
|
22
|
-
s.summary = "
|
|
23
|
-
s.description = "
|
|
26
|
+
s.version = "0.0.2"
|
|
27
|
+
s.summary = "Crystal - Ruby Web Framework"
|
|
28
|
+
s.description = "Crystal - Ruby Web Framework"
|
|
24
29
|
s.author = "Alexey Petrushin"
|
|
25
30
|
# s.email = ""
|
|
26
|
-
s.homepage = "http://
|
|
31
|
+
s.homepage = "http://github.com/alexeypetrushin/crystal"
|
|
27
32
|
# s.rubyforge_project = "RubyExt"
|
|
28
33
|
|
|
29
34
|
s.platform = Gem::Platform::RUBY
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
s.has_rdoc = true
|
|
36
|
+
# s.extra_rdoc_files = ["README.rdoc"]
|
|
32
37
|
|
|
33
|
-
s.files = (%w{
|
|
38
|
+
s.files = (%w{rakefile readme.md .gitignore} + Dir.glob("{lib,spec,.git}/**/*"))
|
|
34
39
|
# s.executables = ['restclient']
|
|
35
40
|
# s.add_dependency("BlueCloth", ">= 0.0.4")
|
|
36
41
|
# s.add_dependency "facets"
|
|
@@ -52,7 +57,7 @@ end
|
|
|
52
57
|
|
|
53
58
|
task :push do
|
|
54
59
|
dir = Dir.chdir PACKAGE_DIR do
|
|
55
|
-
gem_file = Dir.glob("
|
|
60
|
+
gem_file = Dir.glob("crystal*.gem").first
|
|
56
61
|
system "gem push #{gem_file}"
|
|
57
62
|
end
|
|
58
63
|
end
|
data/readme.md
ADDED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: crystal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 27
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 0
|
|
9
|
-
-
|
|
10
|
-
version: 0.0.
|
|
9
|
+
- 2
|
|
10
|
+
version: 0.0.2
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Alexey Petrushin
|
|
@@ -15,24 +15,95 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2010-05
|
|
18
|
+
date: 2010-06-05 00:00:00 +04:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies: []
|
|
21
21
|
|
|
22
|
-
description:
|
|
22
|
+
description: Crystal - Ruby Web Framework
|
|
23
23
|
email:
|
|
24
24
|
executables: []
|
|
25
25
|
|
|
26
26
|
extensions: []
|
|
27
27
|
|
|
28
|
-
extra_rdoc_files:
|
|
29
|
-
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
|
|
30
30
|
files:
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
31
|
+
- rakefile
|
|
32
|
+
- readme.md
|
|
33
|
+
- .gitignore
|
|
34
|
+
- lib/crystal/core.rb
|
|
35
|
+
- lib/crystal/garbage.rb
|
|
36
|
+
- lib/crystal/helpers.rb
|
|
37
|
+
- lib/crystal/rack/adapter.rb
|
|
38
|
+
- lib/crystal/rack/middleware/static_files.rb
|
|
39
|
+
- lib/crystal/rack/rack_app.rb
|
|
40
|
+
- lib/crystal/rack/showexceptions.rb
|
|
41
|
+
- lib/crystal/rack/support.rb
|
|
42
|
+
- lib/crystal/readme.txt
|
|
43
|
+
- lib/crystal/remote.rb
|
|
44
|
+
- lib/crystal/setting.rb
|
|
45
|
+
- lib/crystal/support/active_support.rb
|
|
46
|
+
- lib/crystal/template.rb
|
|
47
|
+
- .git/COMMIT_EDITMSG
|
|
48
|
+
- .git/config
|
|
49
|
+
- .git/description
|
|
50
|
+
- .git/HEAD
|
|
51
|
+
- .git/hooks/applypatch-msg.sample
|
|
52
|
+
- .git/hooks/commit-msg.sample
|
|
53
|
+
- .git/hooks/post-commit.sample
|
|
54
|
+
- .git/hooks/post-receive.sample
|
|
55
|
+
- .git/hooks/post-update.sample
|
|
56
|
+
- .git/hooks/pre-applypatch.sample
|
|
57
|
+
- .git/hooks/pre-commit.sample
|
|
58
|
+
- .git/hooks/pre-rebase.sample
|
|
59
|
+
- .git/hooks/prepare-commit-msg.sample
|
|
60
|
+
- .git/hooks/update.sample
|
|
61
|
+
- .git/index
|
|
62
|
+
- .git/info/exclude
|
|
63
|
+
- .git/logs/HEAD
|
|
64
|
+
- .git/logs/refs/heads/master
|
|
65
|
+
- .git/logs/refs/remotes/origin/master
|
|
66
|
+
- .git/objects/00/c26dc781c8997ed2bfa61f03b323466a3860d1
|
|
67
|
+
- .git/objects/04/03ee94b34a5760c46616cd13c317e69cb90d56
|
|
68
|
+
- .git/objects/09/66c6b45bacaae9282463b9b4c0ce8488bbcee5
|
|
69
|
+
- .git/objects/0d/03131f9fbc3e2fb1f8f9b616ac273d05f3a7f5
|
|
70
|
+
- .git/objects/0e/50f691a65b41ba9e7df9d6738ff9f333089b34
|
|
71
|
+
- .git/objects/14/6253750089fd522535155630344c8ff2a60830
|
|
72
|
+
- .git/objects/15/f5669d470665c6bd28b5ab7b65431e199c36e9
|
|
73
|
+
- .git/objects/28/a3d1b3c79d0e59b8359ecd75e4ec0d62f60468
|
|
74
|
+
- .git/objects/2c/b339709e1fb005a3e3006e76558d2e45333008
|
|
75
|
+
- .git/objects/2d/077a401db466ea0b1f062eac15293879293439
|
|
76
|
+
- .git/objects/38/3af6cb155addf818eb9396229892743112dbc3
|
|
77
|
+
- .git/objects/42/828defbc95389daf9bfa11c8b2341f98303ee7
|
|
78
|
+
- .git/objects/44/b6d8df4e1f924caeb7de56135c251ce24998fd
|
|
79
|
+
- .git/objects/46/42a59703e31a105aa67c4511be5e41402d08af
|
|
80
|
+
- .git/objects/56/43b861a644b727a8b54ae003977151d975f360
|
|
81
|
+
- .git/objects/5e/11ba5e0794a914747bae95b942b2dbeb5c36c3
|
|
82
|
+
- .git/objects/5f/2b8b8c9f16877c91fda9a177e235698103c4c9
|
|
83
|
+
- .git/objects/63/82b66d637e3d04e78a6272e25d3e212de90a10
|
|
84
|
+
- .git/objects/66/aa791f24ea2ed8717abe3d1c74da4ad544723d
|
|
85
|
+
- .git/objects/68/02f5c496f00787242a9fa1a81eb9847071e076
|
|
86
|
+
- .git/objects/81/031da1db90c4cd43fe27336054d76564dbe64c
|
|
87
|
+
- .git/objects/86/738c97b0d7ac8443a25cbcd0c4e147bcdb152e
|
|
88
|
+
- .git/objects/8d/a995e65577a82011df060b13a498f0a738d017
|
|
89
|
+
- .git/objects/90/cfb8df76866f20a0c9c4adfb6d5d1faebad9d3
|
|
90
|
+
- .git/objects/a9/38b272a1a743d7e30caa05b5513760195ec0a8
|
|
91
|
+
- .git/objects/b1/244bb3652d56ebe4b465468e214d1a1fa87ebb
|
|
92
|
+
- .git/objects/b4/e715329ba853104d19112487b1188571bca659
|
|
93
|
+
- .git/objects/bf/1e07cd3085bed5be7f3ca1860ec8c14d0fdebb
|
|
94
|
+
- .git/objects/c5/ef2f6ac4ee5779fa79f7afb54a458191352e86
|
|
95
|
+
- .git/objects/cc/0b74e32ae045d3208839aa91d0dc94f5ea4c09
|
|
96
|
+
- .git/objects/d8/53eba81fc4b75fbc90cf8c39f4b1344deddc9f
|
|
97
|
+
- .git/objects/df/e23416f16c0bb6c98ca2a0b1d02d803ff53831
|
|
98
|
+
- .git/objects/df/ee793cf3fd89820e5a9a4481d35000d75040d9
|
|
99
|
+
- .git/objects/e0/f7b971a634e34d7976676a4d575c654746a5f8
|
|
100
|
+
- .git/objects/f3/8245046bfe9c478a502c58b1c1434f382800d3
|
|
101
|
+
- .git/objects/f6/3c2e6d83a07cf107ff63a6b92a72a0ac5e9e3f
|
|
102
|
+
- .git/objects/fc/794047fc246572581a7fdce016e1be6b8ab61a
|
|
103
|
+
- .git/refs/heads/master
|
|
104
|
+
- .git/refs/remotes/origin/master
|
|
34
105
|
has_rdoc: true
|
|
35
|
-
homepage: http://
|
|
106
|
+
homepage: http://github.com/alexeypetrushin/crystal
|
|
36
107
|
licenses: []
|
|
37
108
|
|
|
38
109
|
post_install_message:
|
|
@@ -64,6 +135,6 @@ rubyforge_project:
|
|
|
64
135
|
rubygems_version: 1.3.7
|
|
65
136
|
signing_key:
|
|
66
137
|
specification_version: 3
|
|
67
|
-
summary:
|
|
138
|
+
summary: Crystal - Ruby Web Framework
|
|
68
139
|
test_files: []
|
|
69
140
|
|
data/README.rdoc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Web Framework, not releaset yet.
|
data/spec/spec.opts
DELETED