crampy 0.15.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,6 @@
1
+ development:
2
+ adapter: em_mysql2
3
+ database: <%= app_name %>_development
4
+ host: localhost
5
+ username: root
6
+ pool: 100
@@ -0,0 +1,4 @@
1
+ # Check out https://github.com/joshbuddy/http_router for more information on HttpRouter
2
+ HttpRouter.new do
3
+ add('/').to(HomeAction)
4
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ module Cramp
2
+ # All the usual Cramp::Action stuff. But the request is terminated as soon as render() is called.
3
+ class LongPolling < Action
4
+ self.transport = :long_polling
5
+ end
6
+ end