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