From dfbebd488769738ac854adf37c930ad1d31bb94f Mon Sep 17 00:00:00 2001 From: Mike Dvorkin Date: Sat, 5 Feb 2011 11:31:51 -0800 Subject: [PATCH] Major refactoring: split AwesomePrint class into Inspector and Formatter --- lib/ap.rb | 3 +- lib/ap/awesome_print.rb | 332 --------------------------------- lib/ap/core_ext/kernel.rb | 2 +- lib/ap/core_ext/logger.rb | 19 +- lib/ap/formatter.rb | 238 +++++++++++++++++++++++ lib/ap/inspector.rb | 122 ++++++++++++ lib/ap/mixin/action_view.rb | 53 +++--- lib/ap/mixin/active_record.rb | 72 +++---- lib/ap/mixin/active_support.rb | 62 +++--- lib/awesome_print.rb | 3 +- spec/action_view_spec.rb | 2 +- spec/active_record_spec.rb | 137 +++++++------- spec/active_support_spec.rb | 22 +++ spec/awesome_print_spec.rb | 20 +- spec/logger_spec.rb | 4 +- 15 files changed, 568 insertions(+), 523 deletions(-) delete mode 100755 lib/ap/awesome_print.rb create mode 100755 lib/ap/formatter.rb create mode 100755 lib/ap/inspector.rb create mode 100755 spec/active_support_spec.rb diff --git a/lib/ap.rb b/lib/ap.rb index 835355b..3922810 100755 --- a/lib/ap.rb +++ b/lib/ap.rb @@ -7,7 +7,8 @@ require File.dirname(__FILE__) + "/ap/core_ext/#{file}" end -require File.dirname(__FILE__) + "/ap/awesome_print" +require File.dirname(__FILE__) + "/ap/inspector" +require File.dirname(__FILE__) + "/ap/formatter" require File.dirname(__FILE__) + "/ap/core_ext/logger" if defined?(Logger) require File.dirname(__FILE__) + "/ap/mixin/action_view" if defined?(ActionView) diff --git a/lib/ap/awesome_print.rb b/lib/ap/awesome_print.rb deleted file mode 100755 index e17fb36..0000000 --- a/lib/ap/awesome_print.rb +++ /dev/null @@ -1,332 +0,0 @@ -# Copyright (c) 2010-2011 Michael Dvorkin -# -# Awesome Print is freely distributable under the terms of MIT license. -# See LICENSE file or http://www.opensource.org/licenses/mit-license.php -#------------------------------------------------------------------------------ -require "shellwords" - -class AwesomePrint - AP = :__awesome_print__ unless defined?(AwesomePrint::AP) - CORE = [ :array, :hash, :class, :file, :dir, :bigdecimal, :rational, :struct, :method, :unboundmethod ] unless defined?(AwesomePrint::CORE) - - def initialize(options = {}) - @options = { - :multiline => true, - :plain => false, - :indent => 4, - :index => true, - :sorted_hash_keys => false, - :color => { - :array => :white, - :bigdecimal => :blue, - :class => :yellow, - :date => :greenish, - :falseclass => :red, - :fixnum => :blue, - :float => :blue, - :hash => :pale, - :struct => :pale, - :nilclass => :red, - :string => :yellowish, - :symbol => :cyanish, - :time => :greenish, - :trueclass => :green, - :method => :purpleish, - :args => :pale - } - } - - # Merge custom defaults and let explicit options parameter override them. - merge_custom_defaults! - merge_options!(options) - - @indentation = @options[:indent].abs - Thread.current[AP] ||= [] - end - - private - - # Format an array. - #------------------------------------------------------------------------------ - def awesome_array(a) - return "[]" if a == [] - - if a.instance_variable_defined?('@__awesome_methods__') - methods_array(a) - elsif @options[:multiline] - width = (a.size - 1).to_s.size - data = a.inject([]) do |arr, item| - index = if @options[:index] - colorize("#{indent}[#{arr.size.to_s.rjust(width)}] ", :array) - else - colorize(indent, :array) - end - indented do - arr << (index << awesome(item)) - end - end - "[\n" << data.join(",\n") << "\n#{outdent}]" - else - "[ " << a.map{ |item| awesome(item) }.join(", ") << " ]" - end - end - - # Format a hash. If @options[:indent] if negative left align hash keys. - #------------------------------------------------------------------------------ - def awesome_hash(h) - return "{}" if h == {} - - keys = @options[:sorted_hash_keys] ? h.keys.sort { |a, b| a.to_s <=> b.to_s } : h.keys - data = keys.map do |key| - plain_single_line do - [ awesome(key), h[key] ] - end - end - - width = data.map { |key, | key.size }.max || 0 - width += @indentation if @options[:indent] > 0 - - data = data.map do |key, value| - if @options[:multiline] - formatted_key = (@options[:indent] >= 0 ? key.rjust(width) : indent + key.ljust(width)) - else - formatted_key = key - end - indented do - formatted_key << colorize(" => ", :hash) << awesome(value) - end - end - if @options[:multiline] - "{\n" << data.join(",\n") << "\n#{outdent}}" - else - "{ #{data.join(', ')} }" - end - end - - # Format a Struct. If @options[:indent] if negative left align hash keys. - #------------------------------------------------------------------------------ - def awesome_struct(s) - h = {} - s.each_pair { |k,v| h[k] = v } - awesome_hash(h) - end - - # Format Class object. - #------------------------------------------------------------------------------ - def awesome_class(c) - if superclass = c.superclass # <-- Assign and test if nil. - awesome_self(c, :with => " < #{superclass}") - else - awesome_self(c) - end - end - - # Format File object. - #------------------------------------------------------------------------------ - def awesome_file(f) - ls = File.directory?(f) ? `ls -adlF #{f.path.shellescape}` : `ls -alF #{f.path.shellescape}` - awesome_self(f, :with => ls.empty? ? nil : "\n#{ls.chop}") - end - - # Format Dir object. - #------------------------------------------------------------------------------ - def awesome_dir(d) - ls = `ls -alF #{d.path.shellescape}` - awesome_self(d, :with => ls.empty? ? nil : "\n#{ls.chop}") - end - - # Format BigDecimal and Rational objects by convering them to Float. - #------------------------------------------------------------------------------ - def awesome_bigdecimal(n) - awesome_self(n.to_f, :as => :bigdecimal) - end - alias :awesome_rational :awesome_bigdecimal - - # Format a method. - #------------------------------------------------------------------------------ - def awesome_method(m) - name, args, owner = method_tuple(m) - "#{colorize(owner, :class)}##{colorize(name, :method)}#{colorize(args, :args)}" - end - alias :awesome_unboundmethod :awesome_method - - # Catch all method to format an arbitrary object. - #------------------------------------------------------------------------------ - def awesome_self(object, appear = {}) - colorize(object.inspect.to_s << appear[:with].to_s, appear[:as] || declassify(object)) - end - - # Dispatcher that detects data nesting and invokes object-aware formatter. - #------------------------------------------------------------------------------ - def awesome(object) - if Thread.current[AP].include?(object.object_id) - nested(object) - else - begin - Thread.current[AP] << object.object_id - send(:"awesome_#{printable(object)}", object) - ensure - Thread.current[AP].pop - end - end - end - - # Format object.methods array. - #------------------------------------------------------------------------------ - def methods_array(a) - object = a.instance_variable_get('@__awesome_methods__') - tuples = a.map do |name| - if object.respond_to?(name, true) # Regular method? - method_tuple(object.method(name)) - elsif object.respond_to?(:instance_method) # Unbound method? - method_tuple(object.instance_method(name)) - else # WTF method. - [ name.to_s, '(?)', '' ] - end - end - - width = (tuples.size - 1).to_s.size - name_width = tuples.map { |item| item[0].size }.max || 0 - args_width = tuples.map { |item| item[1].size }.max || 0 - - data = tuples.inject([]) do |arr, item| - index = if @options[:index] - "#{indent}[#{arr.size.to_s.rjust(width)}]" - else - indent - end - indented do - arr << "#{index} #{colorize(item[0].rjust(name_width), :method)}#{colorize(item[1].ljust(args_width), :args)} #{colorize(item[2], :class)}" - end - end - - "[\n" << data.join("\n") << "\n#{outdent}]" - end - - # Format nested data, for example: - # arr = [1, 2]; arr << arr - # => [1,2, [...]] - # hsh = { :a => 1 }; hsh[:b] = hsh - # => { :a => 1, :b => {...} } - #------------------------------------------------------------------------------ - def nested(object) - case printable(object) - when :array then colorize("[...]", :array) - when :hash then colorize("{...}", :hash) - when :struct then colorize("{...}", :struct) - else colorize("...#{object.class}...", :class) - end - end - - # Return one of the "core" types that have a formatter of :self otherwise. - #------------------------------------------------------------------------------ - def printable(object) - CORE.grep(declassify(object))[0] || :self - end - - # Turn class name into symbol, ex: Hello::World => :hello_world. Classes that - # inherit from Array, Hash, File, Dir, and Struct are treated as the base class. - #------------------------------------------------------------------------------ - def declassify(object) - case object - when Array then :array - when Hash then :hash - when File then :file - when Dir then :dir - when Struct then :struct - else object.class.to_s.gsub(/:+/, "_").downcase.to_sym - end - end - - # Pick the color and apply it to the given string as necessary. - #------------------------------------------------------------------------------ - def colorize(s, type) - if @options[:plain] || @options[:color][type].nil? - s - else - s.send(@options[:color][type]) - end - end - - # Return [ name, arguments, owner ] tuple for a given method. - #------------------------------------------------------------------------------ - def method_tuple(method) - if method.respond_to?(:parameters) # Ruby 1.9.2+ - # See http://ruby.runpaint.org/methods#method-objects-parameters - args = method.parameters.inject([]) do |arr, (type, name)| - name ||= (type == :block ? 'block' : "arg#{arr.size + 1}") - arr << case type - when :req then name.to_s - when :opt, :rest then "*#{name}" - when :block then "&#{name}" - else '?' - end - end - else # See http://ruby-doc.org/core/classes/Method.html#M001902 - args = (1..method.arity.abs).map { |i| "arg#{i}" } - args[-1] = "*#{args[-1]}" if method.arity < 0 - end - - if method.to_s =~ /(Unbound)*Method: (.*?)[#\.]/ - owner = "#{$2}#{$1 ? '(unbound)' : ''}".gsub('(', ' (') - end - - [ method.name.to_s, "(#{args.join(', ')})", owner.to_s ] - end - - # Format hash keys as plain string regardless of underlying data type. - #------------------------------------------------------------------------------ - def plain_single_line - plain, multiline = @options[:plain], @options[:multiline] - @options[:plain], @options[:multiline] = true, false - yield - ensure - @options[:plain], @options[:multiline] = plain, multiline - end - - #------------------------------------------------------------------------------ - def indented - @indentation += @options[:indent].abs - yield - ensure - @indentation -= @options[:indent].abs - end - - def indent - @indent = ' ' * @indentation - end - - def outdent - @outdent = ' ' * (@indentation - @options[:indent].abs) - end - - # Update @options by first merging the :color hash and then the remaining keys. - #------------------------------------------------------------------------------ - def merge_options!(options = {}) - @options[:color].merge!(options.delete(:color) || {}) - @options.merge!(options) - end - - # Load ~/.aprc file with custom defaults that override default options. - #------------------------------------------------------------------------------ - def merge_custom_defaults! - dotfile = File.join(ENV["HOME"], ".aprc") - if File.readable?(dotfile) - load dotfile - merge_options!(self.class.defaults) - end - rescue => e - $stderr.puts "Could not load #{dotfile}: #{e}" - end - - # Class accessors for custom defaults. - #------------------------------------------------------------------------------ - def self.defaults - @@defaults ||= {} - end - - def self.defaults=(args = {}) - @@defaults = args - end - -end diff --git a/lib/ap/core_ext/kernel.rb b/lib/ap/core_ext/kernel.rb index 5f93600..ab043b9 100644 --- a/lib/ap/core_ext/kernel.rb +++ b/lib/ap/core_ext/kernel.rb @@ -6,7 +6,7 @@ module Kernel def ai(options = {}) - ap = AwesomePrint.new(options) + ap = AwesomePrint::Inspector.new(options) ap.send(:awesome, self) end alias :awesome_inspect :ai diff --git a/lib/ap/core_ext/logger.rb b/lib/ap/core_ext/logger.rb index a0e5720..04a5920 100644 --- a/lib/ap/core_ext/logger.rb +++ b/lib/ap/core_ext/logger.rb @@ -3,16 +3,17 @@ # Awesome Print is freely distributable under the terms of MIT license. # See LICENSE file or http://www.opensource.org/licenses/mit-license.php #------------------------------------------------------------------------------ -module AwesomePrintLogger +module AwesomePrint + module Logger - # Add ap method to logger - #------------------------------------------------------------------------------ - def ap(object, level = nil) - level ||= AwesomePrint.defaults[:log_level] || :debug - send level, object.ai + # Add ap method to logger + #------------------------------------------------------------------------------ + def ap(object, level = nil) + level ||= AwesomePrint.defaults[:log_level] || :debug + send level, object.ai + end end - end -Logger.send(:include, AwesomePrintLogger) -ActiveSupport::BufferedLogger.send(:include, AwesomePrintLogger) if defined?(::ActiveSupport::BufferedLogger) +Logger.send(:include, AwesomePrint::Logger) +ActiveSupport::BufferedLogger.send(:include, AwesomePrint::Logger) if defined?(::ActiveSupport::BufferedLogger) diff --git a/lib/ap/formatter.rb b/lib/ap/formatter.rb new file mode 100755 index 0000000..7dab041 --- /dev/null +++ b/lib/ap/formatter.rb @@ -0,0 +1,238 @@ +# Copyright (c) 2010-2011 Michael Dvorkin +# +# Awesome Print is freely distributable under the terms of MIT license. +# See LICENSE file or http://www.opensource.org/licenses/mit-license.php +#------------------------------------------------------------------------------ +require "shellwords" + +module AwesomePrint + class Formatter + + CORE = [ :array, :hash, :class, :file, :dir, :bigdecimal, :rational, :struct, :method, :unboundmethod ] + + def initialize(inspector) + @inspector = inspector + @options = inspector.options + @indentation = @options[:indent].abs + end + + # Main entry point to format an object. + #------------------------------------------------------------------------------ + def format(object, type = nil) + klass = cast(object, type) + return send(:"awesome_#{klass}", object) if klass != :self + send(:awesome_self, object, :as => type) # Catch all that falls back on object.inspect. + end + + # Hook this when adding custom formatters. Check out how it's done in + # ap/lib/mixin/active_record.rb + #------------------------------------------------------------------------------ + def cast(object, type) + CORE.grep(type)[0] || :self + end + + # Pick the color and apply it to the given string as necessary. + #------------------------------------------------------------------------------ + def colorize(s, type = nil) + return s if @options[:plain] || @options[:color][type].nil? + s.send(@options[:color][type]) + end + + + private + + # Catch all method to format an arbitrary object. + #------------------------------------------------------------------------------ + def awesome_self(object, appear = {}) + colorize(object.inspect.to_s << appear[:with].to_s, appear[:as]) + end + + # Format an array. + #------------------------------------------------------------------------------ + def awesome_array(a) + return "[]" if a == [] + + if a.instance_variable_defined?('@__awesome_methods__') + methods_array(a) + elsif @options[:multiline] + width = (a.size - 1).to_s.size + data = a.inject([]) do |arr, item| + index = if @options[:index] + colorize("#{indent}[#{arr.size.to_s.rjust(width)}] ", :array) + else + colorize(indent, :array) + end + indented do + arr << (index << @inspector.send(:awesome, item)) + end + end + "[\n" << data.join(",\n") << "\n#{outdent}]" + else + "[ " << a.map{ |item| @inspector.send(:awesome, item) }.join(", ") << " ]" + end + end + + # Format a hash. If @options[:indent] if negative left align hash keys. + #------------------------------------------------------------------------------ + def awesome_hash(h) + return "{}" if h == {} + + keys = @options[:sort_keys] ? h.keys.sort { |a, b| a.to_s <=> b.to_s } : h.keys + data = keys.map do |key| + plain_single_line do + [ @inspector.send(:awesome, key), h[key] ] + end + end + + width = data.map { |key, | key.size }.max || 0 + width += @indentation if @options[:indent] > 0 + + data = data.map do |key, value| + if @options[:multiline] + formatted_key = (@options[:indent] >= 0 ? key.rjust(width) : indent + key.ljust(width)) + else + formatted_key = key + end + indented do + formatted_key << colorize(" => ", :hash) << @inspector.send(:awesome, value) + end + end + if @options[:multiline] + "{\n" << data.join(",\n") << "\n#{outdent}}" + else + "{ #{data.join(', ')} }" + end + end + + # Format a Struct. If @options[:indent] if negative left align hash keys. + #------------------------------------------------------------------------------ + def awesome_struct(s) + h = {} + s.each_pair { |k,v| h[k] = v } + awesome_hash(h) + end + + # Format Class object. + #------------------------------------------------------------------------------ + def awesome_class(c) + if superclass = c.superclass # <-- Assign and test if nil. + awesome_self(c, :as => :class, :with => " < #{superclass}") + else + awesome_self(c, :as => :class) + end + end + + # Format File object. + #------------------------------------------------------------------------------ + def awesome_file(f) + ls = File.directory?(f) ? `ls -adlF #{f.path.shellescape}` : `ls -alF #{f.path.shellescape}` + awesome_self(f, :as => :file, :with => ls.empty? ? nil : "\n#{ls.chop}") + end + + # Format Dir object. + #------------------------------------------------------------------------------ + def awesome_dir(d) + ls = `ls -alF #{d.path.shellescape}` + awesome_self(d, :as => :dir, :with => ls.empty? ? nil : "\n#{ls.chop}") + end + + # Format BigDecimal and Rational objects by convering them to Float. + #------------------------------------------------------------------------------ + def awesome_bigdecimal(n) + awesome_self(n.to_f, :as => :bigdecimal) + end + alias :awesome_rational :awesome_bigdecimal + + # Format a method. + #------------------------------------------------------------------------------ + def awesome_method(m) + name, args, owner = method_tuple(m) + "#{colorize(owner, :class)}##{colorize(name, :method)}#{colorize(args, :args)}" + end + alias :awesome_unboundmethod :awesome_method + + # Format object.methods array. + #------------------------------------------------------------------------------ + def methods_array(a) + object = a.instance_variable_get('@__awesome_methods__') + tuples = a.map do |name| + if object.respond_to?(name, true) # Regular method? + method_tuple(object.method(name)) + elsif object.respond_to?(:instance_method) # Unbound method? + method_tuple(object.instance_method(name)) + else # WTF method. + [ name.to_s, '(?)', '' ] + end + end + + width = (tuples.size - 1).to_s.size + name_width = tuples.map { |item| item[0].size }.max || 0 + args_width = tuples.map { |item| item[1].size }.max || 0 + + data = tuples.inject([]) do |arr, item| + index = if @options[:index] + "#{indent}[#{arr.size.to_s.rjust(width)}]" + else + indent + end + indented do + arr << "#{index} #{colorize(item[0].rjust(name_width), :method)}#{colorize(item[1].ljust(args_width), :args)} #{colorize(item[2], :class)}" + end + end + + "[\n" << data.join("\n") << "\n#{outdent}]" + end + + # Return [ name, arguments, owner ] tuple for a given method. + #------------------------------------------------------------------------------ + def method_tuple(method) + if method.respond_to?(:parameters) # Ruby 1.9.2+ + # See http://ruby.runpaint.org/methods#method-objects-parameters + args = method.parameters.inject([]) do |arr, (type, name)| + name ||= (type == :block ? 'block' : "arg#{arr.size + 1}") + arr << case type + when :req then name.to_s + when :opt, :rest then "*#{name}" + when :block then "&#{name}" + else '?' + end + end + else # See http://ruby-doc.org/core/classes/Method.html#M001902 + args = (1..method.arity.abs).map { |i| "arg#{i}" } + args[-1] = "*#{args[-1]}" if method.arity < 0 + end + + if method.to_s =~ /(Unbound)*Method: (.*?)[#\.]/ + owner = "#{$2}#{$1 ? '(unbound)' : ''}".gsub('(', ' (') + end + + [ method.name.to_s, "(#{args.join(', ')})", owner.to_s ] + end + + # Format hash keys as plain string regardless of underlying data type. + #------------------------------------------------------------------------------ + def plain_single_line + plain, multiline = @options[:plain], @options[:multiline] + @options[:plain], @options[:multiline] = true, false + yield + ensure + @options[:plain], @options[:multiline] = plain, multiline + end + + #------------------------------------------------------------------------------ + def indented + @indentation += @options[:indent].abs + yield + ensure + @indentation -= @options[:indent].abs + end + + def indent + ' ' * @indentation + end + + def outdent + ' ' * (@indentation - @options[:indent].abs) + end + end +end diff --git a/lib/ap/inspector.rb b/lib/ap/inspector.rb new file mode 100755 index 0000000..ea9a7c6 --- /dev/null +++ b/lib/ap/inspector.rb @@ -0,0 +1,122 @@ +# Copyright (c) 2010-2011 Michael Dvorkin +# +# Awesome Print is freely distributable under the terms of MIT license. +# See LICENSE file or http://www.opensource.org/licenses/mit-license.php +#------------------------------------------------------------------------------ +module AwesomePrint + + class << self # Class accessors for custom defaults. + attr_accessor :defaults + end + + class Inspector + attr_accessor :options + + AP = :__awesome_print__ + + def initialize(options = {}) + @options = { + :color => { + :array => :white, + :bigdecimal => :blue, + :class => :yellow, + :date => :greenish, + :falseclass => :red, + :fixnum => :blue, + :float => :blue, + :hash => :pale, + :struct => :pale, + :nilclass => :red, + :string => :yellowish, + :symbol => :cyanish, + :time => :greenish, + :trueclass => :green, + :method => :purpleish, + :args => :pale + }, + :indent => 4, + :index => true, + :multiline => true, + :plain => false, + :sort_keys => false + } + + # Merge custom defaults and let explicit options parameter override them. + merge_custom_defaults! + merge_options!(options) + + @formatter = AwesomePrint::Formatter.new(self) + Thread.current[AP] ||= [] + end + + private + + # Dispatcher that detects data nesting and invokes object-aware formatter. + #------------------------------------------------------------------------------ + def awesome(object) + if Thread.current[AP].include?(object.object_id) + nested(object) + else + begin + Thread.current[AP] << object.object_id + unnested(object) + ensure + Thread.current[AP].pop + end + end + end + + # Format nested data, for example: + # arr = [1, 2]; arr << arr + # => [1,2, [...]] + # hash = { :a => 1 }; hash[:b] = hash + # => { :a => 1, :b => {...} } + #------------------------------------------------------------------------------ + def nested(object) + case printable(object) + when :array then @formatter.colorize("[...]", :array) + when :hash then @formatter.colorize("{...}", :hash) + when :struct then @formatter.colorize("{...}", :struct) + else @formatter.colorize("...#{object.class}...", :class) + end + end + + #------------------------------------------------------------------------------ + def unnested(object) + @formatter.format(object, printable(object)) + end + + # Turn class name into symbol, ex: Hello::World => :hello_world. Classes that + # inherit from Array, Hash, File, Dir, and Struct are treated as the base class. + #------------------------------------------------------------------------------ + def printable(object) + case object + when Array then :array + when Hash then :hash + when File then :file + when Dir then :dir + when Struct then :struct + else object.class.to_s.gsub(/:+/, "_").downcase.to_sym + end + end + + # Update @options by first merging the :color hash and then the remaining keys. + #------------------------------------------------------------------------------ + def merge_options!(options = {}) + @options[:color].merge!(options.delete(:color) || {}) + @options.merge!(options) + end + + # Load ~/.aprc file with custom defaults that override default options. + #------------------------------------------------------------------------------ + def merge_custom_defaults! + dotfile = File.join(ENV["HOME"], ".aprc") + if File.readable?(dotfile) + load dotfile + merge_options!(AwesomePrint.defaults) + end + rescue => e + $stderr.puts "Could not load #{dotfile}: #{e}" + end + end +end diff --git a/lib/ap/mixin/action_view.rb b/lib/ap/mixin/action_view.rb index 1a34c6e..ab8ecd3 100644 --- a/lib/ap/mixin/action_view.rb +++ b/lib/ap/mixin/action_view.rb @@ -3,36 +3,37 @@ # Awesome Print is freely distributable under the terms of MIT license. # See LICENSE file or http://www.opensource.org/licenses/mit-license.php #------------------------------------------------------------------------------ -module AwesomePrintActionView +module AwesomePrint + module ActionView - def self.included(base) - unless base.const_defined?(:AP_ANSI_TO_HTML) - hash = {} # Build ANSI => HTML color map. - [ :gray, :red, :green, :yellow, :blue, :purple, :cyan, :white ].each_with_index do |color, i| - hash["\033[1;#{30+i}m"] = color + def self.included(base) + unless base.const_defined?(:AP_ANSI_TO_HTML) + hash = {} # Build ANSI => HTML color map. + [ :gray, :red, :green, :yellow, :blue, :purple, :cyan, :white ].each_with_index do |color, i| + hash["\033[1;#{30+i}m"] = color + end + [ :black, :darkred, :darkgreen, :brown, :navy, :darkmagenta, :darkcyan, :slategray ].each_with_index do |color, i| + hash["\033[0;#{30+i}m"] = color + end + base.const_set(:AP_ANSI_TO_HTML, hash.freeze) end - [ :black, :darkred, :darkgreen, :brown, :navy, :darkmagenta, :darkcyan, :slategray ].each_with_index do |color, i| - hash["\033[0;#{30+i}m"] = color - end - base.const_set(:AP_ANSI_TO_HTML, hash.freeze) - end - end - - def ap_debug(object, options = {}) - formatted = h(object.ai(options)) - - unless options[:plain] - self.class::AP_ANSI_TO_HTML.each do |key, value| - formatted.gsub!(key, %Q||) - end - formatted.gsub!("\033[0m", "") end - content_tag(:pre, formatted, :class => "debug_dump") + def ap_debug(object, options = {}) + formatted = h(object.ai(options)) + + unless options[:plain] + self.class::AP_ANSI_TO_HTML.each do |key, value| + formatted.gsub!(key, %Q||) + end + formatted.gsub!("\033[0m", "") + end + + content_tag(:pre, formatted, :class => "debug_dump") + end + + alias_method :ap, :ap_debug end - - alias_method :ap, :ap_debug - end -ActionView::Base.send(:include, AwesomePrintActionView) if defined?(ActionView) +ActionView::Base.send(:include, AwesomePrint::ActionView) if defined?(ActionView) diff --git a/lib/ap/mixin/active_record.rb b/lib/ap/mixin/active_record.rb index 3f7a8cd..2fffd79 100644 --- a/lib/ap/mixin/active_record.rb +++ b/lib/ap/mixin/active_record.rb @@ -3,52 +3,52 @@ # Awesome Print is freely distributable under the terms of MIT license. # See LICENSE file or http://www.opensource.org/licenses/mit-license.php #------------------------------------------------------------------------------ -module AwesomePrintActiveRecord +module AwesomePrint + module ActiveRecord - def self.included(base) - base.send :alias_method, :printable_without_active_record, :printable - base.send :alias_method, :printable, :printable_with_active_record - end + def self.included(base) + base.send :alias_method, :cast_without_active_record, :cast + base.send :alias_method, :cast, :cast_with_active_record + end - # Add ActiveRecord class names to the dispatcher pipeline. - #------------------------------------------------------------------------------ - def printable_with_active_record(object) - printable = printable_without_active_record(object) - return printable if !defined?(ActiveRecord::Base) - - if printable == :self - if object.is_a?(ActiveRecord::Base) - printable = :active_record_instance + def cast_with_active_record(object, type) + cast = cast_without_active_record(object, type) + if defined?(::ActiveRecord) + if object.is_a?(::ActiveRecord::Base) + cast = :active_record_instance + elsif object.is_a?(Class) and object.ancestors.include?(::ActiveRecord::Base) + cast = :active_record_class + end end - elsif printable == :class and object.ancestors.include?(ActiveRecord::Base) - printable = :active_record_class + cast end - printable - end - # Format ActiveRecord instance object. - #------------------------------------------------------------------------------ - def awesome_active_record_instance(object) - return object.inspect if !defined?(ActiveSupport::OrderedHash) + private - data = object.class.column_names.inject(ActiveSupport::OrderedHash.new) do |hash, name| - hash[name.to_sym] = object.send(name) if object.has_attribute?(name) || object.new_record? - hash + # Format ActiveRecord instance object. + #------------------------------------------------------------------------------ + def awesome_active_record_instance(object) + return object.inspect if !defined?(::ActiveSupport::OrderedHash) + + data = object.class.column_names.inject(::ActiveSupport::OrderedHash.new) do |hash, name| + hash[name.to_sym] = object.send(name) if object.has_attribute?(name) || object.new_record? + hash + end + "#{object} " + awesome_hash(data) end - "#{object} " + awesome_hash(data) - end - # Format ActiveRecord class object. - #------------------------------------------------------------------------------ - def awesome_active_record_class(object) - return object.inspect if !defined?(ActiveSupport::OrderedHash) || !object.respond_to?(:columns) + # Format ActiveRecord class object. + #------------------------------------------------------------------------------ + def awesome_active_record_class(object) + return object.inspect if !defined?(::ActiveSupport::OrderedHash) || !object.respond_to?(:columns) - data = object.columns.inject(ActiveSupport::OrderedHash.new) do |hash, c| - hash[c.name.to_sym] = c.type - hash + data = object.columns.inject(::ActiveSupport::OrderedHash.new) do |hash, c| + hash[c.name.to_sym] = c.type + hash + end + "class #{object} < #{object.superclass} " << awesome_hash(data) end - "class #{object} < #{object.superclass} " << awesome_hash(data) end end -AwesomePrint.send(:include, AwesomePrintActiveRecord) +AwesomePrint::Formatter.send(:include, AwesomePrint::ActiveRecord) diff --git a/lib/ap/mixin/active_support.rb b/lib/ap/mixin/active_support.rb index c906841..490940b 100644 --- a/lib/ap/mixin/active_support.rb +++ b/lib/ap/mixin/active_support.rb @@ -3,44 +3,38 @@ # Awesome Print is freely distributable under the terms of MIT license. # See LICENSE file or http://www.opensource.org/licenses/mit-license.php #------------------------------------------------------------------------------ -module AwesomePrintActiveSupport +module AwesomePrint + module ActiveSupport - def self.included(base) - base.send :alias_method, :printable_without_active_support, :printable - base.send :alias_method, :printable, :printable_with_active_support - end - - # Add ActiveSupport class names to the dispatcher pipeline. - #------------------------------------------------------------------------------ - def printable_with_active_support(object) - printable = printable_without_active_support(object) - return printable if !defined?(ActiveSupport::TimeWithZone) || !defined?(HashWithIndifferentAccess) - - if printable == :self - if object.is_a?(ActiveSupport::TimeWithZone) - printable = :active_support_time - elsif object.is_a?(HashWithIndifferentAccess) - printable = :hash_with_indifferent_access - end + def self.included(base) + base.send :alias_method, :cast_without_active_support, :cast + base.send :alias_method, :cast, :cast_with_active_support end - printable - end - # Format ActiveSupport::TimeWithZone as standard Time. - #------------------------------------------------------------------------------ - def awesome_active_support_time(object) - awesome_self(object, :as => :time) - end + def cast_with_active_support(object, type) + cast = cast_without_active_support(object, type) + if defined?(::ActiveSupport) && defined?(::HashWithIndifferentAccess) + if object.is_a?(::ActiveSupport::TimeWithZone) + cast = :active_support_time + elsif object.is_a?(::HashWithIndifferentAccess) + cast = :hash_with_indifferent_access + end + end + cast + end - # Format HashWithIndifferentAccess as standard Hash. - # - # NOTE: can't use awesome_self(object, :as => :hash) since awesome_self uses - # object.inspect internally, i.e. it would convert hash to string. - #------------------------------------------------------------------------------ - def awesome_hash_with_indifferent_access(object) - awesome_hash(object) - end + # Format ActiveSupport::TimeWithZone as standard Time. + #------------------------------------------------------------------------------ + def awesome_active_support_time(object) + awesome_self(object, :as => :time) + end + # Format HashWithIndifferentAccess as standard Hash. + #------------------------------------------------------------------------------ + def awesome_hash_with_indifferent_access(object) + awesome_hash(object) + end + end end -AwesomePrint.send(:include, AwesomePrintActiveSupport) +AwesomePrint::Formatter.send(:include, AwesomePrint::ActiveSupport) diff --git a/lib/awesome_print.rb b/lib/awesome_print.rb index fc1cc15..d887f1d 100755 --- a/lib/awesome_print.rb +++ b/lib/awesome_print.rb @@ -14,7 +14,8 @@ require File.dirname(__FILE__) + "/ap/core_ext/#{file}" end -require File.dirname(__FILE__) + "/ap/awesome_print" +require File.dirname(__FILE__) + "/ap/inspector" +require File.dirname(__FILE__) + "/ap/formatter" require File.dirname(__FILE__) + "/ap/core_ext/logger" if defined?(Logger) require File.dirname(__FILE__) + "/ap/mixin/action_view" if defined?(ActionView) diff --git a/spec/action_view_spec.rb b/spec/action_view_spec.rb index 7bff0f9..d9cde3b 100644 --- a/spec/action_view_spec.rb +++ b/spec/action_view_spec.rb @@ -4,7 +4,7 @@ require 'action_view' require 'ap/mixin/action_view' describe "AwesomePrint ActionView extensions" do - before(:each) do + before do @view = ActionView::Base.new end diff --git a/spec/active_record_spec.rb b/spec/active_record_spec.rb index e5e5836..ee86346 100644 --- a/spec/active_record_spec.rb +++ b/spec/active_record_spec.rb @@ -4,53 +4,51 @@ require 'active_record' require 'ap/mixin/active_record' -if defined?(::ActiveRecord) +# Create tableless ActiveRecord model. +#------------------------------------------------------------------------------ +class User < ActiveRecord::Base + def self.columns() + @columns ||= [] + end + + def self.column(name, sql_type = nil, default = nil, null = true) + columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null) + end + + column :id, :integer + column :name, :string + column :rank, :integer + column :admin, :boolean + column :created_at, :datetime + + def self.table_exists? + true + end +end + +class SubUser < User + def self.columns + User.columns + end +end + +describe "AwesomePrint::ActiveRecord" do + before do + stub_dotfile! + end - # Create tableless ActiveRecord model. #------------------------------------------------------------------------------ - class User < ActiveRecord::Base - def self.columns() - @columns ||= [] + describe "ActiveRecord instance" do + before do + ActiveRecord::Base.default_timezone = :utc + @diana = User.new(:name => "Diana", :rank => 1, :admin => false, :created_at => "1992-10-10 12:30:00") + @laura = User.new(:name => "Laura", :rank => 2, :admin => true, :created_at => "2003-05-26 14:15:00") + @ap = AwesomePrint::Inspector.new(:plain => true) end - def self.column(name, sql_type = nil, default = nil, null = true) - columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null) - end - - column :id, :integer - column :name, :string - column :rank, :integer - column :admin, :boolean - column :created_at, :datetime - - def self.table_exists? - true - end - end - - class SubUser < User - def self.columns - User.columns - end - end - - describe "AwesomePrint/ActiveRecord" do - before(:each) do - stub_dotfile! - end - - #------------------------------------------------------------------------------ - describe "ActiveRecord instance" do - before(:each) do - ActiveRecord::Base.default_timezone = :utc - @diana = User.new(:name => "Diana", :rank => 1, :admin => false, :created_at => "1992-10-10 12:30:00") - @laura = User.new(:name => "Laura", :rank => 2, :admin => true, :created_at => "2003-05-26 14:15:00") - @ap = AwesomePrint.new(:plain => true) - end - - it "display single record" do - out = @ap.send(:awesome, @diana) - str = <<-EOS.strip + it "display single record" do + out = @ap.send(:awesome, @diana) + str = <<-EOS.strip # { :id => nil, :name => "Diana", @@ -59,18 +57,18 @@ if defined?(::ActiveRecord) :created_at => ? } EOS - if RUBY_VERSION.to_f < 1.9 - str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992') - else - str.sub!('?', '1992-10-10 12:30:00 UTC') - end - - out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str + if RUBY_VERSION.to_f < 1.9 + str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992') + else + str.sub!('?', '1992-10-10 12:30:00 UTC') end - it "display multiple records" do - out = @ap.send(:awesome, [ @diana, @laura ]) - str = <<-EOS.strip + out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str + end + + it "display multiple records" do + out = @ap.send(:awesome, [ @diana, @laura ]) + str = <<-EOS.strip [ [0] # { :id => nil, @@ -88,23 +86,23 @@ EOS } ] EOS - if RUBY_VERSION.to_f < 1.9 - str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992') - str.sub!('!', 'Mon May 26 14:15:00 UTC 2003') - else - str.sub!('?', '1992-10-10 12:30:00 UTC') - str.sub!('!', '2003-05-26 14:15:00 UTC') - end - - out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str + if RUBY_VERSION.to_f < 1.9 + str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992') + str.sub!('!', 'Mon May 26 14:15:00 UTC 2003') + else + str.sub!('?', '1992-10-10 12:30:00 UTC') + str.sub!('!', '2003-05-26 14:15:00 UTC') end - end - #------------------------------------------------------------------------------ - describe "ActiveRecord class" do - it "should print the class" do - @ap = AwesomePrint.new(:plain => true) - @ap.send(:awesome, User).should == <<-EOS.strip + out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str + end + end + + #------------------------------------------------------------------------------ + describe "ActiveRecord class" do + it "should print the class" do + @ap = AwesomePrint::Inspector.new(:plain => true) + @ap.send(:awesome, User).should == <<-EOS.strip class User < ActiveRecord::Base { :id => :integer, :name => :string, @@ -117,7 +115,7 @@ class User < ActiveRecord::Base { end it "should print the class for non-direct subclasses of AR::Base" do - @ap = AwesomePrint.new(:plain => true) + @ap = AwesomePrint::Inspector.new(:plain => true) @ap.send(:awesome, SubUser).should == <<-EOS.strip class SubUser < User { :id => :integer, @@ -128,7 +126,6 @@ class SubUser < User { } EOS - end end end end diff --git a/spec/active_support_spec.rb b/spec/active_support_spec.rb new file mode 100755 index 0000000..9cf3939 --- /dev/null +++ b/spec/active_support_spec.rb @@ -0,0 +1,22 @@ +require File.expand_path(File.dirname(__FILE__) + '/spec_helper') + +require 'active_support' +require 'ap/mixin/active_support' + +describe "AwesomePrint::ActiveSupport" do + before do + stub_dotfile! + @ap = AwesomePrint::Inspector.new() + end + + it "should format ActiveSupport::TimeWithZone as regular Time" do + Time.zone = 'Eastern Time (US & Canada)' + time = Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone + @ap.send(:awesome, time).should == "\e[0;32mSat, 10 Feb 2007 15:30:45 EST -05:00\e[0m" + end + + it "should format HashWithIndifferentAccess as regular Hash" do + hash = HashWithIndifferentAccess.new({ :hello => "world" }) + @ap.send(:awesome, hash).should == "{\n \"hello\"\e[0;37m => \e[0m\e[0;33m\"world\"\e[0m\n}" + end +end diff --git a/spec/awesome_print_spec.rb b/spec/awesome_print_spec.rb index 1a265ec..1cedd6d 100644 --- a/spec/awesome_print_spec.rb +++ b/spec/awesome_print_spec.rb @@ -3,12 +3,12 @@ require "bigdecimal" require "rational" describe "AwesomePrint" do - before(:each) do + before do stub_dotfile! end describe "Array" do - before(:each) do + before do @arr = [ 1, :two, "three", [ nil, [ true, false] ] ] end @@ -129,7 +129,7 @@ EOS #------------------------------------------------------------------------------ describe "Nested Array" do - before(:each) do + before do @arr = [ 1, 2 ] @arr << @arr end @@ -161,7 +161,7 @@ EOS #------------------------------------------------------------------------------ describe "Hash" do - before(:each) do + before do @hash = { 1 => { :sym => { "str" => { [1, 2, 3] => { { :k => :v } => Hash } } } } } end @@ -245,7 +245,7 @@ EOS #------------------------------------------------------------------------------ describe "Nested Hash" do - before(:each) do + before do @hash = {} @hash[:a] = @hash end @@ -265,7 +265,7 @@ EOS #------------------------------------------------------------------------------ describe "Hash with several keys" do - before(:each) do + before do @hash = {"b" => "b", :a => "a", :z => "z", "alpha" => "alpha"} end @@ -290,7 +290,7 @@ EOS end it "plain multiline with sorted keys" do - @hash.ai(:plain => true, :sorted_hash_keys => true).should == <<-EOS.strip + @hash.ai(:plain => true, :sort_keys => true).should == <<-EOS.strip { :a => "a", "alpha" => "alpha", @@ -304,7 +304,7 @@ EOS #------------------------------------------------------------------------------ describe "Negative options[:indent]" do - before(:each) do + before do @hash = { [0, 0, 255] => :yellow, :red => "rgb(255, 0, 0)", "magenta" => "rgb(255, 0, 255)" } end @@ -363,7 +363,7 @@ EOS #------------------------------------------------------------------------------ describe "Utility methods" do it "should merge options" do - ap = AwesomePrint.new + ap = AwesomePrint::Inspector.new ap.send(:merge_options!, { :color => { :array => :black }, :indent => 0 }) options = ap.instance_variable_get("@options") options[:color][:array].should == :black @@ -374,7 +374,7 @@ EOS #------------------------------------------------------------------------------ describe "Struct" do - before(:each) do + before do @struct = unless defined?(Struct::SimpleStruct) Struct.new("SimpleStruct", :name, :address).new else diff --git a/spec/logger_spec.rb b/spec/logger_spec.rb index b5a9c80..7e80326 100644 --- a/spec/logger_spec.rb +++ b/spec/logger_spec.rb @@ -17,8 +17,8 @@ describe "AwesomePrint logging extensions" do end describe "the log level" do - before(:each) do - AwesomePrint.defaults = { } + before do + AwesomePrint.defaults = {} end it "should fallback to the default :debug log level" do