debug-bar 1.0.0

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,34 @@
1
+ require 'pp'
2
+ require 'awesome_print'
3
+ require 'cgi'
4
+
5
+ module DebugBar
6
+ module RecipeBook
7
+ # A default RecipeBook with recipes useful for Rails applications.
8
+ class Default < Base
9
+
10
+ # Displays params in a user readable fashion.
11
+ #
12
+ # If the :cutoff option is given, it auto-hides when the params are
13
+ # more characters in length than the cutoff, otherwise it defaults to
14
+ # a sane length.
15
+ def params_recipe(opts={})
16
+ return Proc.new do |b|
17
+ params_s = b[:params].awesome_print_html
18
+ ['Params', params_s, {:id => 'params'}]
19
+ end
20
+ end
21
+
22
+ # Displays the session in a pretty printed way.
23
+ def session_recipe
24
+ return Proc.new {|b| ['Session', b[:session].awesome_print_html, {:id => 'session'}]}
25
+ end
26
+
27
+ # Displays the cookies.
28
+ def cookies_recipe
29
+ return Proc.new {|b| ['Cookies', b[:cookies].awesome_print_html, {:id => 'cookies'}]}
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'recipe_book/base'
2
+ require_relative 'recipe_book/default'
@@ -0,0 +1,3 @@
1
+ module DebugBar
2
+ VERSION = '1.0.0'
3
+ end
data/lib/debug-bar.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'debug-bar/ext'
2
+
3
+ require_relative 'debug-bar/base'
4
+ require_relative 'debug-bar/default'
5
+
6
+ require_relative 'debug-bar/recipe_book'
@@ -0,0 +1,12 @@
1
+ <div class="callback-box dbar-togglable" style="border:1px solid gray; margin:4px; padding:4px;">
2
+ <% if opts[:id] %>
3
+ <a href="" id="<%= opts[:id] %>" class='dbar-toggle persistent'>
4
+ <% else %>
5
+ <a href="" class='dbar-toggle'>
6
+ <% end %>
7
+ [x] <span class="title" style="font-weight:bold; color: #000000;"><%= title %></span>
8
+ </a>
9
+ <div class="dbar-content <%= 'show' unless opts[:hidden] %>" <%= 'style="display:none"' if opts[:hidden] %> >
10
+ <%= content %>
11
+ </div>
12
+ </div>
@@ -0,0 +1,6 @@
1
+ <div class="error">
2
+ <pre>
3
+ <%= error.class.to_s.html_escape %>:<%= error.message.to_s.html_escape %>
4
+ </pre>
5
+ <%= error.backtrace.awesome_print_html %>
6
+ </div>
@@ -0,0 +1,142 @@
1
+ <style>
2
+ .dbar-content pre {
3
+ background: #a9a9a9;
4
+ margin: 0;
5
+ padding: 0;
6
+ font-size: 10px;
7
+ font-family: "Lucida Console", Monaco, monospace;
8
+ }
9
+
10
+ .dbar-content pre pre {
11
+ background: #a9a9a9;
12
+ margin: 0;
13
+ padding: 0;
14
+ border: 0;
15
+ font-size: 10px;
16
+ font-family: "Lucida Console", Monaco, monospace;
17
+ display: inline;
18
+ }
19
+
20
+ </style>
21
+
22
+ <div id="debug-bar" style="text-align:left; border:1px solid yellow; background:#ffffaa; width:24px; position:fixed; top:0; left:0; z-index:999">
23
+ <a href="" id="debug-toggle">[x]</a>
24
+ <div id="debug-data" style="font-size:9pt; display:none; overflow:auto;">
25
+ <%= content %>
26
+ </div>
27
+ </div>
28
+
29
+ <script>
30
+ /*!
31
+ * jQuery Cookie Plugin
32
+ * https://github.com/carhartl/jquery-cookie
33
+ *
34
+ * Copyright 2011, Klaus Hartl
35
+ * Dual licensed under the MIT or GPL Version 2 licenses.
36
+ * http://www.opensource.org/licenses/mit-license.php
37
+ * http://www.opensource.org/licenses/GPL-2.0
38
+ */
39
+ (function($) {
40
+ $.cookie = function(key, value, options) {
41
+
42
+ // key and at least value given, set cookie...
43
+ if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
44
+ options = $.extend({}, options);
45
+
46
+ if (value === null || value === undefined) {
47
+ options.expires = -1;
48
+ }
49
+
50
+ if (typeof options.expires === 'number') {
51
+ var days = options.expires, t = options.expires = new Date();
52
+ t.setDate(t.getDate() + days);
53
+ }
54
+
55
+ value = String(value);
56
+
57
+ return (document.cookie = [
58
+ encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
59
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
60
+ options.path ? '; path=' + options.path : '',
61
+ options.domain ? '; domain=' + options.domain : '',
62
+ options.secure ? '; secure' : ''
63
+ ].join(''));
64
+ }
65
+
66
+ // key and possibly options given, get cookie...
67
+ options = value || {};
68
+ var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
69
+
70
+ var pairs = document.cookie.split('; ');
71
+ for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
72
+ if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
73
+ }
74
+ return null;
75
+ };
76
+ })(jQuery);
77
+ </script>
78
+ <script>
79
+ jQuery('document').ready(function(){
80
+ var toggleDebugBar = function(){
81
+ var data = jQuery('#debug-data').toggle();
82
+ jQuery('#debug-bar').css('width', data.is(':visible') ? 'auto' : '24px');
83
+ jQuery('#debug-bar').css('max-height', data.is(':visible') ? '100%' : '24px');
84
+ jQuery('#debug-bar').css('overflow-y', data.is(':visible') ? 'auto' : 'hidden');
85
+ };
86
+
87
+ jQuery('#debug-toggle').on('click', function(e){
88
+ toggleDebugBar();
89
+ return false;
90
+ });
91
+
92
+ jQuery('body').bind('keydown', function(e){
93
+ if(e.keyCode == 192 && e.ctrlKey == true)
94
+ toggleDebugBar();
95
+ });
96
+
97
+ // Debug-bar section specific toggle (may differ from generic toggle.
98
+ // This is meant to be consumable by callback makers.
99
+ jQuery('.dbar-toggle').bind('click', function(){
100
+ var toggle = jQuery(this);
101
+ var content = toggle.closest('.dbar-togglable').find('.dbar-content');
102
+ content.first().toggle();
103
+ return false;
104
+ });
105
+
106
+ // Generic togglable inside of debug bar callback content area.
107
+ jQuery('.dbar-content').find('.toggle-switch').click(function(){
108
+ var toggle_content = jQuery(this).siblings('.toggle-content');
109
+ toggle_content.toggle();
110
+ return false;
111
+ });
112
+
113
+ jQuery('.callback-box .dbar-toggle.persistent').bind('click', function() {
114
+ // caching
115
+ var toggle = jQuery(this);
116
+ var id = toggle.attr('id');
117
+
118
+ // Deal with an empty cookie and split the commas
119
+ var debug_bar_array = jQuery.cookie('debug_bar') ? jQuery.cookie('debug_bar').split(',') : [];
120
+
121
+ // Just a double check it has an id
122
+ if (id)
123
+ {
124
+ // Decide which way to toggle this key
125
+ var id_index = debug_bar_array.indexOf(id);
126
+ if (id_index > -1)
127
+ {
128
+ // Remove id from cookie
129
+ debug_bar_array.splice(id_index, 1);
130
+ }
131
+ else
132
+ {
133
+ // Add the id into the list to keep open
134
+ debug_bar_array.push(id);
135
+ }
136
+
137
+ // Rejoin the cookie keys and store
138
+ jQuery.cookie('debug_bar', debug_bar_array.join(','), { path: '/', expires: 7 });
139
+ }
140
+ });
141
+ });
142
+ </script>
data/spec/base_spec.rb ADDED
@@ -0,0 +1,363 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ class TestDefaultRecipeBook < DebugBar::RecipeBook::Base
5
+
6
+ def params_recipe(opts={})
7
+ return Proc.new do |b|
8
+ params_s = b[:params].awesome_print_html
9
+ hidden = params_s.length > opts.fetch(:cutoff, 512)
10
+ puts [params_s.length, hidden, opts].inspect
11
+ ['Params', params_s, {:hidden => hidden, :id => 'params'}]
12
+ end
13
+ end
14
+
15
+ def block_test_recipe(arg=nil)
16
+ return Proc.new do |b|
17
+ ['Block Test', "|arg|#{arg}|arg| |block|#{yield(b) if block_given?}|block|", {}]
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ describe DebugBar::Base do
24
+
25
+ describe 'callbacks' do
26
+
27
+ before(:each) do
28
+ @debug_bar = DebugBar::Base.new
29
+ end
30
+
31
+ it 'should register recipes in initializer after the block is called' do
32
+ debug_bar = DebugBar::Base.new(:params) do |bar|
33
+ bar.add_recipe_book(TestDefaultRecipeBook)
34
+ bar.callbacks.length.should == 0
35
+ end
36
+
37
+ debug_bar.callbacks.length.should == 1
38
+ end
39
+
40
+
41
+ it 'should register blocks' do
42
+ @debug_bar.add {|b| ["Test Block", "Test Content", {}]}
43
+
44
+ @debug_bar.callbacks.length.should == 1
45
+ @debug_bar.callbacks.first.should be_kind_of(Proc)
46
+ end
47
+
48
+ it 'should register recipes' do
49
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
50
+ @debug_bar.add(:params)
51
+
52
+ @debug_bar.callbacks.length.should == 1
53
+ @debug_bar.callbacks.first.should be_kind_of(Proc)
54
+ end
55
+
56
+ it 'should error on nil' do
57
+ lambda {@debug_bar.add(nil)}.should raise_error(ArgumentError)
58
+ end
59
+
60
+ end
61
+
62
+ describe 'render' do
63
+
64
+ before(:each) do
65
+ @debug_bar = DebugBar::Base.new
66
+ end
67
+
68
+ it 'should render blocks' do
69
+ title = "Testing The Block"
70
+ content = "<pre>Alpha Beta</pre>"
71
+ @debug_bar.add {|b| [title, content, {}]}
72
+
73
+ html = @debug_bar.render(binding)
74
+
75
+ html.should be_kind_of(String)
76
+ html.should_not be_empty
77
+
78
+ html.index(title).should_not be_nil
79
+ html.index(content).should_not be_nil
80
+ end
81
+
82
+ it 'should render as html_safe string' do
83
+ @debug_bar.add {|b| ["foo", "bar", {}]}
84
+ html = @debug_bar.render(binding)
85
+ html.should be_kind_of(ActiveSupport::SafeBuffer)
86
+ end
87
+
88
+ it 'should render with a given binding' do
89
+ @debug_bar.add {|b| ["Title", "||#{b.eval('foobar')}||", {}]}
90
+
91
+ foobar = "Spoilers!"
92
+ html = @debug_bar.render(binding)
93
+
94
+ html.should be_kind_of(String)
95
+
96
+ html.index(foobar).should_not be_nil
97
+ end
98
+
99
+ it 'should render with a custom decorated binding' do
100
+ @debug_bar.add {|b| ["Binding", "||get is #{b.respond_to?(:[])}||", {}]}
101
+
102
+ binding.should_not respond_to(:[])
103
+ html = @debug_bar.render(binding)
104
+
105
+ html.index("||get is true||").should_not be_nil
106
+ end
107
+
108
+ it 'should render recipes' do
109
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
110
+ @debug_bar.add(:params)
111
+ params = {:given_name => 'Amelia', :family_name => 'Pond'}
112
+
113
+ html = @debug_bar.render(binding)
114
+
115
+ html.should be_kind_of(String)
116
+ html.should_not be_empty
117
+
118
+ html.index('Amelia').should_not be_nil
119
+ html.index(':given_name').should_not be_nil
120
+ html.index('dbar-content show').should_not be_nil # See if params was expanded.
121
+ end
122
+
123
+ it 'should render recipes with args' do
124
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
125
+ @debug_bar.add(:params, :cutoff => 12)
126
+
127
+ params = {:given_name => 'Amelia', :family_name => 'Pond'}
128
+
129
+ html = @debug_bar.render(binding)
130
+
131
+ html.should be_kind_of(String)
132
+ html.should_not be_empty
133
+
134
+ html.index('Amelia').should_not be_nil
135
+ html.index(':given_name').should_not be_nil
136
+ html.index('dbar-content show').should be_nil # See if params was collapsed due to cutoff.
137
+ end
138
+
139
+ it 'should render recipes with block args' do
140
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
141
+ @debug_bar.add(:block_test) {|b| b.class.name}
142
+
143
+ html = @debug_bar.render(binding)
144
+
145
+ html.should be_kind_of(String)
146
+ html.should_not be_empty
147
+
148
+ html.index('|block|Binding|block|').should_not be_nil
149
+ end
150
+
151
+ it 'should render the callback_box' do
152
+ @debug_bar.add {|b| ["foo", "bar", {}]}
153
+ html = @debug_bar.render(binding)
154
+
155
+ html.index('callback-box').should_not be_nil # Picked the CSS class as a good indicator of presence.
156
+ end
157
+
158
+ it 'should render the layout' do
159
+ @debug_bar.add {|b| ["foo", "bar", {}]}
160
+ html = @debug_bar.render(binding)
161
+
162
+ html.index('debug-bar').should_not be_nil # Picked the presence of CSS class as a good inidcator of presence.
163
+ end
164
+
165
+ it 'should render on crash in callback' do
166
+ @debug_bar.add {|b| raise RuntimeError, "Uh-oh, you didn't handle the exception!"}
167
+ html = ''
168
+ lambda {html = @debug_bar.render(binding)}.should_not raise_error
169
+ html.index('ERROR').should_not be_nil
170
+ end
171
+
172
+ end
173
+
174
+ describe 'options' do
175
+
176
+ before(:each) do
177
+ @debug_bar = DebugBar::Base.new
178
+ end
179
+
180
+ it 'should interpret missing opts from callback as {}' do
181
+ @debug_bar.add {|b| ['foo', 'bar']}
182
+
183
+ html = @debug_bar.render(binding)
184
+
185
+ html.should be_kind_of(String)
186
+ html.should_not be_empty
187
+
188
+ html.index('foo').should_not be_nil
189
+ html.index('bar').should_not be_nil
190
+ end
191
+
192
+ it 'should interpret nil opts from callback as {}' do
193
+ @debug_bar.add {|b| ['foo', 'bar', nil]}
194
+
195
+ html = @debug_bar.render(binding)
196
+
197
+ html.should be_kind_of(String)
198
+ html.should_not be_empty
199
+
200
+ html.index('foo').should_not be_nil
201
+ html.index('bar').should_not be_nil
202
+ end
203
+
204
+ it 'should default :hidden option to true if not explicitly given and session remembers it as open based on id' do
205
+ # Emulate saving of open callback box ids in cookies.
206
+ cookies = {:debug_bar => 'rory_williams,amelia_pond'}
207
+ # Put into binding.
208
+ b = binding
209
+
210
+ html_rory = DebugBar::Base.new.add {|b| ['foo', 'bar', :id => 'rory_williams']}.render(b)
211
+ html_rose = DebugBar::Base.new.add {|b| ['foo', 'bar', :id => 'rose_tyler']}.render(b)
212
+
213
+ # Extract the classes from the first div that has a dbar-content class in it.
214
+ extract_classes = lambda do |html|
215
+ m = /<div[^>]+class=['"]([^>'"]*dbar-content[^>'"]*)[^>]+>/.match(html)
216
+ return m.to_a[1].to_s.split(/\s+/)
217
+ end
218
+
219
+ html_rory.should include('show')
220
+ html_rose.should_not include('show')
221
+ end
222
+
223
+ it 'should recognize :hidden options' do
224
+ html_hidden = DebugBar::Base.new.add {|b| ['foo', 'bar', :hidden => true]}.render(binding)
225
+ html_nohide = DebugBar::Base.new.add {|b| ['foo', 'bar', :hidden => false]}.render(binding)
226
+
227
+ # Extract the classes from the first div that has a dbar-content class in it.
228
+ extract_classes = lambda do |html|
229
+ m = /<div[^>]+class=['"]([^>'"]*dbar-content[^>'"]*)[^>]+>/.match(html)
230
+ return m.to_a[1].to_s.split(/\s+/)
231
+ end
232
+
233
+ extract_classes.call(html_hidden).should_not include('show')
234
+ extract_classes.call(html_nohide).should include('show')
235
+ end
236
+
237
+ end
238
+
239
+ describe 'recipe books' do
240
+
241
+ class TestBook < DebugBar::RecipeBook::Base
242
+ def duplicated_recipe
243
+ Proc.new {|b| :test_book_duplicated_recipe}
244
+ end
245
+
246
+ def beta_recipe
247
+ Proc.new {|b| :test_book_beta_recipe}
248
+ end
249
+
250
+ def some_helper
251
+ end
252
+ end
253
+
254
+ class AnotherTestBook < DebugBar::RecipeBook::Base
255
+ def duplicated_recipe
256
+ Proc.new {|b| :another_test_book_duplicated_recipe}
257
+ end
258
+
259
+ def gamma_recipe
260
+ Proc.new {|b| :another_test_book_gamma_recipe}
261
+ end
262
+ end
263
+
264
+ before(:each) do
265
+ @book_class = TestBook # TODO: This is a poor choice, we should create one just for testing.
266
+ @book = @book_class.new
267
+
268
+ @base_debug_bar = DebugBar::Base.new
269
+ @debug_bar = DebugBar::Base.new {|bar| bar.add_recipe_book(TestBook); bar.add_recipe_book(AnotherTestBook)}
270
+ end
271
+
272
+ it 'should add recipe books by class' do
273
+ @base_debug_bar.add_recipe_book(@book_class)
274
+
275
+ @base_debug_bar.recipe_books.length.should == 1
276
+ @base_debug_bar.recipe_books.first.should be_kind_of(@book_class)
277
+ end
278
+
279
+ it 'should add recipe books by instance' do
280
+ @base_debug_bar.add_recipe_book(@book)
281
+
282
+ @base_debug_bar.recipe_books.length.should == 1
283
+ @base_debug_bar.recipe_books.first.should be_kind_of(@book_class)
284
+ end
285
+
286
+ it 'should return the list of known recipes' do
287
+ @debug_bar.recipes.sort.should == [:beta, :duplicated, :gamma].sort
288
+ end
289
+
290
+ it 'should return a recipe callback if one is found' do
291
+ callback = @debug_bar.recipe_callback(:beta)
292
+ callback.should be_kind_of(Proc)
293
+ callback.call.should == :test_book_beta_recipe
294
+ end
295
+
296
+ it 'should raise an Argument Error if a recipe is not found' do
297
+ lambda {@debug_bar.recipe_callback(:zeta)}.should raise_error(ArgumentError)
298
+ end
299
+
300
+ it 'should use the last found instance of a recipe when it is duplicated' do
301
+ callback = @debug_bar.recipe_callback(:duplicated)
302
+ callback.should be_kind_of(Proc)
303
+ callback.call.should == :another_test_book_duplicated_recipe
304
+ end
305
+
306
+ end
307
+
308
+ describe 'subclass overrides' do
309
+
310
+ class SubclassTestBook < DebugBar::RecipeBook::Base
311
+
312
+ def beta_recipe
313
+ Proc.new {|b| :test_book_beta_recipe}
314
+ end
315
+
316
+ end
317
+
318
+ class SubclassInstanceTestBook < DebugBar::RecipeBook::Base
319
+
320
+ def foo_recipe
321
+ Proc.new {|b| :instance_test_book_foo_recipe}
322
+ end
323
+
324
+ end
325
+
326
+ class SubclassTestBar < DebugBar::Base
327
+
328
+ private
329
+
330
+ def default_recipe_books
331
+ return [SubclassTestBook, SubclassInstanceTestBook.new]
332
+ end
333
+
334
+ def default_recipes
335
+ return [:beta]
336
+ end
337
+
338
+ def template_search_paths
339
+ return :standin_for_an_array_of_pathnames
340
+ end
341
+
342
+ end
343
+
344
+ before(:each) do
345
+ @debug_bar = SubclassTestBar.new
346
+ end
347
+
348
+ it 'should add recipe books from the default recipe books method' do
349
+ @debug_bar.recipe_books.length.should == 2
350
+ @debug_bar.recipe_books.each {|book| book.should be_kind_of(DebugBar::RecipeBook::Base)}
351
+ end
352
+
353
+ it 'should add recipes from the default recipe method' do
354
+ @debug_bar.recipes.sort.should == [:beta, :foo].sort
355
+ end
356
+
357
+ it 'should allow overriding of the template path' do
358
+ @debug_bar.send(:template_search_paths).should == :standin_for_an_array_of_pathnames
359
+ end
360
+
361
+ end
362
+
363
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ class TestContext
4
+ TARDIS = :tardis
5
+ @@gallifrey = :gallifrey
6
+ $the_doctor = :the_doctor
7
+
8
+ def get_binding
9
+ amelia_pond = :amelia_pond
10
+ @river_song = :river_song
11
+
12
+ return binding
13
+ end
14
+ end
15
+
16
+ describe DebugBar::Ext::Binding do
17
+
18
+ describe '[] method' do
19
+
20
+ before(:all) do
21
+ @binding = TestContext.new.get_binding
22
+ @binding.extend(DebugBar::Ext::Binding)
23
+ end
24
+
25
+ it 'should exist on binding' do
26
+ @binding.should respond_to(:[])
27
+ end
28
+
29
+ it 'should get local variables' do
30
+ @binding[:amelia_pond].should == :amelia_pond
31
+ @binding['amelia_pond'].should == :amelia_pond
32
+ end
33
+
34
+ it 'should get instance varaibles' do
35
+ @binding[:@river_song].should == :river_song
36
+ @binding['@river_song'].should == :river_song
37
+ end
38
+
39
+ it 'should get global variables' do
40
+ @binding[:$the_doctor].should == :the_doctor
41
+ @binding['$the_doctor'].should == :the_doctor
42
+ end
43
+
44
+ it 'should get constants' do
45
+ @binding[:TARDIS].should == :tardis
46
+ @binding['TARDIS'].should == :tardis
47
+ end
48
+
49
+ it 'should get class variables' do
50
+ @binding[:@@gallifrey].should == :gallifrey
51
+ @binding['@@gallifrey'].should == :gallifrey
52
+ end
53
+
54
+ it 'should not perform arbirary code' do
55
+ lambda {@binding['1+1']}.should raise_error(NameError)
56
+ lambda {@binding['amelia_pond.class']}.should raise_error(NameError)
57
+ lambda {@binding['def foo']}.should raise_error(NameError)
58
+ end
59
+
60
+ end
61
+
62
+ end