debug-bar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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