crystal 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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