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.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ Gemfile.lock
3
+ *.swp
4
+ *.swo
5
+ pkg/
6
+ rdoc/
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
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'gemtools'
6
+
7
+ group :development do
8
+ gem 'rake'
9
+ gem 'ruby-debug19', :require => 'ruby-debug'
10
+ end
11
+
12
+ group :test do
13
+ gem 'rspec'
14
+ gem 'vcr'
15
+ gem 'webmock'
16
+ end
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 '&lt;'. (A proper implementation
68
+ would escape all escapable entities.)
69
+ debug_bar.add do |binding|
70
+ body = binding.eval('params').inspect.gsub('<','&lt;')
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,3 @@
1
+ require_relative 'ext/binding'
2
+ require_relative 'ext/object'
3
+ require_relative 'ext/string'
@@ -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('<','&lt;'), :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