require "cgi" require "erb" Dir[File.join(File.dirname(__FILE__), "dt/**/*.rb")].each {|fn| require fn} # Debug toolkit. # # Allows to print debug messages from anywhere in your Rails project. Do a: # # DT.p "Hello, world!" # # , and see the message in log, <tt>log/dt.log</tt>, console and web. # # To set up web output, in your application root do a: # # $ rails generate rails_dt # Rails 3 # $ script/generate rails_dt # Rails 2 # # Follow the instructions the generator gives you then. module DT #:doc: # Maximum number of stored messages, if they're not cleared. # If web output is configured, messages are cleared before every request. MAX_WEB_MESSAGES = 100 # Initializer. def self._initialize #:nodoc: clear_web_messages # NOTES: # * Stuffing is inserted in order to work around buggy RDoc parser. # "a""bc" => "abc", just in case. # * Don't forget to update generator/initializers/dt.rb with these. # * "Canonical" order of imporance: log, console, web. @log_prefix = "[DT <""%= file_rel %>:<""%= line %>] " @console_prefix = @log_prefix.dup @web_prefix = '<a href="txmt://open?url=file://<''%= file %>&line=<''%= line %>"><''%= file_rel %>:<''%= line %></a> ' # In case of path problems @log will be nil. @log = Logger.new(Rails.root + "log/dt.log") rescue nil end # On-the-fly initializer. def self._otf_init #:nodoc: # Consider job done, replace self with a blank. class_eval { def self._otf_init #:nodoc: end } _initialize end # Set message prefix for console. See <tt>log_prefix=</tt>. def self.console_prefix=(s) _otf_init @console_prefix = s end def self.console_prefix _otf_init @console_prefix end # Set logger to use. Must be a <tt>Logger</tt>. # # log = Logger.new("log/my.log") def self.log=(obj) _otf_init raise "Logger expected, #{obj.class} given" if not obj.is_a? Logger @log = obj end def self.log _otf_init @log end # Set message prefix for log. Syntax is ERB. # # log_prefix = "[DT <""%= file_rel %>:<""%= line %>] " # # NOTE: In the above example some stuffing was made to satisfy the buggy RDoc parser. # Just in case, <tt>"a""bc"</tt> is <tt>"abc"</tt> in Ruby. # # Template variables: # # * <tt>file</tt> -- full path to file. # * <tt>file_base</tt> -- file base name. # * <tt>file_rel</tt> -- file name relative to Rails application root. # * <tt>line</tt> -- line number. # # By setting prefix to <tt>nil</tt> you disable respective output. # # web_prefix = nil # Disable web output. def self.log_prefix=(s) _otf_init @log_prefix = s end def self.log_prefix _otf_init @log_prefix end # Return messages accumulated since last cleared. def self.web_messages _otf_init @web_messages end # Set message prefix for web. See <tt>log_prefix=</tt>. def self.web_prefix=(s) _otf_init @web_prefix = s end def self.web_prefix _otf_init @web_prefix end #--------------------------------------- # Clear messages. def self.clear_web_messages _otf_init @web_messages = [] end # Print a debug message or dump a value. Somewhat similar to Ruby's native <tt>p</tt>. # # p "Hello, world!" # p "myvar", myvar def self.p(*args) _otf_init # Fetch caller information. # NOTE: May be lacking file information, e.g. when in an irb session. file, line = caller.first.split(":") # Assign template variables. hc = { :file => file, :line => line, :file_base => (begin; File.basename(file); rescue; file; end), :file_rel => (begin; Pathname(file).relative_path_from(Rails.root).to_s; rescue; file; end), } args.each do |r| s = r.is_a?(String) ? r : r.inspect # To log. if @log_prefix ##Kernel.p "@log", @log #DEBUG if @log pfx = ERB.new(@log_prefix, nil, "-").result(_hash_kbinding(hc)) msg = [pfx, s].join @log.info msg Rails.logger.info msg rescue nil # In case something's wrong with `Rails.logger`. end end # To console. if @console_prefix pfx = ERB.new(@console_prefix, nil, "-").result(_hash_kbinding(hc)) puts [pfx, s].join end # To web. if @web_prefix pfx = ERB.new(@web_prefix, nil, "-").result(_hash_kbinding(hc)) pcs = [] pcs << pfx pcs << CGI.escapeHTML(s).gsub("\n", "<br/>\n") @web_messages << pcs.join # Rotate messages. @web_messages.slice!(0..-(MAX_WEB_MESSAGES + 1)) end end # Be like `puts`, return nil. nil end # Format accumulated web messages as HTML. Usually called from a view template. # # web_messages_as_html # => Something like "<ul><li>Message 1</li><li>Message 2</li>...</ul>". def self.web_messages_as_html _otf_init pcs = [] pcs << "<ul>" @web_messages.each do |s| pcs << ["<li>", s, "</li>"].join end pcs << "</ul>" if (out = pcs.join).respond_to? :html_safe out.html_safe else out end end #--------------------------------------- # NOTE: Singletons can't be private, so mark them syntactically. # Turn hash's entries into locals and return binding. # Useful for simple templating. def self._hash_kbinding(h) #:nodoc: # NOTE: This IS important, since assignment is eval'd in this context. bnd = binding _value = nil h.each do |k, v| ##puts "-- k-#{k.inspect} v-#{_value.inspect}" #DEBUG _value = v # IMPORTANT: Ruby 1.9 compatibility hack. eval("#{k} = _value", bnd) end bnd end # DO NOT invoke `_initialize` load-time, it won't see Rails3 stuff. end # DT