debug-bar 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rbenv-version +1 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/README.rdoc +192 -0
- data/Rakefile +27 -0
- data/debug-bar.gemspec +24 -0
- data/lib/debug-bar/base.rb +208 -0
- data/lib/debug-bar/default.rb +26 -0
- data/lib/debug-bar/ext/binding.rb +17 -0
- data/lib/debug-bar/ext/object.rb +20 -0
- data/lib/debug-bar/ext/string.rb +18 -0
- data/lib/debug-bar/ext.rb +3 -0
- data/lib/debug-bar/recipe_book/base.rb +104 -0
- data/lib/debug-bar/recipe_book/default.rb +34 -0
- data/lib/debug-bar/recipe_book.rb +2 -0
- data/lib/debug-bar/version.rb +3 -0
- data/lib/debug-bar.rb +6 -0
- data/lib/templates/callback_box.html.erb +12 -0
- data/lib/templates/error.html.erb +6 -0
- data/lib/templates/layout.html.erb +142 -0
- data/spec/base_spec.rb +363 -0
- data/spec/binding_spec.rb +62 -0
- data/spec/recipe_book_spec.rb +161 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/templates/basic.html.erb +1 -0
- data/spec/support/templates/content.html.erb +1 -0
- metadata +116 -0
data/.gitignore
ADDED
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.2
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format documentation spec
|
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
= Overview
|
2
|
+
|
3
|
+
DebugBar offers an easy way to show developer/debugging information on a a web
|
4
|
+
page in an unobtrustive way. It features keyboard shortcuts for showing/hiding
|
5
|
+
the bar (Ctrl-~), and memory (through cookies) of which information to show.
|
6
|
+
|
7
|
+
DebugBar uses a modular architecture utilizing callback lambdas to produce
|
8
|
+
the contents of the DebugBar.
|
9
|
+
|
10
|
+
DebugBar::Base is the base debug bar, suitable for use in any application, and
|
11
|
+
comes without any pre-added callbacks.
|
12
|
+
|
13
|
+
DebugBar::Default is a default debug bar suitible for a typical Rails application,
|
14
|
+
and includes several default callbacks meaningful in such an environment.
|
15
|
+
|
16
|
+
= Installation
|
17
|
+
|
18
|
+
To use DebugBar, your application must include jQuery in rendered pages.
|
19
|
+
|
20
|
+
Typically, Rails applications create a debug_bar initializer that defines a
|
21
|
+
DEBUG_BAR constant which refers to the configured DebugBar instance. Customization
|
22
|
+
and setup are typically done in this file.
|
23
|
+
|
24
|
+
= Examples
|
25
|
+
|
26
|
+
== Basic Usage
|
27
|
+
|
28
|
+
Typically the debug bar is added to your layout template with the following code
|
29
|
+
DebugBar::Default.new.render(binding)
|
30
|
+
though it is typically better to pre-instantiate your debug bar instance as a
|
31
|
+
constant in an initializer, and then reference it in your layout, like so:
|
32
|
+
|
33
|
+
# In your initializer
|
34
|
+
DEBUG_BAR = DebugBar::Default.new do |debug_bar|
|
35
|
+
# Do additional setup, such as registering custom recipe books and callbacks here.
|
36
|
+
end
|
37
|
+
|
38
|
+
# In your layout view template
|
39
|
+
DEBUG_BAR.render(binding)
|
40
|
+
|
41
|
+
Additionally, it is common to include code that controls the optional rendering
|
42
|
+
of the debug bar based on environement and/or parameters; for example
|
43
|
+
DEBUG_BAR.render(binding) if Rails.env=='development' || params.include?(:debugger)
|
44
|
+
could be used in Rails.
|
45
|
+
|
46
|
+
== Custom Callbacks
|
47
|
+
|
48
|
+
While there are a basic set of callbacks available, the real power of DebugBar
|
49
|
+
is the ability to add custom callbacks.
|
50
|
+
|
51
|
+
In the context of a Rails application, custom callbacks are typically added
|
52
|
+
to the config/initializer where the debug_bar is instantiated.
|
53
|
+
|
54
|
+
=== Basic
|
55
|
+
|
56
|
+
Custom callbacks are typically Proc objects that take an evaluation binding
|
57
|
+
context as an argument, and produce an array of the form of two to three elements:
|
58
|
+
|
59
|
+
[title] The display title for this callback box.
|
60
|
+
[body] The raw HTML string to render.
|
61
|
+
[opts] A hash of options to pass to the renderer, usually used to control box layout options. This is optional.
|
62
|
+
|
63
|
+
Thus, if one wanted a debug box to display the time, one might do
|
64
|
+
debug_bar.add {|binding| ['Time', Time.now.to_s]}
|
65
|
+
|
66
|
+
A more complex example would to output the params hash. Note that since the output
|
67
|
+
is raw HTML, we must replace all instances of '<' with '<'. (A proper implementation
|
68
|
+
would escape all escapable entities.)
|
69
|
+
debug_bar.add do |binding|
|
70
|
+
body = binding.eval('params').inspect.gsub('<','<')
|
71
|
+
['Params', body]
|
72
|
+
end
|
73
|
+
|
74
|
+
Note that we using binding.eval to extract variable names from the binding by
|
75
|
+
executing snippets of code. This raises two points:
|
76
|
+
* Any code can be evaluated in the binding in this
|
77
|
+
manor, thus choice of render binding has a major impact on the information
|
78
|
+
available for display.
|
79
|
+
* As a convenience, variables can be extracted from the binding with the <code>[]</code> method,
|
80
|
+
thus
|
81
|
+
body = binding[:params].inspect
|
82
|
+
could be substituted for
|
83
|
+
body = binding.eval('params').inspect
|
84
|
+
|
85
|
+
|
86
|
+
=== Rails Render
|
87
|
+
|
88
|
+
If rendering the DebugBar from within a Rails template (e.g. the application
|
89
|
+
layout), you can use Rails render commands in the callback via a binding.eval
|
90
|
+
to render any template and output the results to the debug bar. This is
|
91
|
+
convenient way to render any input, though the use of custom recipe books
|
92
|
+
should be considered if you do this often.
|
93
|
+
|
94
|
+
=== Options
|
95
|
+
|
96
|
+
Callbacks may provide the following options:
|
97
|
+
|
98
|
+
[:id] The HTML id for the callback for use with custom javascript hooks, and remembered settings.
|
99
|
+
[:hidden] Controls default state of disclosure arrow for the callback's content.
|
100
|
+
Note that if an :id is provided, the state can be remembered between requests.
|
101
|
+
|
102
|
+
=== UI Tools
|
103
|
+
|
104
|
+
Note that debug-bar callbacks can use the 'toggle-switch' and 'toggle-content'
|
105
|
+
classes to drive toggleable hide/show behavior inside of the debug-bar. To
|
106
|
+
do so, add the 'toggle-switch' class to the link that causes toggle, and the
|
107
|
+
'toggle-content' class to the <i>sibling</i> element that will toggle.
|
108
|
+
|
109
|
+
For example:
|
110
|
+
|
111
|
+
<div>
|
112
|
+
<a href="" class="toggle-switch">Details</a>
|
113
|
+
<div class="toggle-content" style="display:none">
|
114
|
+
Lot's of content here.
|
115
|
+
</div>
|
116
|
+
</div>
|
117
|
+
|
118
|
+
This would present a "Details" link that would reveal the content div.
|
119
|
+
|
120
|
+
== Custom Recipes
|
121
|
+
|
122
|
+
While it is convenient to define one-off callbacks directly via add, it is
|
123
|
+
often both cleaner and more useful to create re-usable callback recipes in
|
124
|
+
RecipeBooks. Additionally, RecipeBooks provide some convenience methods not
|
125
|
+
available in your own initializers.
|
126
|
+
|
127
|
+
Essentially, subclasses of RecipeBook::Base are factory classes that contain
|
128
|
+
instance methods that generate Proc objects that are used as callbacks. What
|
129
|
+
makes RecipeBook special over any random factory class is the convenience
|
130
|
+
methods it gives and the tight integration with DebugBar::Base instances.
|
131
|
+
|
132
|
+
=== Setup
|
133
|
+
|
134
|
+
One can manually add a RecipeBook by class or instance using the +add_recipe_book+
|
135
|
+
method. For example
|
136
|
+
debug_bar.add_recipe_book(DebugBar::RecipeBook::Default)
|
137
|
+
or
|
138
|
+
book = DebugBar::RecipeBook::Default.new
|
139
|
+
debug_bar.add_recipe_book(book)
|
140
|
+
|
141
|
+
=== Creating Your Own
|
142
|
+
|
143
|
+
To create your own recipe book, simply subclass another recipe book and add
|
144
|
+
recipe generation inatance methods that follow a few simple rules:
|
145
|
+
1. They must be the recipe name suffixed with `_recipe'.
|
146
|
+
2. They must be able to support being called with no elements to support
|
147
|
+
short-hand addition of a recipe. (This does no preclude supporting optional
|
148
|
+
arguments and manual addition of the generated block.
|
149
|
+
|
150
|
+
Recipes in a RecipeBook::Base subclass get access to special functionality, such
|
151
|
+
as being able to use templates at class-defined locations.
|
152
|
+
|
153
|
+
= Contact
|
154
|
+
|
155
|
+
Jeff Reinecke <jreinecke@whitepages.com>
|
156
|
+
Keith Stone <kstone@whitepages.com>
|
157
|
+
|
158
|
+
= Feature Roadmap
|
159
|
+
|
160
|
+
* No future features expected at this time.
|
161
|
+
|
162
|
+
= Version History
|
163
|
+
|
164
|
+
[1.0.0 - 2012-Jul-13] Initial Release.
|
165
|
+
|
166
|
+
= License
|
167
|
+
|
168
|
+
Copyright (c) 2012, WhitePages, Inc.
|
169
|
+
All rights reserved.
|
170
|
+
|
171
|
+
Redistribution and use in source and binary forms, with or without
|
172
|
+
modification, are permitted provided that the following conditions are met:
|
173
|
+
* Redistributions of source code must retain the above copyright
|
174
|
+
notice, this list of conditions and the following disclaimer.
|
175
|
+
* Redistributions in binary form must reproduce the above copyright
|
176
|
+
notice, this list of conditions and the following disclaimer in the
|
177
|
+
documentation and/or other materials provided with the distribution.
|
178
|
+
* Neither the name of the company nor the
|
179
|
+
names of its contributors may be used to endorse or promote products
|
180
|
+
derived from this software without specific prior written permission.
|
181
|
+
|
182
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
183
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
184
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
185
|
+
DISCLAIMED. IN NO EVENT SHALL WHITEPAGES, INC. BE LIABLE FOR ANY
|
186
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
187
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
188
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
189
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
190
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
191
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
192
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
3
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
4
|
+
require 'pathname'
|
5
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../Gemfile", Pathname.new(__FILE__).realpath)
|
6
|
+
require 'rubygems'
|
7
|
+
require 'bundler/setup'
|
8
|
+
require 'gemtools/rake_task'
|
9
|
+
|
10
|
+
require 'rake/dsl_definition'
|
11
|
+
require "rspec/core/rake_task"
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
14
|
+
spec.rspec_opts = ['--backtrace']
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
18
|
+
Gemtools::RakeTask.install_tasks
|
19
|
+
|
20
|
+
task :default => :spec
|
21
|
+
|
22
|
+
require 'rake/rdoctask'
|
23
|
+
|
24
|
+
Rake::RDocTask.new do |rdoc|
|
25
|
+
rdoc.rdoc_dir = "rdoc"
|
26
|
+
rdoc.rdoc_files.add "lib/**/*.rb", "README.rdoc"
|
27
|
+
end
|
data/debug-bar.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'debug-bar/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'debug-bar'
|
7
|
+
s.version = DebugBar::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Jeff Reinecke', 'Keith Stone']
|
10
|
+
s.email = ['jreinecke@whitepages.com', 'kstone@whitepages.com']
|
11
|
+
s.homepage = 'https://github.com/whitepages/debug-bar'
|
12
|
+
s.summary = 'Debug Bar'
|
13
|
+
s.description = 'Base generic debug bar implementation.'
|
14
|
+
s.licenses = ['BSD']
|
15
|
+
|
16
|
+
s.add_dependency 'erubis'
|
17
|
+
s.add_dependency 'activesupport'
|
18
|
+
s.add_dependency 'awesome_print'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'pathname'
|
3
|
+
require 'erubis'
|
4
|
+
|
5
|
+
|
6
|
+
require_relative 'ext'
|
7
|
+
|
8
|
+
# DebugBar is the module namespace for this gem. For the DebugBar base class,
|
9
|
+
# see DebugBar::Base.
|
10
|
+
module DebugBar
|
11
|
+
# = Overview
|
12
|
+
#
|
13
|
+
# DebugBar::Base provides the base methods for all debug bars.
|
14
|
+
#
|
15
|
+
# At it's core, a DebugBar is instantiated with +initialize+, gets callbacks
|
16
|
+
# added with +add_callback+, and then is rendered with +render+.
|
17
|
+
#
|
18
|
+
# Additionally, RecipeBook classes or instance may be added to the DebugBar
|
19
|
+
# via +add_recipe_book+ so that pre-made callbacks may be easily added to the
|
20
|
+
# DebugBar instance via add_callbacks.
|
21
|
+
#
|
22
|
+
# See the README for example usage.
|
23
|
+
#
|
24
|
+
# = Subclassing
|
25
|
+
#
|
26
|
+
# This class is often subclassed to give DebugBars with special behaviors.
|
27
|
+
# If you make a subclass, define <b>private</b> overrides to these methods:
|
28
|
+
# [+default_recipe_books+] Provide a list of recipe books to make available to all instances.
|
29
|
+
# [+default_recipes+] Add a list of recipe callbacks to all instances.
|
30
|
+
# [+template_search_paths+] Override the default formatting template search path.
|
31
|
+
class Base
|
32
|
+
|
33
|
+
# The search path for formatting templates, such as the layout and callback box.
|
34
|
+
# NOTE: This is separate from templates that are used in recipes!
|
35
|
+
TEMPLATE_SEARCH_PATHS = [
|
36
|
+
(Pathname.new(__FILE__).dirname + '../templates')
|
37
|
+
].map {|path| path.expand_path}
|
38
|
+
|
39
|
+
# Initialize a new debug bar. This may optionally take
|
40
|
+
# one or more recipe symbols as arguments.
|
41
|
+
def initialize(*recipes)
|
42
|
+
#Initialize registration variables.
|
43
|
+
@callbacks = []
|
44
|
+
@recipe_books = []
|
45
|
+
# Register defaults.
|
46
|
+
default_recipe_books.each {|book| add_recipe_book(book)}
|
47
|
+
default_recipes.each {|recipe| add_recipe(recipe)}
|
48
|
+
# Give a chance for custom configuration, including addition of books.
|
49
|
+
yield self if block_given?
|
50
|
+
# Now we can add user listed recipes.
|
51
|
+
recipes.each {|recipe| add_recipe(recipe)}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a copy of the raw list of callbacks.
|
55
|
+
attr_reader :callbacks
|
56
|
+
|
57
|
+
# Returns a copy of the list of recipe book instances.
|
58
|
+
attr_reader :recipe_books
|
59
|
+
|
60
|
+
# Adds a recipe book class or instance to the recipe book list for
|
61
|
+
# this debug bar.
|
62
|
+
#
|
63
|
+
# Returns self to support functional programming styles.
|
64
|
+
def add_recipe_book(book)
|
65
|
+
@recipe_books << (book.kind_of?(Class) ? book.new : book)
|
66
|
+
return self
|
67
|
+
end
|
68
|
+
alias_method :add_book, :add_recipe_book
|
69
|
+
|
70
|
+
# Returns the list of recipes recognized by this debug bar.
|
71
|
+
def recipes
|
72
|
+
return @recipe_books.inject([]) {|list,book| list | book.recipes}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the most recently added occurance of the given recipe.
|
76
|
+
def recipe_callback(recipe, *args, &block)
|
77
|
+
book = @recipe_books.reverse.find {|book| book.include?(recipe)}
|
78
|
+
raise ArgumentError, "Could not find recipe #{recipe.inspect}", caller if book.nil?
|
79
|
+
return book.recipe(recipe, *args, &block)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds a callback.
|
83
|
+
#
|
84
|
+
# Takes either a recipe (by symbol) or a block.
|
85
|
+
#
|
86
|
+
# The block takes a single argument, the binding of the render context,
|
87
|
+
# and should return either a string, or an array of [title, content, opts].
|
88
|
+
#
|
89
|
+
# Advanced users can call a recipe by name, and provide additional arguments
|
90
|
+
# to configure the recipe further. These arguments are defined by the
|
91
|
+
# recipe factory method, but usually are via an options hash and/or a block.
|
92
|
+
#
|
93
|
+
# Returns self to support functional programming styles.
|
94
|
+
def add_callback(recipe=nil, *args, &callback)
|
95
|
+
callback_proc = recipe.nil? ? callback : recipe_callback(recipe, *args, &callback)
|
96
|
+
raise ArgumentError, "Expected callback to respond to `call': #{callback_proc.inspect}", caller unless callback_proc.respond_to?(:call)
|
97
|
+
@callbacks << callback_proc
|
98
|
+
return self
|
99
|
+
end
|
100
|
+
alias_method :add_recipe, :add_callback
|
101
|
+
alias_method :add, :add_callback
|
102
|
+
|
103
|
+
# Renders the debug bar with the given binding.
|
104
|
+
def render(eval_binding)
|
105
|
+
# Decorate the binding here (NOT in private methods where we don't want automatic behavior)!
|
106
|
+
eval_binding.extend(DebugBar::Ext::Binding)
|
107
|
+
return render_layout(eval_binding)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# An initialization callback for adding default recipe books to instances;
|
113
|
+
# this should return an array of recipe book classes or instances.
|
114
|
+
#
|
115
|
+
# On the base class, this returns an empty array; subclasses should override this.
|
116
|
+
def default_recipe_books
|
117
|
+
return []
|
118
|
+
end
|
119
|
+
|
120
|
+
# An initialization callback for adding default recipes to the callbacks
|
121
|
+
# array.
|
122
|
+
#
|
123
|
+
# On the base class, this returns an empty array; subclasses should override this.
|
124
|
+
def default_recipes
|
125
|
+
return []
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the template search paths for this instance.
|
129
|
+
#
|
130
|
+
# Paths should be Pathname instances
|
131
|
+
#
|
132
|
+
# Subclasses may override this to change the search path for the formatting
|
133
|
+
# templates such as the layout and callback_box templates.
|
134
|
+
def template_search_paths
|
135
|
+
return TEMPLATE_SEARCH_PATHS
|
136
|
+
end
|
137
|
+
|
138
|
+
# Looks for the given remplate name within the template search paths, and
|
139
|
+
# returns a string containing its contents. The name may be a symbol or string.
|
140
|
+
#
|
141
|
+
# Template names automatically have '.html.erb' appended to them, so call
|
142
|
+
# read_template(:foo)
|
143
|
+
# instead of
|
144
|
+
# read_template('foo.html.erb')
|
145
|
+
def read_template(template)
|
146
|
+
template_name = "#{template}.html.erb"
|
147
|
+
template_path = template_search_paths.map {|base_path| (base_path + template_name).expand_path}.find {|p| p.exist? && p.file?}
|
148
|
+
raise ArgumentError, "Unknown template #{template_name.inspect}. Not in #{template_search_paths.inspect}", caller if template_path.nil?
|
149
|
+
return template_path.read
|
150
|
+
end
|
151
|
+
|
152
|
+
# Renders the callbacks and then renders the layout--all in the given
|
153
|
+
# binding--inserting the callbacks into the layout; returns an html_safe string.
|
154
|
+
def render_layout(eval_binding)
|
155
|
+
content = render_callbacks(@callbacks, eval_binding)
|
156
|
+
return Erubis::Eruby.new(read_template(:layout)).result(:content => content).html_safe
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the contactinated set of rendered callbacks usnig the given binding.
|
160
|
+
def render_callbacks(callbacks, eval_binding)
|
161
|
+
return @callbacks.map {|callback| render_callback(callback, eval_binding)}.join("\n")
|
162
|
+
end
|
163
|
+
|
164
|
+
# Renders the given callback in the given binding.
|
165
|
+
def render_callback(callback, eval_binding)
|
166
|
+
# Get the result of the callback
|
167
|
+
obj = begin
|
168
|
+
callback.respond_to?(:call) ? callback.call(eval_binding) : callback
|
169
|
+
rescue Exception => error
|
170
|
+
render_error_callback(error)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Extract the title, content, and opts from the result
|
174
|
+
title, content, opts = case obj
|
175
|
+
when Array
|
176
|
+
obj
|
177
|
+
else
|
178
|
+
['Debug', obj.to_s, {}]
|
179
|
+
end
|
180
|
+
opts ||= {}
|
181
|
+
|
182
|
+
# reverse merge the opts
|
183
|
+
default_hidden = opts[:id].nil? ? false : !cookie_include?(opts[:id], eval_binding)
|
184
|
+
opts = {:hidden => default_hidden}.merge(opts||{})
|
185
|
+
|
186
|
+
# Render the callback in a box
|
187
|
+
return Erubis::Eruby.new( read_template(:callback_box) ).result(:title => title, :content => content, :opts => opts).html_safe
|
188
|
+
end
|
189
|
+
|
190
|
+
def render_error_callback(error, opts={})
|
191
|
+
return [
|
192
|
+
opts.fetch(:title, '**ERROR'),
|
193
|
+
Erubis::Eruby.new(read_template(:error)).result(:error => error).html_safe,
|
194
|
+
{}
|
195
|
+
]
|
196
|
+
end
|
197
|
+
|
198
|
+
# A helper method that--if the eval_binding defines a cookies hash, and
|
199
|
+
# that hash has a :debug_bar key, returns true if it contains the given
|
200
|
+
# id; otherwise it returns false.
|
201
|
+
#
|
202
|
+
# TODO: This code should be refactored to support more use cases as they appear.
|
203
|
+
def cookie_include?(id, eval_binding)
|
204
|
+
debug_bar = eval_binding.eval("defined?(cookies) && cookies[:debug_bar]")
|
205
|
+
debug_bar.nil? ? false : debug_bar.split(',').include?(id)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DebugBar
|
2
|
+
# A default DebugBar implementation suitable for use in a Ruby On Rails
|
3
|
+
# application layout template.
|
4
|
+
#
|
5
|
+
# This, of course, may be customized, typically by creating an initializer
|
6
|
+
# file at config/initializers/debug_bar.rb, and populating like so:
|
7
|
+
#
|
8
|
+
# DEBUG_BAR = DebugBar::Default.new do |bar|
|
9
|
+
# bar.add {|b| ['Time', Time.now]}
|
10
|
+
# end
|
11
|
+
class Default < Base
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Override superclass method to provide the necessary cookbook.
|
16
|
+
def default_recipe_books
|
17
|
+
return [RecipeBook::Default]
|
18
|
+
end
|
19
|
+
|
20
|
+
# The recipes added to this debug bar by default.
|
21
|
+
def default_recipes
|
22
|
+
return [:params, :session]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DebugBar
|
2
|
+
module Ext
|
3
|
+
# Binding extensions that are decorated onto bindings passed into callbacks.
|
4
|
+
module Binding
|
5
|
+
# A regex for matching only valid local, instance, class, and global variables, as well as constatns.
|
6
|
+
VARIABLE_PATTERN = /^(@{1,2}|\$)?([_a-zA-Z]\w*)$/
|
7
|
+
|
8
|
+
# Returns the value of the given variable symbol within the binding.
|
9
|
+
# Supports local, instance, class, or global variables, as well as constants.
|
10
|
+
def [](var)
|
11
|
+
raise NameError, "#{var.inspect} is not a valid variable name" unless var.to_s =~ VARIABLE_PATTERN
|
12
|
+
return self.eval(var.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'awesome_print'
|
2
|
+
|
3
|
+
module DebugBar
|
4
|
+
module Ext
|
5
|
+
module Object
|
6
|
+
|
7
|
+
def awesome_print(opts={})
|
8
|
+
return self.ai(opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def awesome_print_html
|
12
|
+
return self.awesome_print(:html => true, :indent => -3)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
Object.send(:include, DebugBar::Ext::Object)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module DebugBar
|
4
|
+
module Ext
|
5
|
+
module String
|
6
|
+
|
7
|
+
def html_escape
|
8
|
+
return CGI.escapeHTML(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
unless( String.methods.include?(:html_escape) )
|
17
|
+
String.send(:include, DebugBar::Ext::String)
|
18
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module DebugBar
|
4
|
+
# RecipeBook is the module namespace for the RecipeBooks provided in this
|
5
|
+
# gem. For the base RecipeBook, see RecipeBook::Base.
|
6
|
+
module RecipeBook
|
7
|
+
# The base class for all recipe subclasses. Provides common convenience
|
8
|
+
# methods for recipe use. Essentially, these are factory methods that
|
9
|
+
# lazy generate common configurable callbacks on demand.
|
10
|
+
#
|
11
|
+
# Subclasses need only to define factory instance methods that meet the
|
12
|
+
# following rules:
|
13
|
+
# 1. The method name must be the recipe name suffixed with `_recipe'. So
|
14
|
+
# the recipe
|
15
|
+
# :foo
|
16
|
+
# would have method name
|
17
|
+
# foo_recipe
|
18
|
+
# 2. Recipe factory methods <b>MUST</b> return a valid callback when no arguments
|
19
|
+
# are given, that is
|
20
|
+
# book.foo_recipe()
|
21
|
+
# must work.
|
22
|
+
# 3. The result of a recipe factory method must be a Proc object that
|
23
|
+
# conforms to the requirements of the Procs registered with +add_callback+
|
24
|
+
# on the DebugBar::Base class.
|
25
|
+
# 4. Recipe methods <i>may</i> take an additional argument, which is an
|
26
|
+
# options hash for special configuration when using +add_callback+ on
|
27
|
+
# DebugBar::Base instances. For example, one can then us
|
28
|
+
#
|
29
|
+
# For example, the following recipe renders the params hash from the given
|
30
|
+
# binding:
|
31
|
+
#
|
32
|
+
# def params_recipe(opts={})
|
33
|
+
# Proc.new do |b|
|
34
|
+
# body = (opts[:formatter] == :pretty_inspect) ? b[:params].pretty_inspect : b[:params].inspect
|
35
|
+
# ['Params', body.gsub('<','<'), :hidden => (body.length>160)]
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# It could then be added to the DebugBar like so:
|
40
|
+
#
|
41
|
+
# debug_bar.add(:params)
|
42
|
+
# debug_bar.add(:params, :formatter => :pretty_inspect)
|
43
|
+
class Base
|
44
|
+
|
45
|
+
# Returns a list of recipes known to this class.
|
46
|
+
def recipes
|
47
|
+
return self.methods.select {|m| m.to_s =~ /_recipe$/}.map {|m| m.to_s.gsub(/_recipe$/,'').to_sym}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns true if the given recipe is known.
|
51
|
+
def include?(recipe)
|
52
|
+
return self.respond_to?("#{recipe}_recipe")
|
53
|
+
end
|
54
|
+
alias_method :has_recipe?, :include?
|
55
|
+
|
56
|
+
# Generates the given recipe.
|
57
|
+
# All recipes are expected to accept no arguments, but may optionally
|
58
|
+
# take more. Optional arguments given to this method are passed through
|
59
|
+
# to the recipe method.
|
60
|
+
def recipe(recipe, *args, &block)
|
61
|
+
return self.send("#{recipe}_recipe", *args, &block)
|
62
|
+
end
|
63
|
+
alias_method :[], :recipe
|
64
|
+
|
65
|
+
# Retrieves the template search paths for this recipe instance as
|
66
|
+
# fully expanded Pathname instances.
|
67
|
+
#
|
68
|
+
# While subclasses <i>may</i> override this method, it is preferrable
|
69
|
+
# for them to use the setter (+template_search_paths=+) during instance
|
70
|
+
# initialization, as the setter sanitizes the input.
|
71
|
+
def template_search_paths
|
72
|
+
return @template_search_paths ||= []
|
73
|
+
end
|
74
|
+
|
75
|
+
# Sets the template search paths for this recipe instance, converting
|
76
|
+
# to pathname objects as necessary.
|
77
|
+
def template_search_paths=(paths)
|
78
|
+
@template_search_paths = paths.map {|path| Pathname.new(path.to_s).expand_path }
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Renders the first matching template found in the search paths. Passed
|
84
|
+
# symbols/names are automatically suffixed with 'html.erb'. The template
|
85
|
+
# name may be a symbol or string.
|
86
|
+
#
|
87
|
+
# Optionally, one can pass in :locals, which is a hash of local variables
|
88
|
+
# to render in the template.
|
89
|
+
def render_template(template, opts={})
|
90
|
+
return Erubis::Eruby.new( read_template(template) ).result( opts.fetch(:locals, {}) ).html_safe
|
91
|
+
end
|
92
|
+
|
93
|
+
# Reads the given template and returns the string of its contents.
|
94
|
+
# The template name may be either a symbol or string.
|
95
|
+
def read_template(template)
|
96
|
+
template_name = "#{template}.html.erb"
|
97
|
+
template_path = template_search_paths.map {|base_path| (base_path + template_name).expand_path}.find {|p| p.exist? && p.file?}
|
98
|
+
raise ArgumentError, "Unknown template #{template_name.inspect}. Not in #{template_search_paths.inspect}", caller if template_path.nil?
|
99
|
+
return template_path.read
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|