crampy 0.15.3
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/MIT-LICENSE +20 -0
- data/bin/cramp +17 -0
- data/lib/cramp/abstract.rb +104 -0
- data/lib/cramp/action.rb +134 -0
- data/lib/cramp/body.rb +48 -0
- data/lib/cramp/callbacks.rb +92 -0
- data/lib/cramp/exception_handler.rb +357 -0
- data/lib/cramp/fiber_pool.rb +47 -0
- data/lib/cramp/generators/application.rb +97 -0
- data/lib/cramp/generators/templates/application/Gemfile +32 -0
- data/lib/cramp/generators/templates/application/app/actions/home_action.rb +11 -0
- data/lib/cramp/generators/templates/application/application.rb +36 -0
- data/lib/cramp/generators/templates/application/config/database.yml +6 -0
- data/lib/cramp/generators/templates/application/config/routes.rb +4 -0
- data/lib/cramp/generators/templates/application/config.ru +25 -0
- data/lib/cramp/keep_connection_alive.rb +19 -0
- data/lib/cramp/long_polling.rb +6 -0
- data/lib/cramp/periodic_timer.rb +56 -0
- data/lib/cramp/rendering.rb +11 -0
- data/lib/cramp/sse.rb +5 -0
- data/lib/cramp/test_case.rb +58 -0
- data/lib/cramp/version.rb +3 -0
- data/lib/cramp/websocket.rb +13 -0
- data/lib/cramp.rb +47 -0
- data/lib/crampy.rb +4 -0
- data/lib/vendor/fiber_pool.rb +85 -0
- metadata +130 -0
@@ -0,0 +1,357 @@
|
|
1
|
+
# Based on Rack::ShowExceptions
|
2
|
+
#
|
3
|
+
# Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to
|
7
|
+
# deal in the Software without restriction, including without limitation the
|
8
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
9
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
18
|
+
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'erb'
|
23
|
+
require 'ostruct'
|
24
|
+
|
25
|
+
module Cramp
|
26
|
+
class ExceptionHandler
|
27
|
+
|
28
|
+
attr_reader :env, :exception
|
29
|
+
|
30
|
+
def initialize(env, exception)
|
31
|
+
@env = env
|
32
|
+
@exception = exception
|
33
|
+
@template = ERB.new(TEMPLATE)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dump_exception
|
37
|
+
string = "#{exception.class}: #{exception.message}\n"
|
38
|
+
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
|
39
|
+
string
|
40
|
+
end
|
41
|
+
|
42
|
+
def pretty
|
43
|
+
req = Rack::Request.new(env)
|
44
|
+
|
45
|
+
# This double assignment is to prevent an "unused variable" warning on
|
46
|
+
# Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
|
47
|
+
path = path = (req.script_name + req.path_info).squeeze("/")
|
48
|
+
|
49
|
+
# This double assignment is to prevent an "unused variable" warning on
|
50
|
+
# Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
|
51
|
+
frames = frames = exception.backtrace.map { |line|
|
52
|
+
frame = OpenStruct.new
|
53
|
+
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
54
|
+
frame.filename = $1
|
55
|
+
frame.lineno = $2.to_i
|
56
|
+
frame.function = $4
|
57
|
+
|
58
|
+
begin
|
59
|
+
lineno = frame.lineno-1
|
60
|
+
lines = ::File.readlines(frame.filename)
|
61
|
+
frame.pre_context_lineno = [lineno-CONTEXT, 0].max
|
62
|
+
frame.pre_context = lines[frame.pre_context_lineno...lineno]
|
63
|
+
frame.context_line = lines[lineno].chomp
|
64
|
+
frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
|
65
|
+
frame.post_context = lines[lineno+1..frame.post_context_lineno]
|
66
|
+
rescue
|
67
|
+
end
|
68
|
+
|
69
|
+
frame
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
}.compact
|
74
|
+
|
75
|
+
[@template.result(binding)]
|
76
|
+
end
|
77
|
+
|
78
|
+
def h(obj) # :nodoc:
|
79
|
+
case obj
|
80
|
+
when String
|
81
|
+
Rack::Utils.escape_html(obj)
|
82
|
+
else
|
83
|
+
Rack::Utils.escape_html(obj.inspect)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# adapted from Django <djangoproject.com>
|
88
|
+
# Copyright (c) 2005, the Lawrence Journal-World
|
89
|
+
# Used under the modified BSD license:
|
90
|
+
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
91
|
+
TEMPLATE = <<'HTML'
|
92
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
93
|
+
<html lang="en">
|
94
|
+
<head>
|
95
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
96
|
+
<meta name="robots" content="NONE,NOARCHIVE" />
|
97
|
+
<title><%=h exception.class %> at <%=h path %></title>
|
98
|
+
<style type="text/css">
|
99
|
+
html * { padding:0; margin:0; }
|
100
|
+
body * { padding:10px 20px; }
|
101
|
+
body * * { padding:0; }
|
102
|
+
body { font:small sans-serif; }
|
103
|
+
body>div { border-bottom:1px solid #ddd; }
|
104
|
+
h1 { font-weight:normal; }
|
105
|
+
h2 { margin-bottom:.8em; }
|
106
|
+
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
107
|
+
h3 { margin:1em 0 .5em 0; }
|
108
|
+
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
109
|
+
table {
|
110
|
+
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
111
|
+
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
112
|
+
thead th {
|
113
|
+
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
114
|
+
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
115
|
+
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
116
|
+
table.vars { margin:5px 0 2px 40px; }
|
117
|
+
table.vars td, table.req td { font-family:monospace; }
|
118
|
+
table td.code { width:100%;}
|
119
|
+
table td.code div { overflow:hidden; }
|
120
|
+
table.source th { color:#666; }
|
121
|
+
table.source td {
|
122
|
+
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
123
|
+
ul.traceback { list-style-type:none; }
|
124
|
+
ul.traceback li.frame { margin-bottom:1em; }
|
125
|
+
div.context { margin: 10px 0; }
|
126
|
+
div.context ol {
|
127
|
+
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
128
|
+
div.context ol li {
|
129
|
+
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
130
|
+
div.context ol.context-line li { color:black; background-color:#ccc; }
|
131
|
+
div.context ol.context-line li span { float: right; }
|
132
|
+
div.commands { margin-left: 40px; }
|
133
|
+
div.commands a { color:black; text-decoration:none; }
|
134
|
+
#summary { background: #ffc; }
|
135
|
+
#summary h2 { font-weight: normal; color: #666; }
|
136
|
+
#summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
|
137
|
+
#summary ul#quicklinks li { float: left; padding: 0 1em; }
|
138
|
+
#summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
|
139
|
+
#explanation { background:#eee; }
|
140
|
+
#template, #template-not-exist { background:#f6f6f6; }
|
141
|
+
#template-not-exist ul { margin: 0 0 0 20px; }
|
142
|
+
#traceback { background:#eee; }
|
143
|
+
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
144
|
+
#summary table { border:none; background:transparent; }
|
145
|
+
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
146
|
+
#requestinfo h3 { margin-bottom:-1em; }
|
147
|
+
.error { background: #ffc; }
|
148
|
+
.specific { color:#cc3300; font-weight:bold; }
|
149
|
+
</style>
|
150
|
+
<script type="text/javascript">
|
151
|
+
//<!--
|
152
|
+
function getElementsByClassName(oElm, strTagName, strClassName){
|
153
|
+
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
154
|
+
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
155
|
+
var arrElements = (strTagName == "*" && document.all)? document.all :
|
156
|
+
oElm.getElementsByTagName(strTagName);
|
157
|
+
var arrReturnElements = new Array();
|
158
|
+
strClassName = strClassName.replace(/\-/g, "\\-");
|
159
|
+
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
|
160
|
+
var oElement;
|
161
|
+
for(var i=0; i<arrElements.length; i++){
|
162
|
+
oElement = arrElements[i];
|
163
|
+
if(oRegExp.test(oElement.className)){
|
164
|
+
arrReturnElements.push(oElement);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
return (arrReturnElements)
|
168
|
+
}
|
169
|
+
function hideAll(elems) {
|
170
|
+
for (var e = 0; e < elems.length; e++) {
|
171
|
+
elems[e].style.display = 'none';
|
172
|
+
}
|
173
|
+
}
|
174
|
+
window.onload = function() {
|
175
|
+
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
176
|
+
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
177
|
+
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
178
|
+
}
|
179
|
+
function toggle() {
|
180
|
+
for (var i = 0; i < arguments.length; i++) {
|
181
|
+
var e = document.getElementById(arguments[i]);
|
182
|
+
if (e) {
|
183
|
+
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
184
|
+
}
|
185
|
+
}
|
186
|
+
return false;
|
187
|
+
}
|
188
|
+
function varToggle(link, id) {
|
189
|
+
toggle('v' + id);
|
190
|
+
var s = link.getElementsByTagName('span')[0];
|
191
|
+
var uarr = String.fromCharCode(0x25b6);
|
192
|
+
var darr = String.fromCharCode(0x25bc);
|
193
|
+
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
194
|
+
return false;
|
195
|
+
}
|
196
|
+
//-->
|
197
|
+
</script>
|
198
|
+
</head>
|
199
|
+
<body>
|
200
|
+
|
201
|
+
<div id="summary">
|
202
|
+
<h1><%=h exception.class %> at <%=h path %></h1>
|
203
|
+
<h2><%=h exception.message %></h2>
|
204
|
+
<table><tr>
|
205
|
+
<th>Ruby</th>
|
206
|
+
<td>
|
207
|
+
<% if first = frames.first %>
|
208
|
+
<code><%=h first.filename %></code>: in <code><%=h first.function %></code>, line <%=h frames.first.lineno %>
|
209
|
+
<% else %>
|
210
|
+
unknown location
|
211
|
+
<% end %>
|
212
|
+
</td>
|
213
|
+
</tr><tr>
|
214
|
+
<th>Web</th>
|
215
|
+
<td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
|
216
|
+
</tr></table>
|
217
|
+
|
218
|
+
<h3>Jump to:</h3>
|
219
|
+
<ul id="quicklinks">
|
220
|
+
<li><a href="#get-info">GET</a></li>
|
221
|
+
<li><a href="#post-info">POST</a></li>
|
222
|
+
<li><a href="#cookie-info">Cookies</a></li>
|
223
|
+
<li><a href="#env-info">ENV</a></li>
|
224
|
+
</ul>
|
225
|
+
</div>
|
226
|
+
|
227
|
+
<div id="traceback">
|
228
|
+
<h2>Traceback <span>(innermost first)</span></h2>
|
229
|
+
<ul class="traceback">
|
230
|
+
<% frames.each { |frame| %>
|
231
|
+
<li class="frame">
|
232
|
+
<code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
|
233
|
+
|
234
|
+
<% if frame.context_line %>
|
235
|
+
<div class="context" id="c<%=h frame.object_id %>">
|
236
|
+
<% if frame.pre_context %>
|
237
|
+
<ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
|
238
|
+
<% frame.pre_context.each { |line| %>
|
239
|
+
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
|
240
|
+
<% } %>
|
241
|
+
</ol>
|
242
|
+
<% end %>
|
243
|
+
|
244
|
+
<ol start="<%=h frame.lineno %>" class="context-line">
|
245
|
+
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
|
246
|
+
|
247
|
+
<% if frame.post_context %>
|
248
|
+
<ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
|
249
|
+
<% frame.post_context.each { |line| %>
|
250
|
+
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
|
251
|
+
<% } %>
|
252
|
+
</ol>
|
253
|
+
<% end %>
|
254
|
+
</div>
|
255
|
+
<% end %>
|
256
|
+
</li>
|
257
|
+
<% } %>
|
258
|
+
</ul>
|
259
|
+
</div>
|
260
|
+
|
261
|
+
<div id="requestinfo">
|
262
|
+
<h2>Request information</h2>
|
263
|
+
|
264
|
+
<h3 id="get-info">GET</h3>
|
265
|
+
<% unless req.GET.empty? %>
|
266
|
+
<table class="req">
|
267
|
+
<thead>
|
268
|
+
<tr>
|
269
|
+
<th>Variable</th>
|
270
|
+
<th>Value</th>
|
271
|
+
</tr>
|
272
|
+
</thead>
|
273
|
+
<tbody>
|
274
|
+
<% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
275
|
+
<tr>
|
276
|
+
<td><%=h key %></td>
|
277
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
278
|
+
</tr>
|
279
|
+
<% } %>
|
280
|
+
</tbody>
|
281
|
+
</table>
|
282
|
+
<% else %>
|
283
|
+
<p>No GET data.</p>
|
284
|
+
<% end %>
|
285
|
+
|
286
|
+
<h3 id="post-info">POST</h3>
|
287
|
+
<% unless req.POST.empty? %>
|
288
|
+
<table class="req">
|
289
|
+
<thead>
|
290
|
+
<tr>
|
291
|
+
<th>Variable</th>
|
292
|
+
<th>Value</th>
|
293
|
+
</tr>
|
294
|
+
</thead>
|
295
|
+
<tbody>
|
296
|
+
<% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
297
|
+
<tr>
|
298
|
+
<td><%=h key %></td>
|
299
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
300
|
+
</tr>
|
301
|
+
<% } %>
|
302
|
+
</tbody>
|
303
|
+
</table>
|
304
|
+
<% else %>
|
305
|
+
<p>No POST data.</p>
|
306
|
+
<% end %>
|
307
|
+
|
308
|
+
|
309
|
+
<h3 id="cookie-info">COOKIES</h3>
|
310
|
+
<% unless req.cookies.empty? %>
|
311
|
+
<table class="req">
|
312
|
+
<thead>
|
313
|
+
<tr>
|
314
|
+
<th>Variable</th>
|
315
|
+
<th>Value</th>
|
316
|
+
</tr>
|
317
|
+
</thead>
|
318
|
+
<tbody>
|
319
|
+
<% req.cookies.each { |key, val| %>
|
320
|
+
<tr>
|
321
|
+
<td><%=h key %></td>
|
322
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
323
|
+
</tr>
|
324
|
+
<% } %>
|
325
|
+
</tbody>
|
326
|
+
</table>
|
327
|
+
<% else %>
|
328
|
+
<p>No cookie data.</p>
|
329
|
+
<% end %>
|
330
|
+
|
331
|
+
<h3 id="env-info">Rack ENV</h3>
|
332
|
+
<table class="req">
|
333
|
+
<thead>
|
334
|
+
<tr>
|
335
|
+
<th>Variable</th>
|
336
|
+
<th>Value</th>
|
337
|
+
</tr>
|
338
|
+
</thead>
|
339
|
+
<tbody>
|
340
|
+
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
341
|
+
<tr>
|
342
|
+
<td><%=h key %></td>
|
343
|
+
<td class="code"><div><%=h val %></div></td>
|
344
|
+
</tr>
|
345
|
+
<% } %>
|
346
|
+
</tbody>
|
347
|
+
</table>
|
348
|
+
|
349
|
+
</div>
|
350
|
+
|
351
|
+
</body>
|
352
|
+
</html>
|
353
|
+
HTML
|
354
|
+
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Cramp
|
2
|
+
module FiberPool
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :fiber_pool
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def use_fiber_pool(options = {})
|
11
|
+
unless defined?(::FiberPool)
|
12
|
+
raise "Fiber support is only available for Rubies >= 1.9.2"
|
13
|
+
end
|
14
|
+
|
15
|
+
self.fiber_pool = ::FiberPool.new(options[:size] || 100)
|
16
|
+
yield self.fiber_pool if block_given?
|
17
|
+
include UsesFiberPool
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module UsesFiberPool
|
22
|
+
# Overrides wrapper methods to run callbacks in a fiber
|
23
|
+
|
24
|
+
def callback_wrapper
|
25
|
+
self.fiber_pool.spawn do
|
26
|
+
begin
|
27
|
+
yield
|
28
|
+
rescue StandardError, LoadError, SyntaxError => exception
|
29
|
+
handle_exception(exception)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def timer_method_wrapper(method)
|
35
|
+
self.fiber_pool.spawn do
|
36
|
+
begin
|
37
|
+
send(method)
|
38
|
+
rescue StandardError, LoadError, SyntaxError => exception
|
39
|
+
handle_exception(exception)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'thor/group'
|
2
|
+
require 'active_support/core_ext/string/strip'
|
3
|
+
require 'active_support/inflector/methods'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
|
6
|
+
module Cramp
|
7
|
+
module Generators
|
8
|
+
|
9
|
+
class Application < Thor::Group
|
10
|
+
include Thor::Actions
|
11
|
+
|
12
|
+
argument :application_path, :type => :string
|
13
|
+
class_option :with_active_record, :type => :boolean, :aliases => "-M", :default => false, :desc => "Configures Active Record"
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
raise Thor::Error, "No application name supplied. Please run: cramp --help" if args[0].blank?
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.source_root
|
22
|
+
@_source_root ||= File.join(File.dirname(__FILE__), "templates/application")
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.banner
|
26
|
+
"cramp new #{self.arguments.map(&:usage).join(' ')} [options]"
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_root
|
30
|
+
self.destination_root = File.expand_path(application_path, destination_root)
|
31
|
+
valid_const?
|
32
|
+
|
33
|
+
empty_directory '.'
|
34
|
+
FileUtils.cd(destination_root)
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_root_files
|
38
|
+
template 'config.ru'
|
39
|
+
template 'Gemfile'
|
40
|
+
template 'application.rb'
|
41
|
+
|
42
|
+
empty_directory "public"
|
43
|
+
empty_directory "public/javascripts"
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_config
|
47
|
+
empty_directory "config"
|
48
|
+
|
49
|
+
inside "config" do
|
50
|
+
template "routes.rb"
|
51
|
+
template 'database.yml' if active_record?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_home_action
|
56
|
+
empty_directory "app/actions"
|
57
|
+
|
58
|
+
inside "app/actions" do
|
59
|
+
template "home_action.rb"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_models
|
64
|
+
if active_record?
|
65
|
+
empty_directory "app/models"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
def active_record?
|
72
|
+
options[:with_active_record]
|
73
|
+
end
|
74
|
+
|
75
|
+
def app_name
|
76
|
+
@app_name ||= File.basename(destination_root)
|
77
|
+
end
|
78
|
+
|
79
|
+
def app_const
|
80
|
+
@app_const ||= "#{app_const_base}::Application"
|
81
|
+
end
|
82
|
+
|
83
|
+
def app_const_base
|
84
|
+
@app_const_base ||= ActiveSupport::Inflector.camelize(app_name.gsub(/\W/, '_').squeeze('_'), true)
|
85
|
+
end
|
86
|
+
|
87
|
+
def valid_const?
|
88
|
+
if app_const =~ /^\d/
|
89
|
+
raise Thor::Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers."
|
90
|
+
elsif Object.const_defined?(app_const_base)
|
91
|
+
raise Thor::Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
source :rubygems
|
2
|
+
|
3
|
+
gem 'cramp'
|
4
|
+
|
5
|
+
# Async webserver for running a cramp application
|
6
|
+
gem 'thin'
|
7
|
+
|
8
|
+
# Rack based routing
|
9
|
+
gem 'http_router'
|
10
|
+
|
11
|
+
# Collection of async-proof rack middlewares - https://github.com/rkh/async-rack.git
|
12
|
+
gem 'async-rack'
|
13
|
+
|
14
|
+
# For async Active Record models
|
15
|
+
<% if active_record? -%>
|
16
|
+
gem 'mysql2', '~> 0.2.11'
|
17
|
+
gem 'activerecord', :require => 'active_record'
|
18
|
+
<% else -%>
|
19
|
+
# gem 'mysql2', '~> 0.2.11'
|
20
|
+
# gem 'activerecord', :require => 'active_record'
|
21
|
+
<% end -%>
|
22
|
+
|
23
|
+
# Using Fibers + async callbacks to emulate synchronous programming
|
24
|
+
# gem 'em-synchrony'
|
25
|
+
|
26
|
+
# Generic interface to multiple Ruby template engines - https://github.com/rtomayko/tilt
|
27
|
+
# gem 'tilt'
|
28
|
+
|
29
|
+
group :development do
|
30
|
+
# Development gems
|
31
|
+
# gem 'ruby-debug19'
|
32
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class HomeAction < Cramp::Action
|
2
|
+
<% if active_record? -%>use_fiber_pool do |pool|
|
3
|
+
# Checkin database connection after each callback
|
4
|
+
pool.generic_callbacks << proc { ActiveRecord::Base.clear_active_connections! }
|
5
|
+
end
|
6
|
+
|
7
|
+
<% end %>def start
|
8
|
+
render "Hello World!"
|
9
|
+
finish
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
|
4
|
+
module <%= app_const_base %>
|
5
|
+
class Application
|
6
|
+
|
7
|
+
def self.root(path = nil)
|
8
|
+
@_root ||= File.expand_path(File.dirname(__FILE__))
|
9
|
+
path ? File.join(@_root, path.to_s) : @_root
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.env
|
13
|
+
@_env ||= ENV['RACK_ENV'] || 'development'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.routes
|
17
|
+
@_routes ||= eval(File.read('./config/routes.rb'))
|
18
|
+
end
|
19
|
+
|
20
|
+
<% if active_record? %>def self.database_config
|
21
|
+
@_database_config ||= YAML.load(File.read('./config/database.yml')).with_indifferent_access
|
22
|
+
end
|
23
|
+
|
24
|
+
<% end %># Initialize the application
|
25
|
+
def self.initialize!<% if active_record? %>
|
26
|
+
ActiveRecord::Base.configurations = <%= app_const %>.database_config
|
27
|
+
ActiveRecord::Base.establish_connection(<%= app_const %>.env)<% end %>
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Bundler.require(:default, <%= app_const %>.env)
|
34
|
+
|
35
|
+
# Preload application classes
|
36
|
+
Dir['./app/**/*.rb'].each {|f| require f}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require './application'
|
2
|
+
<%= app_const %>.initialize!
|
3
|
+
|
4
|
+
# Development middlewares
|
5
|
+
if <%= app_const %>.env == 'development'
|
6
|
+
use AsyncRack::CommonLogger
|
7
|
+
|
8
|
+
# Enable code reloading on every request
|
9
|
+
use Rack::Reloader, 0
|
10
|
+
|
11
|
+
# Serve assets from /public
|
12
|
+
use Rack::Static, :urls => ["/javascripts"], :root => <%= app_const %>.root(:public)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Running thin :
|
16
|
+
#
|
17
|
+
# bundle exec thin --max-persistent-conns 1024 --timeout 0 -R config.ru start
|
18
|
+
#
|
19
|
+
# Vebose mode :
|
20
|
+
#
|
21
|
+
# Very useful when you want to view all the data being sent/received by thin
|
22
|
+
#
|
23
|
+
# bundle exec thin --max-persistent-conns 1024 --timeout 0 -V -R config.ru start
|
24
|
+
#
|
25
|
+
run <%= app_const %>.routes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cramp
|
2
|
+
module KeepConnectionAlive
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include PeriodicTimer
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def keep_connection_alive(options = {})
|
9
|
+
options = { :every => 15 }.merge(options)
|
10
|
+
periodic_timer :keep_connection_alive, options
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def keep_connection_alive
|
15
|
+
@body.call " "
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|